/* * Copyright (c) 2011-2013 The original author or authors * ------------------------------------------------------ * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Apache License v2.0 which accompanies this distribution. * * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * * The Apache License v2.0 is available at * http://www.opensource.org/licenses/apache2.0.php * * You may elect to redistribute this code under either of these licenses. */ package io.vertx.core.http.impl; import io.netty.channel.Channel; import io.netty.channel.ChannelPipeline; import io.netty.handler.codec.http2.Http2Exception; import io.netty.handler.timeout.IdleStateHandler; import io.vertx.core.http.HttpVersion; import io.vertx.core.impl.ContextImpl; import io.vertx.core.spi.metrics.HttpClientMetrics; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Set; /** * @author <a href="mailto:julien@julienviet.com">Julien Viet</a> */ class Http2Pool implements ConnectionManager.Pool<Http2ClientConnection> { // Pools must locks on the queue object to keep a single lock private final ConnectionManager.ConnQueue queue; private final Set<Http2ClientConnection> allConnections = new HashSet<>(); private final Map<Channel, ? super Http2ClientConnection> connectionMap; final HttpClientImpl client; final HttpClientMetrics metrics; final int maxConcurrency; final boolean logEnabled; final int maxSockets; final int windowSize; public Http2Pool(ConnectionManager.ConnQueue queue, HttpClientImpl client, HttpClientMetrics metrics, Map<Channel, ? super Http2ClientConnection> connectionMap, int maxConcurrency, boolean logEnabled, int maxSize, int windowSize) { this.queue = queue; this.client = client; this.metrics = metrics; this.connectionMap = connectionMap; this.maxConcurrency = maxConcurrency; this.logEnabled = logEnabled; this.maxSockets = maxSize; this.windowSize = windowSize; } @Override public HttpVersion version() { return HttpVersion.HTTP_2; } @Override public boolean canCreateConnection(int connCount) { // We create at most one connection concurrently when all others when // all others are busy return connCount == allConnections.size() && connCount < maxSockets; } @Override public Http2ClientConnection pollConnection() { for (Http2ClientConnection conn : allConnections) { if (canReserveStream(conn)) { conn.streamCount++; return conn; } } return null; } void createConn(ContextImpl context, Channel ch, Waiter waiter, boolean upgrade) throws Http2Exception { ChannelPipeline p = ch.pipeline(); synchronized (queue) { VertxHttp2ConnectionHandler<Http2ClientConnection> handler = new VertxHttp2ConnectionHandlerBuilder<Http2ClientConnection>() .connectionMap(connectionMap) .server(false) .useCompression(client.getOptions().isTryUseCompression()) .initialSettings(client.getOptions().getInitialSettings()) .connectionFactory(connHandler -> { Http2ClientConnection conn = new Http2ClientConnection(Http2Pool.this, queue.metric, context, ch, connHandler, metrics); Object metric = metrics.connected(conn.remoteAddress(), conn.remoteName()); conn.metric(metric); return conn; }) .logEnabled(logEnabled) .build(); if (upgrade) { handler.onHttpClientUpgrade(); } Http2ClientConnection conn = handler.connection; metrics.endpointConnected(queue.metric, conn.metric()); p.addLast(handler); allConnections.add(conn); if (windowSize > 0) { conn.setWindowSize(windowSize); } conn.streamCount++; waiter.handleConnection(conn); // Should make same tests than in deliverRequest queue.deliverStream(conn, waiter); checkPending(conn); } } private boolean canReserveStream(Http2ClientConnection handler) { int maxConcurrentStreams = Math.min(handler.handler.connection().local().maxActiveStreams(), maxConcurrency); return handler.streamCount < maxConcurrentStreams; } void checkPending(Http2ClientConnection conn) { synchronized (queue) { Waiter waiter; while (canReserveStream(conn) && (waiter = queue.getNextWaiter()) != null) { conn.streamCount++; queue.deliverStream(conn, waiter); } } } void discard(Http2ClientConnection conn) { synchronized (queue) { if (allConnections.remove(conn)) { queue.connectionClosed(); } } metrics.endpointDisconnected(queue.metric, conn.metric()); } @Override public void recycle(Http2ClientConnection conn) { synchronized (queue) { conn.streamCount--; checkPending(conn); } } @Override public HttpClientStream createStream(Http2ClientConnection conn) throws Exception { return conn.createStream(); } @Override public void closeAllConnections() { List<Http2ClientConnection> toClose; synchronized (queue) { toClose = new ArrayList<>(allConnections); } // Close outside sync block to avoid deadlock toClose.forEach(Http2ConnectionBase::close); } }