/* * Copyright (c) 2018-Present Pivotal Software Inc, 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 * * https://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 reactor.pool; import java.io.Closeable; import java.io.IOException; import java.time.Clock; import java.time.Duration; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.function.Function; import org.reactivestreams.Publisher; import org.reactivestreams.Subscription; import reactor.core.CoreSubscriber; import reactor.core.Disposable; import reactor.core.Disposables; import reactor.core.Scannable; import reactor.core.publisher.Mono; import reactor.core.publisher.Operators; import reactor.core.scheduler.Schedulers; import reactor.util.Logger; import reactor.util.annotation.Nullable; import reactor.util.context.Context; import static reactor.pool.AbstractPool.AbstractPooledRef.STATE_INVALIDATED; /** * An abstract base version of a {@link Pool}, mutualizing small amounts of code and allowing to build common * related classes like {@link AbstractPooledRef} or {@link Borrower}. * * @author Simon Baslé */ abstract class AbstractPool<POOLABLE> implements InstrumentedPool<POOLABLE>, InstrumentedPool.PoolMetrics { //A pool should be rare enough that having instance loggers should be ok //This helps with testability of some methods that for now mainly log final Logger logger; final PoolConfig<POOLABLE> poolConfig; final PoolMetricsRecorder metricsRecorder; final Clock clock; volatile int pendingCount; @SuppressWarnings("rawtypes") static final AtomicIntegerFieldUpdater<AbstractPool> PENDING_COUNT = AtomicIntegerFieldUpdater.newUpdater(AbstractPool.class, "pendingCount"); AbstractPool(PoolConfig<POOLABLE> poolConfig, Logger logger) { this.poolConfig = poolConfig; this.logger = logger; this.metricsRecorder = poolConfig.metricsRecorder(); this.clock = poolConfig.clock(); } // == pool introspection methods == @Override public PoolMetrics metrics() { return this; } @Override public int pendingAcquireSize() { return PENDING_COUNT.get(this); } @Override public int allocatedSize() { return poolConfig.allocationStrategy().permitGranted(); } @Override abstract public int idleSize(); @Override public int acquiredSize() { return allocatedSize() - idleSize(); } @Override public int getMaxAllocatedSize() { return poolConfig.allocationStrategy().permitMaximum(); } @Override public int getMaxPendingAcquireSize() { return poolConfig.maxPending() < 0 ? Integer.MAX_VALUE : poolConfig.maxPending(); } // == common methods to interact with idle/pending queues == abstract boolean elementOffer(POOLABLE element); /** * Note to implementors: stop the {@link Borrower} countdown by calling * {@link Borrower#stopPendingCountdown()} as soon as it is known that a resource is * available or is in the process of being allocated. */ abstract void doAcquire(Borrower<POOLABLE> borrower); abstract void cancelAcquire(Borrower<POOLABLE> borrower); private void defaultDestroy(@Nullable POOLABLE poolable) { if (poolable instanceof Disposable) { ((Disposable) poolable).dispose(); } else if (poolable instanceof Closeable) { try { ((Closeable) poolable).close(); } catch (IOException e) { logger.trace("Failure while discarding a released Poolable that is Closeable, could not close", e); } } //TODO anything else to throw away the Poolable? } /** * Apply the configured destroyHandler to get the destroy {@link Mono} AND return a permit to the {@link AllocationStrategy}, * which assumes that the {@link Mono} will always be subscribed immediately. * <p> * Calls to this method MUST be guarded by {@link AbstractPooledRef#markInvalidate()}. * * @param ref the {@link PooledRef} that is not part of the live set * @return the destroy {@link Mono}, which MUST be subscribed immediately */ Mono<Void> destroyPoolable(AbstractPooledRef<POOLABLE> ref) { if (ref.state != STATE_INVALIDATED) { throw new IllegalStateException("destroying non invalidated ref " + ref); } POOLABLE poolable = ref.poolable(); poolConfig.allocationStrategy().returnPermits(1); long start = clock.millis(); metricsRecorder.recordLifetimeDuration(ref.lifeTime()); Function<POOLABLE, ? extends Publisher<Void>> factory = poolConfig.destroyHandler(); if (factory == PoolBuilder.NOOP_HANDLER) { return Mono.fromRunnable(() -> { defaultDestroy(poolable); metricsRecorder.recordDestroyLatency(clock.millis() - start); }); } else { return Mono.from(factory.apply(poolable)) .doFinally(fin -> metricsRecorder.recordDestroyLatency(clock.millis() - start)); } } /** * An abstract base for most common statistics operator of {@link PooledRef}. * * @author Simon Baslé */ abstract static class AbstractPooledRef<T> implements PooledRef<T>, PooledRefMetadata { final long creationTimestamp; final PoolMetricsRecorder metricsRecorder; final Clock clock; final T poolable; final int acquireCount; long timeSinceRelease; volatile int state; @SuppressWarnings("rawtypes") static final AtomicIntegerFieldUpdater<AbstractPooledRef> STATE = AtomicIntegerFieldUpdater.newUpdater(AbstractPooledRef.class, "state"); /** * Use this constructor the first time a resource is created and wrapped in a {@link PooledRef}. * @param poolable the newly created poolable * @param metricsRecorder the recorder to use for metrics * @param clock the {@link Clock} to use for timestamps */ AbstractPooledRef(T poolable, PoolMetricsRecorder metricsRecorder, Clock clock) { this.poolable = poolable; this.metricsRecorder = metricsRecorder; this.clock = clock; this.creationTimestamp = clock.millis(); this.acquireCount = 0; this.timeSinceRelease = -2L; this.state = STATE_IDLE; } /** * Use this constructor when a resource is passed to another borrower. */ AbstractPooledRef(AbstractPooledRef<T> oldRef) { this.poolable = oldRef.poolable; this.metricsRecorder = oldRef.metricsRecorder; this.clock = oldRef.clock; this.creationTimestamp = oldRef.creationTimestamp; this.acquireCount = oldRef.acquireCount(); //important to use method since the count variable is final this.timeSinceRelease = oldRef.timeSinceRelease; //important to carry over the markReleased for metrics //we're dealing with a new slot that was created when the previous one was released this.state = oldRef.state == STATE_INVALIDATED ? STATE_INVALIDATED : STATE_IDLE; } @Override public T poolable() { return poolable; } @Override public PooledRefMetadata metadata() { return this; } void markAcquired() { if (STATE.compareAndSet(this, STATE_IDLE, STATE_ACQUIRED)) { long tsr = timeSinceRelease; if (tsr > 0) { metricsRecorder.recordIdleTime(clock.millis() - tsr); } else { metricsRecorder.recordIdleTime(clock.millis() - creationTimestamp); } } } boolean markReleased() { for(;;) { int s = state; if (s == STATE_RELEASED || s == STATE_INVALIDATED) { return false; } else if (STATE.compareAndSet(this, s, STATE_RELEASED)) { this.timeSinceRelease = clock.millis(); return true; } } } boolean markInvalidate() { for(;;) { int s = state; if (s == STATE_INVALIDATED) { return false; } else if (STATE.compareAndSet(this, s, STATE_INVALIDATED)) { return true; } } } @Override public int acquireCount() { if (STATE.get(this) == STATE_IDLE) { return this.acquireCount; } return this.acquireCount + 1; } @Override public long lifeTime() { return clock.millis() - creationTimestamp; } @Override public long idleTime() { if (STATE.get(this) == STATE_ACQUIRED) { return 0L; } long tsr = this.timeSinceRelease; if (tsr < 0L) tsr = creationTimestamp; //any negative date other than -1 is considered "never yet released" return clock.millis() - tsr; } /** * Implementors MUST have the Mono call {@link #markReleased()} upon subscription. * <p> * {@inheritDoc} */ @Override public abstract Mono<Void> release(); /** * Implementors MUST have the Mono call {@link #markInvalidate()} upon subscription. * <p> * {@inheritDoc} */ @Override public abstract Mono<Void> invalidate(); @Override public String toString() { return "PooledRef{" + "poolable=" + poolable + ", lifeTime=" + lifeTime() + "ms" + ", idleTime=" + idleTime() + "ms" + ", acquireCount=" + acquireCount + '}'; } static final int STATE_IDLE = 0; static final int STATE_ACQUIRED = 1; static final int STATE_RELEASED = 2; //destroyed or in the process of being destroyed static final int STATE_INVALIDATED = 3; } /** * Common inner {@link Subscription} to be used to deliver poolable elements wrapped in {@link AbstractPooledRef} from * an {@link AbstractPool}. * * @author Simon Baslé */ static final class Borrower<POOLABLE> extends AtomicBoolean implements Scannable, Subscription, Runnable { static final Disposable TIMEOUT_DISPOSED = Disposables.disposed(); final CoreSubscriber<? super AbstractPooledRef<POOLABLE>> actual; final AbstractPool<POOLABLE> pool; final Duration acquireTimeout; Disposable timeoutTask; Borrower(CoreSubscriber<? super AbstractPooledRef<POOLABLE>> actual, AbstractPool<POOLABLE> pool, Duration acquireTimeout) { this.actual = actual; this.pool = pool; this.acquireTimeout = acquireTimeout; this.timeoutTask = TIMEOUT_DISPOSED; } @Override public void run() { if (Borrower.this.compareAndSet(false, true)) { pool.cancelAcquire(Borrower.this); actual.onError(new PoolAcquireTimeoutException(acquireTimeout)); } } @Override public void request(long n) { if (Operators.validate(n)) { //start the countdown boolean noIdle = pool.idleSize() == 0; boolean noPermits = pool.poolConfig.allocationStrategy().estimatePermitCount() == 0; if (!acquireTimeout.isZero() && noIdle && noPermits) { timeoutTask = Schedulers.parallel().schedule(this, acquireTimeout.toMillis(), TimeUnit.MILLISECONDS); } //doAcquire should interrupt the countdown if there is either an available //resource or the pool can allocate one pool.doAcquire(this); } } /** * Stop the countdown started when calling {@link AbstractPool#doAcquire(Borrower)}. */ void stopPendingCountdown() { timeoutTask.dispose(); } @Override public void cancel() { set(true); pool.cancelAcquire(this); stopPendingCountdown(); } @Override @Nullable @SuppressWarnings("rawtypes") public Object scanUnsafe(Attr key) { if (key == Attr.CANCELLED) return get(); if (key == Attr.REQUESTED_FROM_DOWNSTREAM) return 1; if (key == Attr.ACTUAL) return actual; return null; } void deliver(AbstractPooledRef<POOLABLE> poolSlot) { stopPendingCountdown(); if (get()) { //CANCELLED poolSlot.release().subscribe(aVoid -> {}, e -> Operators.onErrorDropped(e, Context.empty())); //actual mustn't receive onError } else { poolSlot.markAcquired(); actual.onNext(poolSlot); actual.onComplete(); } } void fail(Throwable error) { stopPendingCountdown(); if (!get()) { actual.onError(error); } } @Override public String toString() { return get() ? "Borrower(cancelled)" : "Borrower"; } } }