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

reactor.netty.http.client.Http2Pool Maven / Gradle / Ivy

There is a newer version: 1.2.1
Show newest version
/*
 * Copyright (c) 2021-2024 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.netty.http.client;

import java.time.Clock;
import java.time.Duration;
import java.util.Iterator;
import java.util.Objects;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.function.Function;

import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http2.Http2FrameCodec;
import io.netty.handler.codec.http2.Http2MultiplexHandler;
import io.netty.handler.ssl.ApplicationProtocolNames;
import io.netty.handler.ssl.SslHandler;
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.netty.Connection;
import reactor.netty.FutureMono;
import reactor.netty.NettyPipeline;
import reactor.netty.internal.shaded.reactor.pool.InstrumentedPool;
import reactor.netty.internal.shaded.reactor.pool.PoolAcquirePendingLimitException;
import reactor.netty.internal.shaded.reactor.pool.PoolAcquireTimeoutException;
import reactor.netty.internal.shaded.reactor.pool.PoolConfig;
import reactor.netty.internal.shaded.reactor.pool.PoolShutdownException;
import reactor.netty.internal.shaded.reactor.pool.PooledRef;
import reactor.netty.internal.shaded.reactor.pool.PooledRefMetadata;
import reactor.netty.resources.ConnectionProvider;
import reactor.util.Logger;
import reactor.util.Loggers;
import reactor.util.annotation.Nullable;
import reactor.util.context.Context;

import static reactor.netty.ReactorNetty.format;

/**
 * 

This class is intended to be used only as {@code HTTP/2} connection pool. It doesn't have generic purpose. *

* The connection is removed from the pool when: *

    *
  • The connection is closed.
  • *
  • GO_AWAY is received and there are no active streams.
  • *
  • The eviction predicate evaluates to true and there are no active streams.
  • *
  • When the client is in one of the two modes: 1) H2 and HTTP/1.1 or 2) H2C and HTTP/1.1, * and the negotiated protocol is HTTP/1.1.
  • *
*

* The connection is filtered out when: *

    *
  • The connection's eviction predicate evaluates to true or GO_AWAY is received, and there are active streams. In this case, the * connection stays in the pool, but it is not used. Once there are no active streams, the connection is removed * from the pool.
  • *
  • The connection has reached its max active streams configuration. In this case, the connection stays * in the pool, but it is not used. Once the number of the active streams is below max active streams configuration, * the connection can be used again.
  • *
*

* This pool always invalidate the {@link PooledRef}, there is no release functionality. *

    *
  • {@link PoolMetrics#acquiredSize()}, {@link PoolMetrics#allocatedSize()} and {@link PoolMetrics#idleSize()} * always return the number of the cached connections.
  • *
  • {@link Http2Pool#activeStreams()} always return the active streams from all connections currently in the pool.
  • *
*

* If minimum connections is specified, the cached connections with active streams will be kept at that minimum * (can be the best effort). However, if the cached connections have reached max concurrent streams, * then new connections will be allocated up to the maximum connections limit. *

* Configurations that are not applicable *

    *
  • {@link PoolConfig#destroyHandler()} - the destroy handler cannot be used as the destruction is more complex.
  • *
  • {@link PoolConfig#metricsRecorder()} - no pool instrumentation.
  • *
  • {@link PoolConfig#releaseHandler()} - release functionality works as invalidate.
  • *
  • {@link PoolConfig#reuseIdleResourcesInLruOrder()} - FIFO is used when checking the connections.
  • *
  • FIFO is used when obtaining the pending borrowers
  • *
  • Warm up functionality is not supported
  • *
*

This class is based on * https://github.com/reactor/reactor-pool/blob/v0.2.7/src/main/java/reactor/pool/SimpleDequePool.java * * @author Violeta Georgieva */ final class Http2Pool implements InstrumentedPool, InstrumentedPool.PoolMetrics { static final Logger log = Loggers.getLogger(Http2Pool.class); volatile int acquired; static final AtomicIntegerFieldUpdater ACQUIRED = AtomicIntegerFieldUpdater.newUpdater(Http2Pool.class, "acquired"); volatile ConcurrentLinkedQueue connections; @SuppressWarnings("rawtypes") static final AtomicReferenceFieldUpdater CONNECTIONS = AtomicReferenceFieldUpdater.newUpdater(Http2Pool.class, ConcurrentLinkedQueue.class, "connections"); volatile int idleSize; private static final AtomicIntegerFieldUpdater IDLE_SIZE = AtomicIntegerFieldUpdater.newUpdater(Http2Pool.class, "idleSize"); /** * Pending borrowers queue. Never invoke directly the poll/add/remove methods and instead of that, * use addPending/pollPending/removePending methods which take care of maintaining the pending queue size. * @see #removePending(ConcurrentLinkedDeque, Borrower) * @see #addPending(ConcurrentLinkedDeque, Borrower, boolean) * @see #pollPending(ConcurrentLinkedDeque, boolean) * @see #PENDING_SIZE * @see #pendingSize */ volatile ConcurrentLinkedDeque pending; @SuppressWarnings("rawtypes") static final AtomicReferenceFieldUpdater PENDING = AtomicReferenceFieldUpdater.newUpdater(Http2Pool.class, ConcurrentLinkedDeque.class, "pending"); volatile int pendingSize; private static final AtomicIntegerFieldUpdater PENDING_SIZE = AtomicIntegerFieldUpdater.newUpdater(Http2Pool.class, "pendingSize"); @SuppressWarnings("rawtypes") static final ConcurrentLinkedDeque TERMINATED = new ConcurrentLinkedDeque(); volatile long totalMaxConcurrentStreams; static final AtomicLongFieldUpdater TOTAL_MAX_CONCURRENT_STREAMS = AtomicLongFieldUpdater.newUpdater(Http2Pool.class, "totalMaxConcurrentStreams"); volatile int wip; static final AtomicIntegerFieldUpdater WIP = AtomicIntegerFieldUpdater.newUpdater(Http2Pool.class, "wip"); final Clock clock; final Long maxConcurrentStreams; final int minConnections; final PoolConfig poolConfig; long lastInteractionTimestamp; Disposable evictionTask; Http2Pool(PoolConfig poolConfig, @Nullable ConnectionProvider.AllocationStrategy allocationStrategy) { this.clock = poolConfig.clock(); this.connections = new ConcurrentLinkedQueue<>(); this.lastInteractionTimestamp = clock.millis(); this.maxConcurrentStreams = allocationStrategy instanceof Http2AllocationStrategy ? ((Http2AllocationStrategy) allocationStrategy).maxConcurrentStreams() : -1; this.minConnections = allocationStrategy == null ? 0 : allocationStrategy.permitMinimum(); this.pending = new ConcurrentLinkedDeque<>(); this.poolConfig = poolConfig; recordInteractionTimestamp(); scheduleEviction(); } @Override public Mono> acquire() { return new BorrowerMono(this, Duration.ZERO); } @Override public Mono> acquire(Duration timeout) { return new BorrowerMono(this, timeout); } @Override public int acquiredSize() { return allocatedSize() - idleSize(); } @Override public int allocatedSize() { return poolConfig.allocationStrategy().permitGranted(); } @Override public PoolConfig config() { return poolConfig; } @Override public Mono disposeLater() { return Mono.defer(() -> { recordInteractionTimestamp(); @SuppressWarnings("unchecked") ConcurrentLinkedDeque q = PENDING.getAndSet(this, TERMINATED); if (q != TERMINATED) { evictionTask.dispose(); Borrower p; while ((p = pollPending(q, true)) != null) { p.fail(new PoolShutdownException()); } @SuppressWarnings("unchecked") ConcurrentLinkedQueue slots = CONNECTIONS.getAndSet(this, null); if (slots != null) { Mono closeMonos = Mono.empty(); while (!slots.isEmpty()) { Slot slot = pollSlot(slots); if (slot != null) { slot.invalidate(); closeMonos = closeMonos.and(DEFAULT_DESTROY_HANDLER.apply(slot.connection)); } } return closeMonos; } } return Mono.empty(); }); } @Override public int getMaxAllocatedSize() { return Integer.MAX_VALUE; } @Override public int getMaxPendingAcquireSize() { return poolConfig.maxPending() < 0 ? Integer.MAX_VALUE : poolConfig.maxPending(); } @Override public int idleSize() { return idleSize; } @Override public boolean isDisposed() { return PENDING.get(this) == TERMINATED || CONNECTIONS.get(this) == null; } @Override public boolean isInactiveForMoreThan(Duration duration) { return pendingAcquireSize() == 0 && allocatedSize() == 0 && secondsSinceLastInteraction() >= duration.getSeconds(); } @Override public PoolMetrics metrics() { return this; } @Override public int pendingAcquireSize() { return pendingSize; } @Override public long secondsSinceLastInteraction() { long sinceMs = clock.millis() - lastInteractionTimestamp; return sinceMs / 1000; } @Override public Mono warmup() { return Mono.just(0); } int activeStreams() { return acquired; } void cancelAcquire(Borrower borrower) { if (!isDisposed()) { ConcurrentLinkedDeque q = pending; removePending(q, borrower); } } @SuppressWarnings("FutureReturnValueIgnored") Mono destroyPoolable(Http2PooledRef ref) { assert ref.slot.connection.channel().eventLoop().inEventLoop(); Mono mono = Mono.empty(); try { // By default, check the connection for removal on acquire and invalidate (only if there are no active streams) if (ref.slot.decrementConcurrencyAndGet() == 0) { // not HTTP/2 request if (ref.slot.http2FrameCodecCtx() == null) { ref.slot.invalidate(); removeSlot(ref.slot); } // If there is eviction in background, the background process will remove this connection else if (poolConfig.evictInBackgroundInterval().isZero()) { // not active if (!ref.poolable().channel().isActive()) { ref.slot.invalidate(); removeSlot(ref.slot); } // received GO_AWAY if (ref.slot.goAwayReceived()) { ref.slot.invalidate(); removeSlot(ref.slot); } // eviction predicate evaluates to true else if (testEvictionPredicate(ref.slot)) { //"FutureReturnValueIgnored" this is deliberate ref.slot.connection.channel().close(); ref.slot.invalidate(); removeSlot(ref.slot); } } } } catch (Throwable destroyFunctionError) { mono = Mono.error(destroyFunctionError); } return mono; } void doAcquire(Borrower borrower) { if (isDisposed()) { borrower.fail(new PoolShutdownException()); return; } pendingOffer(borrower); drain(); } void drain() { if (WIP.getAndIncrement(this) == 0) { drainLoop(); } } void drainLoop() { recordInteractionTimestamp(); int maxPending = poolConfig.maxPending(); for (;;) { @SuppressWarnings("unchecked") ConcurrentLinkedQueue resources = CONNECTIONS.get(this); @SuppressWarnings("unchecked") ConcurrentLinkedDeque borrowers = PENDING.get(this); if (resources == null || borrowers == TERMINATED) { return; } int borrowersCount = pendingSize; if (borrowersCount != 0) { // find a connection that can be used for opening a new stream // when cached connections are below minimum connections, then allocate a new connection boolean belowMinConnections = minConnections > 0 && poolConfig.allocationStrategy().permitGranted() < minConnections; Slot slot = belowMinConnections ? null : findConnection(resources); if (slot != null) { Borrower borrower = pollPending(borrowers, true); if (borrower == null) { offerSlot(resources, slot); continue; } if (isDisposed()) { borrower.fail(new PoolShutdownException()); return; } borrower.stopPendingCountdown(true); if (log.isDebugEnabled()) { log.debug(format(slot.connection.channel(), "Channel activated")); } ACQUIRED.incrementAndGet(this); slot.connection.channel().eventLoop().execute(() -> { borrower.deliver(new Http2PooledRef(slot)); drain(); }); } else { int resourcesCount = idleSize; if (minConnections > 0 && poolConfig.allocationStrategy().permitGranted() >= minConnections && resourcesCount == 0) { // connections allocations were triggered } else { int permits = poolConfig.allocationStrategy().getPermits(1); if (permits <= 0) { if (maxPending >= 0) { borrowersCount = pendingSize; int toCull = borrowersCount - maxPending; for (int i = 0; i < toCull; i++) { Borrower extraneous = pollPending(borrowers, true); if (extraneous != null) { pendingAcquireLimitReached(extraneous, maxPending); } } } } else { if (permits > 1) { // warmup is not supported poolConfig.allocationStrategy().returnPermits(permits - 1); } Borrower borrower = pollPending(borrowers, true); if (borrower == null) { continue; } if (isDisposed()) { borrower.fail(new PoolShutdownException()); return; } borrower.stopPendingCountdown(true); Mono allocator = poolConfig.allocator(); Mono primary = allocator.doOnEach(sig -> { if (sig.isOnNext()) { Connection newInstance = sig.get(); assert newInstance != null; Slot newSlot = new Slot(this, newInstance); if (log.isDebugEnabled()) { log.debug(format(newInstance.channel(), "Channel activated")); } ACQUIRED.incrementAndGet(this); borrower.deliver(new Http2PooledRef(newSlot)); } else if (sig.isOnError()) { Throwable error = sig.getThrowable(); assert error != null; poolConfig.allocationStrategy().returnPermits(1); borrower.fail(error); } }) .contextWrite(borrower.currentContext()); primary.subscribe(alreadyPropagated -> {}, alreadyPropagatedOrLogged -> drain(), this::drain); } } } } if (WIP.decrementAndGet(this) == 0) { recordInteractionTimestamp(); break; } } } @SuppressWarnings("FutureReturnValueIgnored") void evictInBackground() { @SuppressWarnings("unchecked") ConcurrentLinkedQueue resources = CONNECTIONS.get(this); if (resources == null) { //no need to schedule the task again, pool has been disposed return; } if (WIP.getAndIncrement(this) == 0) { if (pendingSize == 0) { Iterator slots = resources.iterator(); while (slots.hasNext()) { Slot slot = slots.next(); if (slot.concurrency() == 0) { if (!slot.connection.channel().isActive()) { if (log.isDebugEnabled()) { log.debug(format(slot.connection.channel(), "Channel is closed, remove from pool")); } recordInteractionTimestamp(); slots.remove(); IDLE_SIZE.decrementAndGet(this); slot.invalidate(); continue; } if (slot.goAwayReceived()) { if (log.isDebugEnabled()) { log.debug(format(slot.connection.channel(), "Channel received GO_AWAY, remove from pool")); } recordInteractionTimestamp(); slots.remove(); IDLE_SIZE.decrementAndGet(this); slot.invalidate(); continue; } if (testEvictionPredicate(slot)) { if (log.isDebugEnabled()) { log.debug(format(slot.connection.channel(), "Eviction predicate was true, remove from pool")); } //"FutureReturnValueIgnored" this is deliberate slot.connection.channel().close(); recordInteractionTimestamp(); slots.remove(); IDLE_SIZE.decrementAndGet(this); slot.invalidate(); } } } } //at the end if there are racing drain calls, go into the drainLoop if (WIP.decrementAndGet(this) > 0) { drainLoop(); } } //schedule the next iteration scheduleEviction(); } @Nullable @SuppressWarnings("FutureReturnValueIgnored") Slot findConnection(ConcurrentLinkedQueue resources) { int resourcesCount = idleSize; while (resourcesCount > 0) { // There are connections in the queue resourcesCount--; // get the connection Slot slot = pollSlot(resources); if (slot == null) { continue; } // check the connection is active if (!slot.connection.channel().isActive()) { if (slot.concurrency() > 0) { if (log.isDebugEnabled()) { log.debug(format(slot.connection.channel(), "Channel is closed, {} active streams"), slot.concurrency()); } offerSlot(resources, slot); } else { if (log.isDebugEnabled()) { log.debug(format(slot.connection.channel(), "Channel is closed, remove from pool")); } slot.invalidate(); } continue; } // check the connection received GO_AWAY if (slot.goAwayReceived()) { if (slot.concurrency() > 0) { if (log.isDebugEnabled()) { log.debug(format(slot.connection.channel(), "Channel received GO_AWAY, {} active streams"), slot.concurrency()); } offerSlot(resources, slot); } else { if (log.isDebugEnabled()) { log.debug(format(slot.connection.channel(), "Channel received GO_AWAY, remove from pool")); } slot.invalidate(); } continue; } // check whether the eviction predicate for the connection evaluates to true if (testEvictionPredicate(slot)) { if (slot.concurrency() > 0) { if (log.isDebugEnabled()) { log.debug(format(slot.connection.channel(), "Eviction predicate was true, {} active streams"), slot.concurrency()); } offerSlot(resources, slot); } else { if (log.isDebugEnabled()) { log.debug(format(slot.connection.channel(), "Eviction predicate was true, remove from pool")); } //"FutureReturnValueIgnored" this is deliberate slot.connection.channel().close(); slot.invalidate(); } continue; } // check that the connection's max active streams has not been reached if (!slot.canOpenStream()) { offerSlot(resources, slot); if (log.isDebugEnabled()) { log.debug(format(slot.connection.channel(), "Max active streams is reached")); } continue; } return slot; } return null; } boolean testEvictionPredicate(Slot slot) { return poolConfig.evictionPredicate().test(slot.connection, slot); } void pendingAcquireLimitReached(Borrower borrower, int maxPending) { if (maxPending == 0) { borrower.fail(new PoolAcquirePendingLimitException(0, "No pending allowed and pool has reached allocation limit")); } else { borrower.fail(new PoolAcquirePendingLimitException(maxPending)); } } /** * Adds a new {@link Borrower} to the queue. * * @param borrower a new {@link Borrower} to add to the queue and later either serve or consider pending */ void pendingOffer(Borrower borrower) { int maxPending = poolConfig.maxPending(); ConcurrentLinkedDeque pendingQueue = pending; if (pendingQueue == TERMINATED) { return; } int postOffer = addPending(pendingQueue, borrower, false); if (WIP.getAndIncrement(this) == 0) { ConcurrentLinkedQueue ir = connections; if (maxPending >= 0 && postOffer > maxPending && ir.isEmpty() && poolConfig.allocationStrategy().estimatePermitCount() == 0) { Borrower toCull = pollPending(pendingQueue, false); if (toCull != null) { pendingAcquireLimitReached(toCull, maxPending); } if (WIP.decrementAndGet(this) > 0) { drainLoop(); } return; } drainLoop(); } } void recordInteractionTimestamp() { this.lastInteractionTimestamp = clock.millis(); } @Nullable Borrower pollPending(ConcurrentLinkedDeque borrowers, boolean pollFirst) { Borrower borrower = pollFirst ? borrowers.pollFirst() : borrowers.pollLast(); if (borrower != null) { PENDING_SIZE.decrementAndGet(this); } return borrower; } void removePending(ConcurrentLinkedDeque borrowers, Borrower borrower) { if (borrowers.remove(borrower)) { PENDING_SIZE.decrementAndGet(this); } } int addPending(ConcurrentLinkedDeque borrowers, Borrower borrower, boolean first) { if (first) { borrowers.offerFirst(borrower); } else { borrowers.offerLast(borrower); } return PENDING_SIZE.incrementAndGet(this); } void offerSlot(@Nullable ConcurrentLinkedQueue slots, Slot slot) { if (slots != null && slots.offer(slot)) { IDLE_SIZE.incrementAndGet(this); } } @Nullable Slot pollSlot(@Nullable ConcurrentLinkedQueue slots) { if (slots == null) { return null; } Slot slot = slots.poll(); if (slot != null) { IDLE_SIZE.decrementAndGet(this); } return slot; } void removeSlot(Slot slot) { @SuppressWarnings("unchecked") ConcurrentLinkedQueue q = CONNECTIONS.get(slot.pool); if (q != null && q.remove(slot)) { IDLE_SIZE.decrementAndGet(this); } } void scheduleEviction() { if (!poolConfig.evictInBackgroundInterval().isZero()) { long nanosEvictionInterval = poolConfig.evictInBackgroundInterval().toNanos(); this.evictionTask = poolConfig.evictInBackgroundScheduler() .schedule(this::evictInBackground, nanosEvictionInterval, TimeUnit.NANOSECONDS); } else { this.evictionTask = Disposables.disposed(); } } static final Function> DEFAULT_DESTROY_HANDLER = connection -> { if (!connection.channel().isActive()) { return Mono.empty(); } return FutureMono.from(connection.channel().close()); }; static final class Borrower extends AtomicBoolean implements Scannable, Subscription, Runnable { static final Disposable TIMEOUT_DISPOSED = Disposables.disposed(); final Duration acquireTimeout; final CoreSubscriber actual; final Http2Pool pool; long pendingAcquireStart; Disposable timeoutTask; Borrower(CoreSubscriber actual, Http2Pool pool, Duration acquireTimeout) { this.acquireTimeout = acquireTimeout; this.actual = actual; this.pool = pool; this.timeoutTask = TIMEOUT_DISPOSED; } @Override public void cancel() { stopPendingCountdown(true); // this is not failure, the subscription was canceled if (compareAndSet(false, true)) { pool.cancelAcquire(this); } } Context currentContext() { return actual.currentContext(); } @Override public void request(long n) { if (Operators.validate(n)) { long estimateStreamsCount = pool.totalMaxConcurrentStreams - pool.acquired; int permits = pool.poolConfig.allocationStrategy().estimatePermitCount(); int pending = pool.pendingSize; if (permits + estimateStreamsCount <= pending) { pendingAcquireStart = pool.clock.millis(); if (!acquireTimeout.isZero()) { timeoutTask = pool.poolConfig.pendingAcquireTimer().apply(this, acquireTimeout); } } pool.doAcquire(this); } } @Override public void run() { if (compareAndSet(false, true)) { // this is failure, a timeout was observed stopPendingCountdown(false); pool.cancelAcquire(Http2Pool.Borrower.this); actual.onError(new PoolAcquireTimeoutException(acquireTimeout)); } } @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; } @Override public String toString() { return get() ? "Borrower(cancelled)" : "Borrower"; } void deliver(Http2PooledRef poolSlot) { assert poolSlot.slot.connection.channel().eventLoop().inEventLoop(); poolSlot.slot.incrementConcurrencyAndGet(); poolSlot.slot.deactivate(); if (get()) { //CANCELLED or timeout reached poolSlot.invalidate().subscribe(aVoid -> {}, e -> Operators.onErrorDropped(e, Context.empty())); } else { actual.onNext(poolSlot); actual.onComplete(); } } void fail(Throwable error) { stopPendingCountdown(false); if (!get()) { actual.onError(error); } } void stopPendingCountdown(boolean success) { if (pendingAcquireStart > 0) { if (success) { pool.poolConfig.metricsRecorder().recordPendingSuccessAndLatency(pool.clock.millis() - pendingAcquireStart); } else { pool.poolConfig.metricsRecorder().recordPendingFailureAndLatency(pool.clock.millis() - pendingAcquireStart); } pendingAcquireStart = 0; } timeoutTask.dispose(); } } static final class BorrowerMono extends Mono> { final Duration acquireTimeout; final Http2Pool parent; BorrowerMono(Http2Pool pool, Duration acquireTimeout) { this.acquireTimeout = acquireTimeout; this.parent = pool; } @Override public void subscribe(CoreSubscriber> actual) { Objects.requireNonNull(actual, "subscribing with null"); Borrower borrower = new Borrower(actual, parent, acquireTimeout); actual.onSubscribe(borrower); } } static final class Http2PooledRef extends AtomicBoolean implements PooledRef, PooledRefMetadata { final int acquireCount; final Slot slot; Http2PooledRef(Slot slot) { this.acquireCount = 0; this.slot = slot; } @Override public int acquireCount() { return 1; } @Override public long allocationTimestamp() { return 0; } @Override public long idleTime() { return 0; } @Override public Mono invalidate() { return Mono.defer(() -> { if (compareAndSet(false, true)) { ACQUIRED.decrementAndGet(slot.pool); return slot.pool.destroyPoolable(this).doFinally(st -> slot.pool.drain()); } else { return Mono.empty(); } }); } @Override public long lifeTime() { return 0; } @Override public PooledRefMetadata metadata() { return this; } @Override public Connection poolable() { return slot.connection; } @Override public Mono release() { return invalidate(); } @Override public long releaseTimestamp() { return 0; } @Override public String toString() { return "PooledRef{poolable=" + slot.connection + '}'; } } static final class Slot extends AtomicBoolean implements PooledRefMetadata { volatile int concurrency; static final AtomicIntegerFieldUpdater CONCURRENCY = AtomicIntegerFieldUpdater.newUpdater(Slot.class, "concurrency"); final Connection connection; final long creationTimestamp; final Http2Pool pool; final String applicationProtocol; long idleTimestamp; long maxConcurrentStreams; volatile ChannelHandlerContext http2FrameCodecCtx; volatile ChannelHandlerContext http2MultiplexHandlerCtx; volatile ChannelHandlerContext h2cUpgradeHandlerCtx; Slot(Http2Pool pool, Connection connection) { this.connection = connection; this.creationTimestamp = pool.clock.millis(); this.pool = pool; SslHandler handler = connection.channel().pipeline().get(SslHandler.class); if (handler != null) { this.applicationProtocol = handler.applicationProtocol() != null ? handler.applicationProtocol() : ApplicationProtocolNames.HTTP_1_1; } else { this.applicationProtocol = null; } ChannelHandlerContext frameCodec = http2FrameCodecCtx(); if (frameCodec != null && http2MultiplexHandlerCtx() != null) { this.maxConcurrentStreams = ((Http2FrameCodec) frameCodec.handler()).connection().local().maxActiveStreams(); this.maxConcurrentStreams = pool.maxConcurrentStreams == -1 ? maxConcurrentStreams : Math.min(pool.maxConcurrentStreams, maxConcurrentStreams); } TOTAL_MAX_CONCURRENT_STREAMS.addAndGet(this.pool, this.maxConcurrentStreams); } boolean canOpenStream() { ChannelHandlerContext frameCodec = http2FrameCodecCtx(); if (frameCodec != null && http2MultiplexHandlerCtx() != null) { long maxActiveStreams = ((Http2FrameCodec) frameCodec.handler()).connection().local().maxActiveStreams(); maxActiveStreams = pool.maxConcurrentStreams == -1 ? maxActiveStreams : Math.min(pool.maxConcurrentStreams, maxActiveStreams); long diff = maxActiveStreams - maxConcurrentStreams; if (diff != 0) { maxConcurrentStreams = maxActiveStreams; TOTAL_MAX_CONCURRENT_STREAMS.addAndGet(this.pool, diff); } int concurrency = this.concurrency; return concurrency < maxActiveStreams; } return false; } int concurrency() { return concurrency; } void deactivate() { if (log.isDebugEnabled()) { log.debug(format(connection.channel(), "Channel deactivated")); } @SuppressWarnings("unchecked") ConcurrentLinkedQueue slots = CONNECTIONS.get(pool); pool.offerSlot(slots, this); } int decrementConcurrencyAndGet() { int concurrency = CONCURRENCY.decrementAndGet(this); idleTimestamp = pool.clock.millis(); return concurrency; } boolean goAwayReceived() { ChannelHandlerContext frameCodec = http2FrameCodecCtx(); return frameCodec != null && ((Http2FrameCodec) frameCodec.handler()).connection().goAwayReceived(); } @Nullable ChannelHandlerContext http2FrameCodecCtx() { ChannelHandlerContext ctx = http2FrameCodecCtx; // ChannelHandlerContext.isRemoved is only meant to be called from within the EventLoop if (ctx != null && connection.channel().eventLoop().inEventLoop() && !ctx.isRemoved()) { return ctx; } ctx = connection.channel().pipeline().context(Http2FrameCodec.class); http2FrameCodecCtx = ctx; return ctx; } @Nullable ChannelHandlerContext http2MultiplexHandlerCtx() { ChannelHandlerContext ctx = http2MultiplexHandlerCtx; // ChannelHandlerContext.isRemoved is only meant to be called from within the EventLoop if (ctx != null && connection.channel().eventLoop().inEventLoop() && !ctx.isRemoved()) { return ctx; } ctx = connection.channel().pipeline().context(Http2MultiplexHandler.class); http2MultiplexHandlerCtx = ctx; return ctx; } @Nullable ChannelHandlerContext h2cUpgradeHandlerCtx() { ChannelHandlerContext ctx = h2cUpgradeHandlerCtx; // ChannelHandlerContext.isRemoved is only meant to be called from within the EventLoop if (ctx != null && connection.channel().eventLoop().inEventLoop() && !ctx.isRemoved()) { return ctx; } ctx = connection.channel().pipeline().context(NettyPipeline.H2CUpgradeHandler); h2cUpgradeHandlerCtx = ctx; return ctx; } void incrementConcurrencyAndGet() { CONCURRENCY.incrementAndGet(this); } void invalidate() { if (compareAndSet(false, true)) { if (log.isDebugEnabled()) { log.debug(format(connection.channel(), "Channel removed from pool")); } pool.poolConfig.allocationStrategy().returnPermits(1); TOTAL_MAX_CONCURRENT_STREAMS.addAndGet(this.pool, -maxConcurrentStreams); } } @Override public long idleTime() { if (concurrency() > 0) { return 0L; } long idleTime = idleTimestamp != 0 ? idleTimestamp : creationTimestamp; return pool.clock.millis() - idleTime; } @Override public int acquireCount() { return 1; } @Override public long lifeTime() { return pool.clock.millis() - creationTimestamp; } @Override public long releaseTimestamp() { return 0; } @Override public long allocationTimestamp() { return creationTimestamp; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy