/*
* 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.vertx.core.http.HttpClientOptions;
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.HashSet;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
/**
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
*/
public class Http1xPool implements ConnectionManager.Pool<ClientConnection> {
// Pools must locks on the queue object to keep a single lock
private final ConnectionManager.ConnQueue queue;
private final HttpClientImpl client;
private final HttpClientMetrics metrics;
private final Map<Channel, HttpClientConnection> connectionMap;
private final boolean pipelining;
private final boolean keepAlive;
private final int pipeliningLimit;
private final boolean ssl;
private final HttpVersion version;
private final Set<ClientConnection> allConnections = new HashSet<>();
private final Queue<ClientConnection> availableConnections = new ArrayDeque<>();
private final int maxSockets;
public Http1xPool(HttpClientImpl client, HttpClientMetrics metrics, HttpClientOptions options, ConnectionManager.ConnQueue queue,
Map<Channel, HttpClientConnection> connectionMap, HttpVersion version, int maxSockets) {
this.queue = queue;
this.version = version;
this.client = client;
this.metrics = metrics;
this.pipelining = options.isPipelining();
this.keepAlive = options.isKeepAlive();
this.pipeliningLimit = options.getPipeliningLimit();
this.ssl = options.isSsl();
this.connectionMap = connectionMap;
this.maxSockets = maxSockets;
}
@Override
public HttpVersion version() {
// Correct this
return version;
}
@Override
public ClientConnection pollConnection() {
return availableConnections.poll();
}
@Override
public boolean canCreateConnection(int connCount) {
return connCount < maxSockets;
}
@Override
public HttpClientStream createStream(ClientConnection conn) {
return conn;
}
public void recycle(ClientConnection conn) {
synchronized (queue) {
Waiter waiter = queue.getNextWaiter();
if (waiter != null) {
queue.deliverStream(conn, waiter);
} else if (conn.getOutstandingRequestCount() == 0) {
// Return to set of available from here to not return it several times
availableConnections.add(conn);
}
}
}
void requestEnded(ClientConnection conn) {
ContextImpl context = conn.getContext();
context.runOnContext(v -> {
if (pipelining && conn.getOutstandingRequestCount() < pipeliningLimit) {
recycle(conn);
}
});
}
void responseEnded(ClientConnection conn, boolean close) {
if (!keepAlive || close) {
conn.close();
} else {
ContextImpl ctx = conn.getContext();
ctx.runOnContext(v -> {
if (conn.getCurrentRequest() == null) {
recycle(conn);
}
});
}
}
void createConn(HttpVersion version, ContextImpl context, int port, String host, Channel ch, Waiter waiter) {
ClientConnection conn = new ClientConnection(version, client, queue.metric, ch,
ssl, host, port, context, this, metrics);
Object metric = metrics.connected(conn.remoteAddress(), conn.remoteName());
conn.metric(metric);
metrics.endpointConnected(queue.metric, metric);
ClientHandler handler = ch.pipeline().get(ClientHandler.class);
handler.conn = conn;
synchronized (queue) {
allConnections.add(conn);
}
connectionMap.put(ch, conn);
waiter.handleConnection(conn);
queue.deliverStream(conn, waiter);
}
// Called if the connection is actually closed, OR the connection attempt failed - in the latter case
// conn will be null
public synchronized void connectionClosed(ClientConnection conn) {
synchronized (queue) {
allConnections.remove(conn);
availableConnections.remove(conn);
queue.connectionClosed();
}
metrics.endpointDisconnected(queue.metric, conn.metric());
}
public void closeAllConnections() {
Set<ClientConnection> copy;
synchronized (this) {
copy = new HashSet<>(allConnections);
allConnections.clear();
}
// Close outside sync block to avoid deadlock
for (ClientConnection conn : copy) {
try {
conn.close();
} catch (Throwable t) {
ConnectionManager.log.error("Failed to close connection", t);
}
}
}
void removeChannel(Channel channel) {
connectionMap.remove(channel);
}
}