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

com.files.util.BufferPool Maven / Gradle / Ivy

Go to download

The Files.com Java client library provides convenient access to the Files.com API from JVM based applications.

There is a newer version: 1.4.123
Show newest version
package com.files.util;

import com.files.FilesConfig;
import java.util.Collection;
import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Semaphore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.threadly.util.StackSuppressedRuntimeException;

public class BufferPool {
  private static final Logger log = LoggerFactory.getLogger(BufferPool.class);
  private static final boolean TRACK_BUFFER_CREATION_STACK = true;
  public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
  public static final int T_BUFFER_SIZE = FilesConfig.getInstance().getCachedBufferTinySize();
  public static final int S_BUFFER_SIZE = FilesConfig.getInstance().getCachedBufferSmallSize();
  public static final int M_BUFFER_SIZE = FilesConfig.getInstance().getCachedBufferMediumSize();
  public static final int L_BUFFER_SIZE = FilesConfig.getInstance().getCachedBufferLargeSize();
  protected static final int T_BUFFER_TARGET_COUNT;
  protected static final int S_BUFFER_TARGET_COUNT;
  protected static final int M_BUFFER_TARGET_COUNT;
  protected static final int MAX_ALLOCATION;
  protected static final Semaphore TOTAL_ALLOCATED;
  protected static final Queue T_BUFFERS = new ConcurrentLinkedQueue<>();
  protected static final Queue S_BUFFERS = new ConcurrentLinkedQueue<>();
  protected static final Queue M_BUFFERS = new ConcurrentLinkedQueue<>();
  protected static final Queue L_BUFFERS = new ConcurrentLinkedQueue<>();

  static {
    MAX_ALLOCATION = FilesConfig.getInstance().getCachedBufferMaxBytes();
    TOTAL_ALLOCATED = new Semaphore(MAX_ALLOCATION);
    T_BUFFER_TARGET_COUNT = (int) ((MAX_ALLOCATION * .25) / T_BUFFER_SIZE);
    S_BUFFER_TARGET_COUNT = (int) ((MAX_ALLOCATION * .25) / S_BUFFER_SIZE);
    M_BUFFER_TARGET_COUNT = (int) ((MAX_ALLOCATION * .25) / M_BUFFER_SIZE);
    // remaining % is provided as minimum cached allocations to large buffers
  }

  private BufferPool() {
  }

  public static int getAvailableAllocation() {
    return TOTAL_ALLOCATED.availablePermits();
  }

  public static int getTotalAllocated() {
    return MAX_ALLOCATION - TOTAL_ALLOCATED.availablePermits();
  }

  public static int getWaitingToAllocateThreadCount() {
    return TOTAL_ALLOCATED.getQueueLength();
  }

  public static int availableTinyBufferCount() {
    return T_BUFFERS.size();
  }

  public static int availableSmallBufferCount() {
    return S_BUFFERS.size();
  }

  public static int availableMediumBufferCount() {
    return M_BUFFERS.size();
  }

  public static int availableLargeBufferCount() {
    return L_BUFFERS.size();
  }

  public static Buffer needBuffer(int minSize) {
    try {
      if (minSize <= T_BUFFER_SIZE) {
        return needTinyBuffer(minSize);
      } else if (minSize <= S_BUFFER_SIZE) {
        return needSmallBuffer(minSize);
      } else if (minSize <= M_BUFFER_SIZE) {
        return needMediumBuffer(minSize);
      } else if (minSize <= L_BUFFER_SIZE) {
        return needLargeBuffer(minSize);
      } else {
        throw new IllegalArgumentException("Buffer size too large: " + minSize);
      }
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      throw new RuntimeException("Interrupted waiting for buffer", e);
    }
  }

  private static Buffer atomicPoll(Queue buffers) {
    byte[] bytes = buffers.poll();
    if (bytes == null) {
      return null;
    } else {
      return new Buffer(bytes);
    }
  }

  private static Buffer needTinyBuffer(int minSize) throws InterruptedException {
    while (true) {
      Buffer result = atomicPoll(T_BUFFERS);
      if (result == null) {
        result = tryMakeBuffer(minSize, T_BUFFER_SIZE);
        if (result == null) {
          // we can't allocate, lets try to find a larger buffer we can de-allocate first
          Buffer larger = atomicPoll(S_BUFFERS);
          if (larger == null) {
            larger = atomicPoll(M_BUFFERS);
          }
          if (larger == null) {
            larger = atomicPoll(L_BUFFERS);
          }
          if (larger != null) {
            // use swapBuffer to make sure we get a buffer from de-allocation
            return swapBuffer(larger, T_BUFFER_SIZE);
          }
        } else {
          return result;
        }
      } else {
        return result;
      }

      noBufferBlock(T_BUFFERS);
    }
  }

  private static Buffer needSmallBuffer(int minSize) throws InterruptedException {
    int attempt = 0;
    while (true) {
      attempt++;
      Buffer result = atomicPoll(S_BUFFERS);
      if (result == null) {
        result = tryMakeBuffer(minSize, S_BUFFER_SIZE);
        if (result == null) {
          // we can't allocate, lets try to find a larger buffer we can de-allocate first
          Buffer larger = atomicPoll(M_BUFFERS);
          if (larger == null) {
            larger = atomicPoll(L_BUFFERS);
          }
          if (larger != null) {
            // use swapBuffer to make sure we get a buffer from de-allocation
            return swapBuffer(larger, S_BUFFER_SIZE);
          } else if (attempt % 2 == 1) {
            // on every other attempt we make sure our failure to allocate
            // is not due to large amounts of small buffers
            trimCache(T_BUFFER_TARGET_COUNT, T_BUFFERS);
          }
        } else {
          return result;
        }
      } else {
        return result;
      }

      noBufferBlock(S_BUFFERS);
    }
  }

  private static Buffer needMediumBuffer(int minSize) throws InterruptedException {
    int attempt = 0;
    while (true) {
      attempt++;
      Buffer result = atomicPoll(M_BUFFERS);
      if (result == null) {
        result = tryMakeBuffer(minSize, M_BUFFER_SIZE);
        if (result == null) {
          // we can't allocate, lets try to find a larger buffer we can de-allocate first
          Buffer larger = atomicPoll(L_BUFFERS);
          if (larger != null) {
            // use swapBuffer to make sure we get a buffer from de-allocation
            return swapBuffer(larger, M_BUFFER_SIZE);
          } else if (attempt % 2 == 1) {
            // make sure failure to allocate is not due to large amounts of small buffers
            // trim which ever queue would save the most memory
            if (((T_BUFFERS.size() - T_BUFFER_TARGET_COUNT)
                * T_BUFFER_SIZE) <= ((S_BUFFERS.size() - S_BUFFER_TARGET_COUNT) * S_BUFFER_SIZE)) {
              trimCache(S_BUFFER_TARGET_COUNT, S_BUFFERS);
            } else {
              trimCache(T_BUFFER_TARGET_COUNT, T_BUFFERS);
            }
          }
        } else {
          return result;
        }
      } else {
        return result;
      }

      noBufferBlock(M_BUFFERS);
    }
  }

  private static Buffer needLargeBuffer(int minSize) throws InterruptedException {
    int attempt = 0;
    while (true) {
      attempt++;
      Buffer result = atomicPoll(L_BUFFERS);
      if (result == null) {
        result = tryMakeBuffer(minSize, L_BUFFER_SIZE);
        if (result == null) {
          if (attempt % 2 == 1) {
            // make sure failure to allocate is not due to large amounts of small buffers
            // trim which ever queue would save the most memory
            int tinySavings = (T_BUFFERS.size() - T_BUFFER_TARGET_COUNT) * T_BUFFER_SIZE;
            int smallSavings = (S_BUFFERS.size() - S_BUFFER_TARGET_COUNT) * S_BUFFER_SIZE;
            int medSavings = (M_BUFFERS.size() - M_BUFFER_TARGET_COUNT) * M_BUFFER_SIZE;
            if (medSavings >= smallSavings && medSavings >= tinySavings) {
              trimCache(M_BUFFER_TARGET_COUNT, M_BUFFERS);
            } else if (smallSavings >= tinySavings) {
              trimCache(S_BUFFER_TARGET_COUNT, S_BUFFERS);
            } else {
              trimCache(T_BUFFER_TARGET_COUNT, T_BUFFERS);
            }
          }
        } else {
          return result;
        }
      } else {
        return result;
      }

      noBufferBlock(L_BUFFERS);
    }
  }

  private static void noBufferBlock(Collection preferedQueue) throws InterruptedException {
    synchronized (preferedQueue) {
      if (preferedQueue.isEmpty()) {
        preferedQueue.wait(50);
      }
    }
  }

  private static Buffer tryMakeBuffer(int minSize, int idealSize) {
    if (TOTAL_ALLOCATED.tryAcquire(idealSize)) {
      return new Buffer(idealSize);
    } else if (TOTAL_ALLOCATED.tryAcquire(minSize)) {
      return new Buffer(minSize);
    } else {
      return null;
    }
  }

  private static void trimCache(int targetSize, Queue buffers) {
    while (buffers.size() > targetSize) {
      byte[] b = buffers.poll();
      if (b != null) {
        releaseBytes(b);
      }
    }
  }

  private static void cacheBytes(byte[] bytes) {
    Queue bufferCache = null;
    if (bytes.length == T_BUFFER_SIZE) {
      bufferCache = T_BUFFERS;
    } else if (bytes.length == S_BUFFER_SIZE) {
      bufferCache = S_BUFFERS;
    } else if (bytes.length == M_BUFFER_SIZE) {
      bufferCache = M_BUFFERS;
    } else if (bytes.length == L_BUFFER_SIZE) {
      bufferCache = L_BUFFERS;
    }

    if (bufferCache == null) {
      releaseBytes(bytes);
    } else {
      bufferCache.add(bytes);

      synchronized (bufferCache) {
        bufferCache.notifyAll();
      }
    }
  }

  private static void releaseBytes(byte[] b) {
    TOTAL_ALLOCATED.release(b.length);
  }

  private static Buffer swapBuffer(Buffer larger, int newSize) {
    int releaseAmount = larger.bytes.length - newSize;
    synchronized (larger.bytes) {
      if (releaseAmount <= 0) {
        throw new IllegalStateException("Buffer smaller than new allocation");
      } else if (larger.closed) {
        throw new IllegalStateException("Buffer already released");
      }

      larger.closed = true;
    }
    TOTAL_ALLOCATED.release(releaseAmount);
    return new Buffer(newSize);
  }

  public static class Buffer implements AutoCloseable {
    public static final Buffer POISON_PILL = new Buffer(EMPTY_BYTE_ARRAY) {
      {
        closed = true;
      }
    };

    private final StackTraceElement[] creationStack = TRACK_BUFFER_CREATION_STACK
        ? Thread.currentThread().getStackTrace()
        : null;
    private final byte[] bytes;
    private int offset;
    private int length;
    protected volatile boolean closed;

    public Buffer(int size) {
      this.bytes = new byte[size];
      this.offset = 0;
      this.length = size;
    }

    public Buffer(byte[] bytes) {
      this.bytes = bytes;
      this.offset = 0;
      this.length = bytes.length;
    }

    public byte[] getBuffer() {
      if (closed) {
        throw new IllegalStateException("Buffer closed");
      }
      return bytes;
    }

    public int getRemaining() {
      if (closed) {
        throw new IllegalStateException("Buffer closed");
      }
      return length - offset;
    }

    public void setLength(int length) {
      if (length > bytes.length) {
        throw new IllegalArgumentException("Length beyond byte size");
      } else if (length < 0) {
        throw new IllegalArgumentException("Length can't be negative");
      }

      this.length = length;
    }

    public int getOffset() {
      if (closed) {
        throw new IllegalStateException("Buffer closed");
      }
      return offset;
    }

    public void incrementOffset(int count) {
      setOffset(offset + count);
    }

    public void setOffset(int offset) {
      if (offset > length) {
        throw new IllegalArgumentException("Offset beyond length");
      } else if (offset < 0) {
        throw new IllegalArgumentException("Offset can't be negative");
      }

      this.offset = offset;
    }

    public void flip() {
      this.length = this.offset;
      this.offset = 0;
    }

    public void closeAsUnhealthy() {
      synchronized (bytes) {
        if (closed) {
          throw new IllegalStateException("Buffer already released");
        }
        closed = true;
        releaseBytes(bytes);
      }
    }

    @Override
    public void close() {
      if (closed) {
        return;
      }
      atomicClose();
    }

    private void atomicClose() {
      synchronized (bytes) {
        if (!closed) {
          closed = true;

          cacheBytes(bytes);
        }
      }
    }

    private Throwable creationLogThrowable() {
      if (creationStack == null) {
        return null;
      } else {
        StackSuppressedRuntimeException ex = new StackSuppressedRuntimeException();
        ex.setStackTrace(creationStack);
        return ex;
      }
    }

    @Override
    protected void finalize() {
      if (!closed) {
        log.warn("Buffer not released!", creationLogThrowable());
        atomicClose();
      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy