net.officefloor.server.stream.impl.ThreadLocalStreamBufferPool Maven / Gradle / Ivy
/*-
* #%L
* HTTP Server
* %%
* Copyright (C) 2005 - 2020 Daniel Sagenschneider
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package net.officefloor.server.stream.impl;
import java.nio.ByteBuffer;
import java.util.Deque;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicInteger;
import net.officefloor.frame.api.managedobject.pool.ManagedObjectPool;
import net.officefloor.frame.api.managedobject.pool.ThreadCompletionListener;
import net.officefloor.frame.api.managedobject.pool.ThreadCompletionListenerFactory;
import net.officefloor.server.stream.BufferJvmFix;
import net.officefloor.server.stream.ByteBufferFactory;
import net.officefloor.server.stream.StreamBuffer;
import net.officefloor.server.stream.StreamBufferPool;
/**
* {@link StreamBufferPool} of {@link ByteBuffer} instances that utilises
* {@link ThreadLocal} caches for performance.
*
* @author Daniel Sagenschneider
*/
public class ThreadLocalStreamBufferPool extends AbstractStreamBufferPool
implements ThreadCompletionListenerFactory, ThreadCompletionListener {
/**
* {@link ThreadLocalPool}.
*/
private final ThreadLocal threadLocalPool = new ThreadLocal<>();
/**
* Number of {@link StreamBuffer} instances in circulation.
*/
private final AtomicInteger bufferCount = new AtomicInteger(0);
/**
* {@link ByteBufferFactory}.
*/
private final ByteBufferFactory byteBufferFactory;
/**
* Maximum {@link ThreadLocal} pool size.
*/
private final int maxThreadLocalPoolSize;
/**
* Maximum core pool size.
*/
private volatile int maxCorePoolSize;
/**
* Core pool of {@link StreamBuffer} instances.
*/
private Deque> corePool = new ConcurrentLinkedDeque<>();
/**
*
* Instantiate with details of pool sizes.
*
* The total potential amount of memory used is:
*
* pooledByteBufferSize
* (active buffers
+
* (threadLocalPoolSize
* active threads
) +
* corePoolSize
).
*
* @param byteBufferFactory {@link ByteBufferFactory}.
* @param maxThreadLocalPoolSize Maximum {@link ThreadLocal} pool size.
* @param maxCorePoolSize Maximum core pool size.
*/
public ThreadLocalStreamBufferPool(ByteBufferFactory byteBufferFactory, int maxThreadLocalPoolSize,
int maxCorePoolSize) {
this.byteBufferFactory = byteBufferFactory;
this.maxThreadLocalPoolSize = maxThreadLocalPoolSize;
this.maxCorePoolSize = maxCorePoolSize;
}
/**
* Activates {@link ThreadLocal} pooling of {@link StreamBuffer} on current
* {@link Thread}.
*/
public void activeThreadLocalPooling() {
// Ensure only singleton on the thread
if (this.threadLocalPool.get() == null) {
this.threadLocalPool.set(new ThreadLocalPool());
}
}
/**
* Obtains the number of {@link StreamBuffer} instances in circulation.
*
* @return Number of {@link StreamBuffer} instances in circulation.
*/
public int getStreamBufferCount() {
return this.bufferCount.get();
}
/**
* Releases the {@link StreamBuffer} to the core pool.
*
* @param buffer {@link StreamBuffer}.
*/
private void releaseToCorePool(StreamBuffer buffer) {
// Determine if release (keep approximate core pool size)
if (this.corePool.size() < this.maxCorePoolSize) {
// Released to core pool
this.corePool.push(buffer);
return;
}
// Allow buffer to be garbage collected (too many buffers)
this.bufferCount.decrementAndGet();
}
/**
* Creates a pooled {@link StreamBuffer}.
*
* @return New pooled {@link StreamBuffer}.
*/
private StreamBuffer createPooledStreamBuffer() {
// Create and return new buffer
ByteBuffer byteBuffer = this.byteBufferFactory.createByteBuffer();
StreamBuffer streamBuffer = new PooledStreamBuffer(byteBuffer);
// Capture created buffer
this.bufferCount.incrementAndGet();
// Return the created buffer
return streamBuffer;
}
/**
* =============== StreamBufferPool ===========================
*/
@Override
public StreamBuffer getPooledStreamBuffer() {
// Attempt to obtain from thread local pool
ThreadLocalPool pool = threadLocalPool.get();
if (pool != null) {
if (pool.threadHead != null) {
// Obtain from thread pool
StreamBuffer pooledBuffer = pool.threadHead;
pool.threadHead = pool.threadHead.next;
pool.threadPoolSize--;
// Clear buffer, so reset for use
BufferJvmFix.clear(pooledBuffer.pooledBuffer);
pooledBuffer.next = null;
// Use the thread local buffer
return pooledBuffer;
}
}
// No thread buffers, so attempt core pool
StreamBuffer pooledBuffer = this.corePool.poll();
if (pooledBuffer != null) {
// Clear buffer, so reset for use
BufferJvmFix.clear(pooledBuffer.pooledBuffer);
pooledBuffer.next = null;
// Use the core pool buffer
return pooledBuffer;
}
// Create new buffer
return this.createPooledStreamBuffer();
}
@Override
public void close() {
// Thread local pools clean on thread exit, so avoid going to core
this.maxCorePoolSize = 0;
// Release reference to allow GC of core pooled buffers
this.corePool.clear();
}
/**
* ============= ThreadCompletionListenerFactory =========
*/
@Override
public ThreadCompletionListener createThreadCompletionListener(ManagedObjectPool pool) {
return this;
}
/**
* ================= ThreadCompletionListener ============
*/
@Override
public void threadComplete() {
// Obtain the thread pool
ThreadLocalPool pool = this.threadLocalPool.get();
if (pool == null) {
return; // no thread local pool to clean up
}
// Release all to pool
StreamBuffer buffer = pool.threadHead;
while (buffer != null) {
// Obtain release buffer (must obtain next, as release sets next)
StreamBuffer release = buffer;
buffer = buffer.next;
// Release the buffer
this.releaseToCorePool(release);
}
// Remove from thread local pooling
this.threadLocalPool.remove();
}
/**
* Pooled {@link StreamBuffer}.
*/
private class PooledStreamBuffer extends StreamBuffer {
/**
* Instantiate.
*
* @param byteBuffer {@link ByteBuffer}.
*/
private PooledStreamBuffer(ByteBuffer byteBuffer) {
super(byteBuffer, null, null);
}
/*
* ================= StreamBuffer ========================
*/
@Override
public boolean write(byte datum) {
// Ensure space write data
if (this.pooledBuffer.remaining() <= 0) {
return false; // buffer is full
}
// Write the data
this.pooledBuffer.put(datum);
return true;
}
@Override
public int write(byte[] data, int offset, int length) {
// Obtain the bytes to write
int writeBytes = Math.min(length, this.pooledBuffer.remaining());
// Write the bytes
this.pooledBuffer.put(data, offset, writeBytes);
// Return the number of written bytes
return writeBytes;
}
@Override
public void release() {
// Easy access to pool
@SuppressWarnings("resource")
ThreadLocalStreamBufferPool bufferPool = ThreadLocalStreamBufferPool.this;
// Attempt to release to thread local pool
ThreadLocalPool pool = bufferPool.threadLocalPool.get();
if (pool != null) {
if (pool.threadPoolSize < bufferPool.maxThreadLocalPoolSize) {
// Release to thread pool
this.next = pool.threadHead;
pool.threadHead = this;
pool.threadPoolSize++;
return; // released
}
}
// As here, release to core pool
bufferPool.releaseToCorePool(this);
}
}
/**
* {@link Thread} local pool of {@link StreamBuffer} instances.
*/
private static class ThreadLocalPool {
/**
* Head {@link StreamBuffer} to linked list of {@link StreamBuffer} instances.
*/
private StreamBuffer threadHead = null;
/**
* Number of {@link StreamBuffer} instances within this pool.
*/
private int threadPoolSize = 0;
}
}