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

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

There is a newer version: 10.1.1
Show newest version
/* This file is part of VoltDB.
 * Copyright (C) 2008-2018 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 sun.misc.Cleaner;
import sun.nio.ch.DirectBuffer;

#ifndef NO_MEMCHECK
import java.util.List;
import java.util.ArrayList;
#endif

/**
 * 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");
#ifndef NO_MEMCHECK

    static {
        System.setProperty("io.netty.leakDetection.level", "ADVANCED");
        final String msg = "Strict java memory checking is enabled, don't do release builds " +
                     "or performance runs with this enabled. Invoke \"ant clean\" and \"ant -Djmemcheck=NO_MEMCHECK\" to disable.";
        HOST.warn(msg);
    }
#endif

    /**
     * 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;
#ifndef NO_MEMCHECK
        private volatile Throwable m_freeThrowable;
        private final Throwable m_allocationThrowable;
        private List m_tags = null;
#else
        private boolean m_previouslyFreed = false;
#endif

        public BBContainer(ByteBuffer b) {
#ifndef NO_MEMCHECK
            m_allocationThrowable = new Throwable("\"" + Thread.currentThread().getName() + "\" at " + System.currentTimeMillis());
#endif
            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() {
#ifndef NO_MEMCHECK
            if (m_freeThrowable != null) {
                System.err.println("Use after free in DBBPool");
                System.err.println("Free was by:");
                m_freeThrowable.printStackTrace();
                System.err.println("Use was by:");
                Throwable t = new Throwable("\"" + Thread.currentThread().getName() + "\" at " + System.currentTimeMillis());
                t.printStackTrace();
                if (isTagged()) {
                    for (String tag: m_tags) {
                        System.err.println(tag);
                    }
                }
                HOST.fatal("Use after free in DBBPool");
                HOST.fatal("Free was by:", m_freeThrowable);
                HOST.fatal("Use was by:", t);
                System.exit(-1);
            }
#else
            if (m_previouslyFreed) {
                crash("Use after free in DBBPool", true, null);
            }
#endif
        }

        final protected ByteBuffer checkDoubleFree() {
#ifndef NO_MEMCHECK
            synchronized (this) {
                if (m_freeThrowable != null) {
                    System.err.println("Double free in DBBPool");
                    System.err.println("Original free was by:");
                    m_freeThrowable.printStackTrace();
                    System.err.println("Current free was by:");
                    Throwable t = new Throwable("\"" + Thread.currentThread().getName() + "\" at " + System.currentTimeMillis());
                    t.printStackTrace();
                    if (isTagged()) {
                        for (String tag: m_tags) {
                            System.err.println(tag);
                        }
                    }
                    HOST.fatal("Double free in DBBPool");
                    HOST.fatal("Original free was by:", m_freeThrowable);
                    HOST.fatal("Current free was by:", t);
                    System.exit(-1);
                }
                m_freeThrowable = new Throwable("\"" + Thread.currentThread().getName() + "\" at " + System.currentTimeMillis());
            }
#else
            if (m_previouslyFreed) {
                crash("Double free in DBBPool", true, null);
            }
            m_previouslyFreed = true;
#endif
            return b;
        }

        public final void tag(final String tag) {
#ifndef NO_MEMCHECK
            StringBuilder sb = new StringBuilder(1024);
            sb.append("<> ");
            sb.append(tag).append("\n");
            sb.append(CoreUtils.throwableToString(new Throwable()));
            synchronized(this) {
                if (m_tags == null) {
                    m_tags = new ArrayList();
                }
                m_tags.add(sb.toString());
            }
#endif
        }

        public final void addToTagTrail(final String tag) {
#ifndef NO_MEMCHECK
            if (isTagged()) tag(tag);
#endif
        }

        public boolean isTagged() {
#ifndef NO_MEMCHECK
            return m_tags != null && !m_tags.isEmpty();
#else
            return false;
#endif
        }

#ifndef NO_MEMCHECK
        @Override
        public void finalize() {
            if (m_freeThrowable == null) {
                System.err.println("BBContainer " + Integer.toHexString(this.hashCode()) + " was never discarded allocated by:");
                m_allocationThrowable.printStackTrace();
                if (isTagged()) {
                    for (String tag: m_tags) {
                        System.err.println(tag);
                    }
                }
                HOST.fatal("BBContainer " + Integer.toHexString(this.hashCode()) + " was never discarded allocated by:", m_allocationThrowable);
                System.exit(-1);
            }
        }
#endif
    }

    /**
     * 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);
        }
    }

    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 try clearing the pool
    private 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) {
                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);
        }
    }

    public static void registerUnsafeMemory(long pointer) {
#ifdef MEMCHECK_FULL
        m_allocatedStuff.put(pointer, new Throwable("Thread \"" + Thread.currentThread().getName() + "\" registered " + Long.toHexString(pointer) + " here at " + System.currentTimeMillis()));
        m_deletedStuff.remove(pointer);
#endif
    }
#ifdef MEMCHECK_FULL

    private static NonBlockingHashMap m_deletedStuff = new NonBlockingHashMap();
    private static NonBlockingHashMap m_allocatedStuff = new NonBlockingHashMap();
#endif

    /*
     * Delete a char array that was allocated on the native heap
     *
     * If you want to debug issues with double free, uncomment m_deletedStuff
     * and the code that populates it and comment out the call to nativeDeleteCharArrayMemory
     * and it will validate that nothing is ever deleted twice at the cost of unbounded memory usage
     */
    private static void deleteCharArrayMemory(long pointer) {
#ifdef MEMCHECK_FULL
        if (!m_allocatedStuff.containsKey(pointer)) {
            Long closest = Long.MAX_VALUE;
            for (Long l : m_allocatedStuff.keySet()) {
                long delta = pointer - l;
                if (delta < 0) continue;
                if (delta < pointer - closest) closest = l;
            }
            Throwable t = new Throwable("Thread \"" + Thread.currentThread().getName() + "\" deleted a pointer that was never allocated " + Long.toHexString(pointer) + " here at " + System.currentTimeMillis());
            t.printStackTrace();
            if (!closest.equals(Long.MAX_VALUE)) {
                System.err.println("Closest known allocation is:");
                m_allocatedStuff.get(closest).printStackTrace();
            }
            HOST.fatal("Deleted address that was never allocated", t);
            if (!closest.equals(Long.MAX_VALUE)) HOST.fatal("Closest known allocation was:", m_allocatedStuff.get(closest));
            System.exit(-1);
        }
        m_allocatedStuff.remove(pointer);
        if (m_deletedStuff.putIfAbsent(pointer, new Throwable("Thread \"" + Thread.currentThread().getName() + "\" deleted " + Long.toHexString(pointer) + " here at " + System.currentTimeMillis())) != null) {
            m_deletedStuff.get(pointer).printStackTrace();
            Throwable t = new Throwable("Thread \"" + Thread.currentThread().getName() + "\" deleted " + Long.toHexString(pointer) + " twice at " + System.currentTimeMillis());
            HOST.fatal("Original:", m_deletedStuff.get(pointer));
            HOST.fatal("Second:", t);
            System.exit(-1);
        }
#else
        nativeDeleteCharArrayMemory(pointer);
#endif
    }

    private static native void nativeDeleteCharArrayMemory(long pointer);

    public static BBContainer allocateUnsafeByteBuffer(long size) {
        final BBContainer retcont = DBBPool.wrapBB(nativeAllocateUnsafeByteBuffer(size));
#ifdef MEMCHECK_FULL
        final long pointer = retcont.address();
        m_allocatedStuff.put(pointer, new Throwable("Thread \"" + Thread.currentThread().getName() + "\" allocated " + Long.toHexString(pointer) + " here at " + System.currentTimeMillis()));
        m_deletedStuff.remove(pointer);
#endif
        return retcont;
    }

    /*
     * 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 pointer can be freed
     * using deleteCharArrayMemory.
     */
    private static native ByteBuffer nativeAllocateUnsafeByteBuffer(long size);

    /*
     * For managed buffers runs the cleaner, if there is no cleaner,
     * called deleteCharArrayMemory on the address
     */
    private static void cleanByteBuffer(ByteBuffer buf) {
        if (buf == null) return;
        if (!buf.isDirect()) return;
        final DirectBuffer dbuf = (DirectBuffer) buf;
        final Cleaner cleaner = dbuf.cleaner();
        if (cleaner != null) cleaner.clean();
        else deleteCharArrayMemory(dbuf.address());
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy