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

reactor.pool.AbstractPool Maven / Gradle / Ivy

/*
 * Copyright (c) 2018-Present VMware Inc. or its affiliates, 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 implements InstrumentedPool,
												 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 poolConfig;

	final PoolMetricsRecorder metricsRecorder;
	final Clock clock;

	long lastInteractionTimestamp;

	AbstractPool(PoolConfig poolConfig, Logger logger) {
		this.poolConfig = poolConfig;
		this.logger = logger;
		this.metricsRecorder = poolConfig.metricsRecorder();
		this.clock = poolConfig.clock();
		this.lastInteractionTimestamp = clock.millis();
	}

	// == pool introspection methods ==

	@Override
	public PoolMetrics metrics() {
		return 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();
	}

	void recordInteractionTimestamp() {
		this.lastInteractionTimestamp = clock.millis();
	}

	@Override
	public long secondsSinceLastInteraction() {
		long sinceMs = clock.millis() - this.lastInteractionTimestamp;
		return sinceMs / 1000;
	}

	@Override
	public boolean isInactiveForMoreThan(Duration duration) {
		//since acquiredSize() is computed from idleSize and allocatedSize, no need to involve it
		return idleSize() == 0 && pendingAcquireSize() == 0 && allocatedSize() == 0
				&& secondsSinceLastInteraction() >= duration.getSeconds();
	}

	// == common methods to interact with idle/pending queues ==

	abstract boolean elementOffer(POOLABLE element); //used in tests

	/**
	 * 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 borrower);
	abstract void cancelAcquire(Borrower 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.
	 * 

* 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 destroyPoolable(AbstractPooledRef 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> factory = poolConfig.destroyHandler(); if (factory == PoolBuilder.NOOP_HANDLER) { return Mono.fromRunnable(() -> { defaultDestroy(poolable); metricsRecorder.recordDestroyLatency(clock.millis() - start); }); } else { Mono userProvidedDestroy; try { userProvidedDestroy = Mono.from(factory.apply(poolable)); } catch (Throwable destroyFunctionError) { userProvidedDestroy = Mono.error(destroyFunctionError); } return userProvidedDestroy.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 implements PooledRef, PooledRefMetadata { final long creationTimestamp; final PoolMetricsRecorder metricsRecorder; final Clock clock; final T poolable; final int acquireCount; long releaseTimestamp; volatile int state; @SuppressWarnings("rawtypes") static final AtomicIntegerFieldUpdater 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.releaseTimestamp = -2L; this.state = STATE_IDLE; } /** * Use this constructor when a resource is passed to another borrower. */ AbstractPooledRef(AbstractPooledRef 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.releaseTimestamp = oldRef.releaseTimestamp; //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 rt = releaseTimestamp; if (rt > 0) { metricsRecorder.recordIdleTime(clock.millis() - rt); } 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.releaseTimestamp = clock.millis(); return true; } } } boolean markInvalidate() { for(;;) { int s = state; if (s == STATE_INVALIDATED) { //TODO should it account for STATE_RELEASED as well? 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 rt = this.releaseTimestamp; if (rt < 0L) rt = creationTimestamp; //any negative date other than -1 is considered "never yet released" return clock.millis() - rt; } @Override public long allocationTimestamp() { return creationTimestamp; } @Override public long releaseTimestamp() { if (STATE.get(this) == STATE_ACQUIRED) { return 0L; } long rt = this.releaseTimestamp; if (rt < 0L) rt = creationTimestamp; //any negative date other than -1 is considered "never yet released" return rt; } /** * Implementors MUST have the Mono call {@link #markReleased()} upon subscription. *

* {@inheritDoc} */ @Override public abstract Mono release(); /** * Implementors MUST have the Mono call {@link #markInvalidate()} upon subscription. *

* {@inheritDoc} */ @Override public abstract Mono 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 extends AtomicBoolean implements Scannable, Subscription, Runnable { static final Disposable TIMEOUT_DISPOSED = Disposables.disposed(); final CoreSubscriber> actual; final AbstractPool pool; final Duration acquireTimeout; Disposable timeoutTask; Borrower(CoreSubscriber> actual, AbstractPool pool, Duration acquireTimeout) { this.actual = actual; this.pool = pool; this.acquireTimeout = acquireTimeout; this.timeoutTask = TIMEOUT_DISPOSED; } Context currentContext() { return actual.currentContext(); } @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 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"; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy