/* * Copyright (c) 2015-2016, Christoph Engelbert (aka noctarius) and * contributors. All rights reserved. * * 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. */ package com.noctarius.tengi.spi.pooling.impl; import com.noctarius.tengi.core.impl.MathUtil; import com.noctarius.tengi.core.impl.UnsafeUtil; import com.noctarius.tengi.spi.logging.Logger; import com.noctarius.tengi.spi.logging.LoggerManager; import com.noctarius.tengi.spi.pooling.ObjectHandler; import com.noctarius.tengi.spi.pooling.ObjectPool; import com.noctarius.tengi.spi.pooling.ObjectValidator; import com.noctarius.tengi.spi.pooling.PooledObject; import sun.misc.Unsafe; import java.lang.reflect.Field; import java.util.ConcurrentModificationException; import java.util.function.Consumer; public class NonBlockingObjectPool<T> implements ObjectPool<T> { private static final Logger LOGGER = LoggerManager.getLogger(NonBlockingObjectPool.class); private static final long ARRAY_BASE = UnsafeUtil.OBJECT_ARRAY_BASE; private static final long ARRAY_SHIFT = UnsafeUtil.OBJECT_ARRAY_SHIFT; private static final Unsafe UNSAFE = UnsafeUtil.UNSAFE; private static final long OFFSET; private static final int ENTRY_FREE = 0; private static final int ENTRY_USED = 1; private static final int ENTRY_UNPOOLED = 2; static { try { Field field = NonBlockingObjectPool.class.getDeclaredField("nextAcquireIndex"); field.setAccessible(true); OFFSET = UNSAFE.objectFieldOffset(field); } catch (Exception e) { throw new RuntimeException(e); } } private final ThreadLocal<Entry<T>> threadCache = new ThreadLocal<>(); private final ObjectHandler<T> handler; private final ObjectValidator<T> validator; private final Object[] entryPool; private final int size; private volatile int nextAcquireIndex = 0; public NonBlockingObjectPool(ObjectHandler<T> handler, ObjectValidator<T> validator, int size) { this.handler = handler; this.validator = validator; this.size = MathUtil.nextPowerOfTwo(size); this.entryPool = createEntryPool(handler, this.size); } @Override public PooledObject<T> acquire(Consumer<T> activator) { int nextAcquireIndex = this.nextAcquireIndex; // ObjectPool closed? if (nextAcquireIndex == -1) { throw new IllegalStateException("ObjectPool already closed"); } // Maybe thread recently released an entry Entry<T> cachedEntry = threadCache.get(); if (cachedEntry != null) { LOGGER.trace("Cached entry found: %s", cachedEntry); // If unused, just return the acquired and cached entry if (cachedEntry.casState(ENTRY_FREE, ENTRY_USED)) { LOGGER.trace("Cached entry was free, acquired: %s", cachedEntry); return validateOrRecreateEntry(cachedEntry.index, cachedEntry, activator); } threadCache.remove(); } // Otherwise let's search a free entry int acquireIndex = nextAcquireIndex; do { Entry<T> entry = (Entry<T>) entryPool[acquireIndex++]; // ObjectPool closed? if (entry == null) { throw new IllegalStateException("ObjectPool already closed"); } if (acquireIndex >= size) { acquireIndex = 0; } if (entry.casState(ENTRY_FREE, ENTRY_USED)) { LOGGER.trace("Acquired entry from looping, index %s: %s", (acquireIndex - 1), entry); updateNextAcquireIndex(nextAcquireIndex, acquireIndex); // Validate object and recreate invalid entries return validateOrRecreateEntry(acquireIndex - 1, entry, activator); } } while (acquireIndex != nextAcquireIndex); // If pool is full create an intermediate object Entry<T> entry = new Entry<>(-1, handler.create(), ENTRY_UNPOOLED).activate(handler, activator); LOGGER.trace("Return intermediate entry: %s", entry); return entry; } @Override public void release(PooledObject<T> object, Consumer<T> passivator) { if (object == null || !(object instanceof Entry)) { throw new IllegalArgumentException("Illegal pooled object passed"); } Entry<T> entry = (Entry<T>) object; int state = entry.state; LOGGER.trace("Returning entry to pool: %s", entry); if (state == ENTRY_UNPOOLED) { // Intermediate entry, will be discarded by the GC LOGGER.trace("Entry is intermediate, let GC handle it: %s", entry); entry.passivate(handler, passivator).destroy(handler); return; } else if (state == ENTRY_FREE) { throw new ConcurrentModificationException("Illegal concurrent update on an object pool entry"); } entry.passivate(handler, passivator); if (!entry.casState(state, ENTRY_FREE)) { throw new ConcurrentModificationException("Illegal concurrent update on an object pool entry"); } LOGGER.trace("Entry returned to the pool, setting as cached entry: %s", entry); threadCache.set(entry); } @Override public void close() { nextAcquireIndex = -1; for (int index = 0; index < size; index++) { long indexOffset = offset(index); // Retrieve element and destroy it Entry<T> entry = (Entry<T>) UNSAFE.getObjectVolatile(entryPool, indexOffset); entry.destroy(handler); // Clear field UNSAFE.putObjectVolatile(entryPool, indexOffset, null); } } private Entry<T>[] createEntryPool(ObjectHandler<T> factory, int size) { Entry<T>[] entryPool = new Entry[size]; for (int i = 0; i < size; i++) { entryPool[i] = new Entry<>(i, factory.create()); } return entryPool; } private void updateNextAcquireIndex(int expected, int newIndex) { while (true) { // Try updating the index if (UNSAFE.compareAndSwapInt(this, OFFSET, expected, newIndex)) { return; } // If failed let's see if the currently set pointer is bigger than // our index, if not retry updating expected = this.nextAcquireIndex; if (expected >= newIndex) { return; } } } private Entry<T> validateOrRecreateEntry(int index, Entry<T> entry, Consumer<T> activator) { if (validator == null) { return entry.activate(handler, activator); } if (validator.isValid(entry.getObject())) { return entry.activate(handler, activator); } // If not valid destroy old element and create a new one entry.destroy(handler); Entry<T> newEntry = new Entry<>(index, handler.create(), ENTRY_USED); if (index != -1) { long indexOffset = offset(index); if (!UNSAFE.compareAndSwapObject(entryPool, indexOffset, entry, newEntry)) { throw new ConcurrentModificationException("Illegal concurrent update on an invalid object pool entry"); } } return newEntry.activate(handler, activator); } private long offset(long index) { return (index << ARRAY_SHIFT) + ARRAY_BASE; } static final class Entry<T> implements PooledObject<T> { private static final long OFFSET; static { try { Field field = Entry.class.getDeclaredField("state"); field.setAccessible(true); OFFSET = UNSAFE.objectFieldOffset(field); } catch (Exception e) { throw new RuntimeException(e); } } private final T value; private final int index; private volatile int state; public Entry(int index, T value) { this(index, value, ENTRY_FREE); } public Entry(int index, T value, int state) { this.index = index; this.value = value; this.state = state; } @Override public T getObject() { return value; } private Entry<T> activate(ObjectHandler<T> handler, Consumer<T> activator) { if (activator != null) { activator.accept(value); } handler.activateObject(value); return this; } private Entry<T> passivate(ObjectHandler<T> handler, Consumer<T> passivator) { if (passivator != null) { passivator.accept(value); } handler.passivateObject(value); return this; } private void destroy(ObjectHandler<T> handler) { handler.destroy(value); } private boolean casState(int expectedState, int newState) { return UNSAFE.compareAndSwapInt(this, OFFSET, expectedState, newState); } @Override public String toString() { return "Entry{" + "value=" + value + ", state=" + (state == 0 ? "FREE" : (state == 1 ? "USED" : "INTERMEDIATE")) + '}'; } } }