All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.voltcore.utils.DBBPool Maven / Gradle / Ivy

/* This file is part of VoltDB.
 * Copyright (C) 2008-2020 VoltDB Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with VoltDB.  If not, see .
 */
package org.voltcore.utils;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicLong;
import com.google_voltpatches.common.base.Preconditions;
import org.cliffc_voltpatches.high_scale_lib.NonBlockingHashMap;
import org.voltcore.logging.VoltLogger;
import org.voltcore.utils.VoltUnsafe;
import sun.nio.ch.DirectBuffer;
/**
 * A pool of {@link java.nio.ByteBuffer ByteBuffers} that are
 * allocated with
 * {@link java.nio.ByteBuffer#allocateDirect(int) * ByteBuffer.allocateDirect}.
 * Buffers are stored in Arenas that are powers of 2. The smallest arena is 16 bytes.
 * Arenas will shrink every 60 seconds if some of the memory isn't being used.
 */
public final class DBBPool {
    private static final VoltLogger TRACE = new VoltLogger("DBBPOOL");
    private static final VoltLogger HOST = new VoltLogger("DBBPOOL");
    /**
     * Abstract base class for a ByteBuffer container. A container serves to hold a reference
     * to the pool/arena/whatever the ByteBuffer was allocated from and possibly the address
     * of the ByteBuffer if it is a DirectByteBuffer. The container also provides the interface
     * for discarding the ByteBuffer and returning it back to the pool. It is a good practice
     * to discard a container even if it is wrapper for HeapByteBuffer that isn't pooled.
     *
     */
    public static abstract class BBContainer {
        /**
         * The buffer
         */
        final private ByteBuffer b;
        private boolean m_previouslyFreed = false;
        public BBContainer(ByteBuffer b) {
            this.b = b;
        }
        final public long address() {
            checkUseAfterFree();
            return ((DirectBuffer)b).address();
        }
        public void discard() {
            checkDoubleFree();
        }
        public ByteBuffer b() {
            checkUseAfterFree();
            return b;
        }
        final public ByteBuffer bD() {
            return b().duplicate();
        }
        final public ByteBuffer bDR() {
            return b().asReadOnlyBuffer();
        }
        final protected void checkUseAfterFree() {
            if (m_previouslyFreed) {
                crash("Use after free in DBBPool", true, null);
            }
        }
        final protected ByteBuffer checkDoubleFree() {
            if (m_previouslyFreed) {
                crash("Double free in DBBPool", true, null);
            }
            m_previouslyFreed = true;
            return b;
        }
        public final void tag(final String tag) {
        }
        public final void addToTagTrail(final String tag) {
        }
        public boolean isTagged() {
            return false;
        }
        @Override
        public String toString() {
            return getClass().getSimpleName() + ": " + b;
        }
    }
    /**
     * Wrapper for HeapByteBuffers that allows them to pose as ByteBuffers from a pool.
     *
     */
    public static final class BBWrapperContainer extends BBContainer {
        private BBWrapperContainer(ByteBuffer b) {
            super( b );
        }
        @Override
        public final void discard() {
            super.discard();
        }
    }
    public static final class DBBWrapperContainer extends BBContainer {
        private DBBWrapperContainer(ByteBuffer b) {
            super( b );
        }
        @Override
        public final void discard() {
            final ByteBuffer buf = checkDoubleFree();
            DBBPool.cleanByteBuffer(buf);
        }
    }
    /**
     * Wrapper for native allocated direct memory, only instantiated
     * in the native JNI topend. Ensures the native memory is properly
     * discarded.
     *
     */
    public static final class NDBBWrapperContainer extends BBContainer {
        private NDBBWrapperContainer(ByteBuffer b) {
            super( b );
            assert b.isDirect();
        }
        @Override
        public final void discard() {
            final ByteBuffer buf = checkDoubleFree();
            if (buf == null || !buf.isDirect()) {
               return;
            }
            final DirectBuffer dbuf = (DirectBuffer) buf;
            nativeDeleteCharArrayMemory(dbuf.address());
        }
    }
    public static class DBBDelegateContainer extends BBContainer {
        protected final BBContainer m_delegate;
        protected DBBDelegateContainer(BBContainer delegate) {
            super(delegate.b());
            m_delegate = delegate;
        }
        @Override
        public void discard() {
            checkDoubleFree();
            m_delegate.discard();
        }
    }
    public static class MBBContainer extends BBContainer {
        private MBBContainer(MappedByteBuffer buf) {
            super(buf);
        }
        @Override
        public MappedByteBuffer b() {
            return (MappedByteBuffer)super.b();
        }
        @Override
        public void discard() {
            final ByteBuffer buf = checkDoubleFree();
            DBBPool.cleanByteBuffer(buf);
        }
    }
    public static final BBContainer dummyWrapBB(ByteBuffer b) {
        return new BBWrapperContainer(b);
    }
    /**
     * Static factory method to wrap a ByteBuffer in a BBContainer that is not
     * associated with any pool
     * @param b
     */
    public static final BBContainer wrapBB(ByteBuffer b) {
        if (b.isDirect()) {
            return new DBBWrapperContainer(b);
        } else {
            return new BBWrapperContainer(b);
        }
    }
    public static final MBBContainer wrapMBB(ByteBuffer b) {
        Preconditions.checkArgument(b.isDirect());
        return new MBBContainer((MappedByteBuffer)b);
    }
    /**
     * Number of bytes allocated globally by DBBPools
     */
    private static AtomicLong bytesAllocatedGlobally = new AtomicLong(0);
    static long getBytesAllocatedGlobally()
    {
        return bytesAllocatedGlobally.get();
    }
    private static final VoltLogger m_logger = new VoltLogger(DBBPool.class.getName());
    /**
     * Retrieve the CRC32C value of a DirectByteBuffer as a long
     * The polynomial is different from java.util.zip.CRC32C,
     * and matches the one used by SSE 4.2. hardware CRC instructions.
     * The implementation will use the SSE 4.2. instruction if the native library
     * was compiled with -msse4.2 and there is hardware support, otherwise it falls
     * back to Intel's slicing by 8 algorithm
     * @param b Buffer you want to retrieve the CRC32 of
     * @param offset Offset into buffer to start calculations
     * @param length Length of the buffer to calculate
     * @return CRC32C of the buffer as an int.
     */
    public static native int getBufferCRC32C( ByteBuffer b, int offset, int length);
    /**
     * Retrieve the CRC32C value of a DirectByteBuffer as a long
     * The polynomial is different from java.util.zip.CRC32C,
     * and matches the one used by SSE 4.2. hardware CRC instructions.
     * The implementation will use the SSE 4.2. instruction if the native library
     * was compiled with -msse4.2 and there is hardware support, otherwise it falls
     * back to Intel's slicing by 8 algorithm
     * @param ptr Address of buffer you want to retrieve the CRC32C of
     * @param offset Offset into buffer to start calculations
     * @param length Length of the buffer to calculate
     * @return CRC32C of the buffer as an int.
     */
    public static native int getCRC32C( long ptr, int offset, int length);
    /**
     * Retrieve the CRC32 value of a DirectByteBuffer as a long
     * @param b Buffer you want to retrieve the CRC32 of
     * @param offset Offset into buffer to start calculations
     * @param length Length of the buffer to calculate
     * @return CRC32 of the buffer as an int.
     */
    public static native int getBufferCRC32( ByteBuffer b, int offset, int length);
    /**
     * Retrieve the CRC32 value of a DirectByteBuffer as a long
     * @param ptr Address of buffer you want to retrieve the CRC32 of
     * @param offset Offset into buffer to start calculations
     * @param length Length of the buffer to calculate
     * @return CRC32 of the buffer as an int.
     */
    public static native int getCRC32( long ptr, int offset, int length);
    /**
     * Retrieve the first 8 bytes of the Murmur hash3_x64_128 of DirectByteBuffer a
     * as a long
     * @param ptr pointer to the buffer
     * @param offset Offset into buffer to start calculations
     * @param length Length of the buffer to calculate
     * @return First 8 bytes of  Murmur hash3_x64_128 of buffer
     */
    public static native int getMurmur3128( long ptr, int offset, int length);
    /**
     * Retrieve the first 8 bytes of the Murmur hash3_x64_128 of long value
     * @param value value to hash
     * @return First 8 bytes of  Murmur hash3_x64_128 of value
     */
    public static native int getMurmur3128( long value);
    private static final NonBlockingHashMap> m_pooledBuffers =
            new NonBlockingHashMap>();
    /**
     * Find the closest power of 2 that's larger than or equal to the requested capacity.
     * @return 0 if the requested capacity is 0, the requested capacity itself if the
     * next power of 2 overflows, or the next power of 2.
     */
    static int roundToClosestPowerOf2(int capacity) {
        if (capacity == 0) return 0;
        else if (capacity == 1) return 2;
        final int result = Integer.highestOneBit(capacity - 1) << 1;
        return result < 0 ? capacity : result;
    }
    /**
     * Allocate a DirectByteBuffer from a global lock free pool. The allocated buffer may
     * have a capacity larger than the requested size. The limit will be set to the requested
     * size.
     */
    public static BBContainer allocateDirectAndPool(final Integer capacity) {
        final int bucket = roundToClosestPowerOf2(capacity);
        ConcurrentLinkedQueue pooledBuffers = m_pooledBuffers.get(bucket);
        if (pooledBuffers == null) {
            pooledBuffers = new ConcurrentLinkedQueue();
            if (m_pooledBuffers.putIfAbsent(bucket, pooledBuffers) != null) {
                pooledBuffers = m_pooledBuffers.get(bucket);
            }
        }
        BBContainer cont = pooledBuffers.poll();
        if (cont == null) {
            cont = allocateDirect(bucket);
        }
        final BBContainer origin = cont;
        cont = new BBContainer(origin.b()) {
            @Override
            public void discard() {
                checkDoubleFree();
                m_pooledBuffers.get(bucket).offer(origin);
            }
        };
        cont.b().clear();
        cont.b().limit(capacity);
        return cont;
    }
    //In OOM conditions (and new instances of LocalServerThread) try clearing the pool
    public static void clear() {
        long startingBytes = bytesAllocatedGlobally.get();
        for (ConcurrentLinkedQueue pool : m_pooledBuffers.values()) {
            BBContainer cont = null;
            while ((cont = pool.poll()) != null) {
                cont.discard();
            }
        }
        new VoltLogger("HOST").warn(
                "Attempted to resolve DirectByteBuffer OOM by freeing pooled buffers. " +
                "Starting bytes was " + startingBytes + " after clearing " +
                 bytesAllocatedGlobally.get() + " change " + (startingBytes - bytesAllocatedGlobally.get()));
    }
    private static void logAllocation(int capacity) {
        if (TRACE.isTraceEnabled()) {
            String message =
                    "Allocated DBB capacity " + capacity +
                     " total allocated " + bytesAllocatedGlobally.get() +
                     " from " + CoreUtils.throwableToString(new Throwable());
            TRACE.trace(message);
        }
    }
    private static void logDeallocation(int capacity) {
        if (TRACE.isTraceEnabled()) {
            String message =
                    "Deallocated DBB capacity " + capacity +
                    " total allocated " + bytesAllocatedGlobally.get() +
                    " from " + CoreUtils.throwableToString(new Throwable());
            TRACE.trace(message);
        }
    }
    /*
     * The only reason to not retrieve the address is that network code shared
     * with the java client shouldn't have a dependency on the native library
     */
    public static BBContainer allocateDirect(final int capacity) {
        ByteBuffer retval = null;
        try {
            retval = ByteBuffer.allocateDirect(capacity);
        } catch (OutOfMemoryError e) {
            if (e.getMessage().contains("Direct buffer memory")) {
                clear();
                retval = ByteBuffer.allocateDirect(capacity);
            } else {
                throw new Error(e);
            }
        }
        bytesAllocatedGlobally.getAndAdd(capacity);
        logAllocation(capacity);
        return new DeallocatingContainer(retval);
    }
    private static class DeallocatingContainer extends BBContainer {
        private DeallocatingContainer(ByteBuffer buf) {
            super(buf);
        }
        @Override
        public void discard() {
            final ByteBuffer buf = checkDoubleFree();
            try {
                bytesAllocatedGlobally.getAndAdd(-buf.capacity());
                logDeallocation(buf.capacity());
                DBBPool.cleanByteBuffer(buf);
            } catch (Throwable e) {
                HOST.fatal("Failed to deallocate direct byte buffer", e);
                crash("Failed to deallocate direct byte buffer", false, e);
            }
        }
    }
    private static void crash(String msg, boolean stackTrace, Throwable e) {
        // The client code doesn't want to link to the VoltDB class, so this hack was born.
        // It should be temporary as the goal is to remove client code dependency on
        // DBBPool in the medium term.
        try {
            Class vdbClz = Class.forName("org.voltdb.VoltDB");
            Method m = vdbClz.getMethod("crashLocalVoltDB", String.class, boolean.class, Throwable.class);
            m.invoke(null, msg, stackTrace, e);
        } catch (Exception ignored) {
            HOST.fatal(msg, ignored);
            System.err.println(msg);
            e.printStackTrace();
            System.exit(-1);
        }
    }
    private static native void nativeDeleteCharArrayMemory(long pointer);
    /*
     * Allocate a direct byte buffer that bypasses all GC and Java limits
     * and requires manual memory management. The memory will not be zeroed
     * and will come from the new/delete in C++. The returned {@link BBContainer}
     * ensures correct release using nativeDeleteCharArrayMemory.
     */
    public static BBContainer allocateUnsafeByteBuffer(long size) {
        return new NDBBWrapperContainer(nativeAllocateUnsafeByteBuffer(size));
    }
    private static native ByteBuffer nativeAllocateUnsafeByteBuffer(long size);
    /*
     * Run the cleaner on managed direct buffers
     */
    private static void cleanByteBuffer(ByteBuffer buf) {
        if (buf == null || !buf.isDirect()) {
            return;
        }
        VoltUnsafe.cleanDirectBuffer(buf);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy