"
+ final String lookupUrlStr = "pulsar://0.0.0.0:";
+ URI lookupUrl = new URI(lookupUrlStr + BROKER_PORT);
+ PulsarClientImpl pulsarClient = (PulsarClientImpl) PulsarClient.create(lookupUrl.toString(), clientConf);
+ boolean isLookupAndServiceUrlSame = false;
+ ConsumerImpl consumer = null;
+ try {
+ consumer = (ConsumerImpl) pulsarClient.subscribe(topicName, "mysub", new ConsumerConfiguration());
+ } catch (PulsarClientException connectionException) {
+ log.error("failed to lookup on {}. Now, connecting on {}", lookupUrlStr, pulsar.getBrokerServiceUrl());
+ // if routing failed on : 0.0.0.0 then create connection on the same brokerServiceUrl
+ pulsarClient = (PulsarClientImpl) PulsarClient.create(pulsar.getBrokerServiceUrl(), clientConf);
+ consumer = (ConsumerImpl) pulsarClient.subscribe(topicName, "mysub", new ConsumerConfiguration());
+ isLookupAndServiceUrlSame = true;
+ }
+ InetSocketAddress lookupConnectionAddress = new InetSocketAddress(lookupUrl.getHost(), lookupUrl.getPort());
+ ClientCnx lookupConnection = pulsarClient.getCnxPool().getConnection(lookupConnectionAddress).get();
+ ClientCnx consumerConnection = consumer.getClientCnx();
+ assertTrue(lookupConnection.channel().isActive());
+ if (!isLookupAndServiceUrlSame) {
+ assertNotEquals(lookupConnection, consumerConnection);
+ assertTrue(consumerConnection.channel().isActive());
+ }
+
+ // wait connection to be closed
+ Thread.sleep((connectionLifeTimeInSecods * 1000) + 1000);
+ // verify lookup-connection should be closed
+ assertFalse(lookupConnection.channel().isActive());
+ if (!isLookupAndServiceUrlSame) {
+ assertTrue(consumerConnection.channel().isActive());
+ }
+ }
+
}
diff --git a/pulsar-client/src/main/java/com/yahoo/pulsar/client/api/ClientConfiguration.java b/pulsar-client/src/main/java/com/yahoo/pulsar/client/api/ClientConfiguration.java
index 6a2ab81035b4a..fba6fb259f9f2 100644
--- a/pulsar-client/src/main/java/com/yahoo/pulsar/client/api/ClientConfiguration.java
+++ b/pulsar-client/src/main/java/com/yahoo/pulsar/client/api/ClientConfiguration.java
@@ -42,6 +42,7 @@ public class ClientConfiguration implements Serializable {
private int numIoThreads = 1;
private int numListenerThreads = 1;
private int connectionsPerBroker = 1;
+ private long lookupConnectionLifetimeInSecond = 600;
private boolean useTcpNoDelay = true;
@@ -219,6 +220,30 @@ public void setConnectionsPerBroker(int connectionsPerBroker) {
this.connectionsPerBroker = connectionsPerBroker;
}
+ /**
+ *
+ * @return lifetime of the persistent-connection used for the binary-proto lookup
+ */
+ public long getLookupConnectionLifetimeInSecond() {
+ return lookupConnectionLifetimeInSecond;
+ }
+
+ /**
+ * Sets lifetime of the persistent-connection (with broker) used for the binary-proto lookup (default: 10
+ * mins)
+ *
+ * Setting time to -1 will not force connection to be closed after default-lifetime duration. Client creates a
+ * separate dedicated connection with broker for a given service-lookup url, if this url is same as broker's
+ * brokerServiceUrl then client shares the same created lookup-connection for producer/consumer connection also. So,
+ * in that case {@link #setLookupConnectionLifetimeInSecond(long)} should be configured -1 to avoid
+ * producer/consumer disruption due to disconnection/reconnection.
+ *
+ * @param lookupConnectionLifetimeInSecond
+ */
+ public void setLookupConnectionLifetimeInSecond(long lookupConnectionLifetimeInSecond) {
+ this.lookupConnectionLifetimeInSecond = lookupConnectionLifetimeInSecond;
+ }
+
/**
* @return whether TCP no-delay should be set on the connections
*/
@@ -322,8 +347,8 @@ public int getConcurrentLookupRequest() {
/**
* Number of concurrent lookup-requests allowed on each broker-connection to prevent overload on broker.
- * (default: 5000) It should be configured with higher value only in case of it requires to produce/subscribe on
- * thousands of topic using created {@link PulsarClient}
+ * (default: 5000) It should be configured with higher value only in case of it requires to produce/subscribe
+ * on thousands of topic using created {@link PulsarClient}
*
* @param concurrentLookupRequest
*/
diff --git a/pulsar-client/src/main/java/com/yahoo/pulsar/client/impl/BinaryProtoLookupService.java b/pulsar-client/src/main/java/com/yahoo/pulsar/client/impl/BinaryProtoLookupService.java
index 98e905ad2532c..5f16464eeb648 100644
--- a/pulsar-client/src/main/java/com/yahoo/pulsar/client/impl/BinaryProtoLookupService.java
+++ b/pulsar-client/src/main/java/com/yahoo/pulsar/client/impl/BinaryProtoLookupService.java
@@ -36,6 +36,7 @@ class BinaryProtoLookupService implements LookupService {
private final PulsarClientImpl client;
protected final InetSocketAddress serviceAddress;
private final boolean useTls;
+ private final long lookupConnectionLifetimeInSecond;
public BinaryProtoLookupService(PulsarClientImpl client, String serviceUrl, boolean useTls)
throws PulsarClientException {
@@ -45,6 +46,7 @@ public BinaryProtoLookupService(PulsarClientImpl client, String serviceUrl, bool
try {
uri = new URI(serviceUrl);
this.serviceAddress = new InetSocketAddress(uri.getHost(), uri.getPort());
+ this.lookupConnectionLifetimeInSecond = client.getConfiguration().getLookupConnectionLifetimeInSecond();
} catch (Exception e) {
log.error("Invalid service-url {} provided {}", serviceUrl, e.getMessage(), e);
throw new PulsarClientException.InvalidServiceURL(e);
@@ -74,7 +76,7 @@ private CompletableFuture findBroker(InetSocketAddress socket
DestinationName destination) {
CompletableFuture addressFuture = new CompletableFuture();
- client.getCnxPool().getConnection(socketAddress).thenAccept(clientCnx -> {
+ client.getCnxPool().getConnection(socketAddress, lookupConnectionLifetimeInSecond).thenAccept(clientCnx -> {
long requestId = client.newRequestId();
ByteBuf request = Commands.newLookup(destination.toString(), authoritative, requestId);
clientCnx.newLookup(request, requestId).thenAccept(lookupDataResult -> {
@@ -132,7 +134,7 @@ private CompletableFuture getPartitionedTopicMetadata(
CompletableFuture partitionFuture = new CompletableFuture();
- client.getCnxPool().getConnection(socketAddress).thenAccept(clientCnx -> {
+ client.getCnxPool().getConnection(socketAddress, lookupConnectionLifetimeInSecond).thenAccept(clientCnx -> {
long requestId = client.newRequestId();
ByteBuf request = Commands.newPartitionMetadataRequest(destination.toString(), requestId);
clientCnx.newLookup(request, requestId).thenAccept(lookupDataResult -> {
diff --git a/pulsar-client/src/main/java/com/yahoo/pulsar/client/impl/ClientCnx.java b/pulsar-client/src/main/java/com/yahoo/pulsar/client/impl/ClientCnx.java
index 0953161c9b044..4f755a1209cb7 100644
--- a/pulsar-client/src/main/java/com/yahoo/pulsar/client/impl/ClientCnx.java
+++ b/pulsar-client/src/main/java/com/yahoo/pulsar/client/impl/ClientCnx.java
@@ -61,6 +61,8 @@ public class ClientCnx extends PulsarHandler {
private final CompletableFuture connectionFuture = new CompletableFuture();
private final Semaphore pendingLookupRequestSemaphore;
+ // Timestamp of when this connection used for lookup
+ protected volatile long lastLookupRequestTime;
enum State {
None, SentConnectFrame, Ready
@@ -258,6 +260,7 @@ protected void handlePartitionResponse(CommandPartitionedTopicMetadataResponse l
private boolean addPendingLookupRequests(long requestId, CompletableFuture future) {
if (pendingLookupRequestSemaphore.tryAcquire()) {
pendingLookupRequests.put(requestId, future);
+ lastLookupRequestTime = System.nanoTime();
return true;
}
return false;
@@ -338,6 +341,7 @@ CompletableFuture newLookup(ByteBuf request, long requestId) {
if (!writeFuture.isSuccess()) {
log.warn("{} Failed to send request {} to broker: {}", ctx.channel(), requestId,
writeFuture.cause().getMessage());
+ getAndRemovePendingLookupRequest(requestId);
future.completeExceptionally(writeFuture.cause());
}
});
@@ -377,6 +381,7 @@ CompletableFuture sendRequestWithId(ByteBuf cmd, long requestId) {
ctx.writeAndFlush(cmd).addListener(writeFuture -> {
if (!writeFuture.isSuccess()) {
log.warn("{} Failed to send request to broker: {}", ctx.channel(), writeFuture.cause().getMessage());
+ pendingRequests.remove(requestId);
future.completeExceptionally(writeFuture.cause());
}
});
diff --git a/pulsar-client/src/main/java/com/yahoo/pulsar/client/impl/ConnectionPool.java b/pulsar-client/src/main/java/com/yahoo/pulsar/client/impl/ConnectionPool.java
index 5741a0e27225e..7b70563924347 100644
--- a/pulsar-client/src/main/java/com/yahoo/pulsar/client/impl/ConnectionPool.java
+++ b/pulsar-client/src/main/java/com/yahoo/pulsar/client/impl/ConnectionPool.java
@@ -24,6 +24,9 @@
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.SystemUtils;
import org.slf4j.Logger;
@@ -55,6 +58,7 @@ public class ConnectionPool implements Closeable {
private final Bootstrap bootstrap;
private final EventLoopGroup eventLoopGroup;
private final int maxConnectionsPerHosts;
+ private final ScheduledExecutorService scheduler;
private static final int MaxMessageSize = 5 * 1024 * 1024;
public static final String TLS_HANDLER = "tls";
@@ -62,6 +66,7 @@ public class ConnectionPool implements Closeable {
public ConnectionPool(final PulsarClientImpl client, EventLoopGroup eventLoopGroup) {
this.eventLoopGroup = eventLoopGroup;
this.maxConnectionsPerHosts = client.getConfiguration().getConnectionsPerBroker();
+ this.scheduler = Executors.newScheduledThreadPool(1);
pool = new ConcurrentHashMap<>();
bootstrap = new Bootstrap();
@@ -111,22 +116,36 @@ public void initChannel(SocketChannel ch) throws Exception {
private static final Random random = new Random();
public CompletableFuture getConnection(final InetSocketAddress address) {
+ return getConnection(address, -1);
+ }
+
+ /**
+ *
+ * @param address
+ * remote client address
+ * @param connectionLifetimeInSecond
+ * connection lifetime (in second): if it's > 0 then created connection will be automatically closed
+ * after given connectionLifetimeInSecond
+ * @return
+ */
+ public CompletableFuture getConnection(final InetSocketAddress address,
+ long connectionLifetimeInSecond) {
if (maxConnectionsPerHosts == 0) {
// Disable pooling
- return createConnection(address, -1);
+ return createConnection(address, -1, connectionLifetimeInSecond);
}
final int randomKey = signSafeMod(random.nextInt(), maxConnectionsPerHosts);
return pool.computeIfAbsent(address, a -> new ConcurrentHashMap<>()) //
- .computeIfAbsent(randomKey, k -> createConnection(address, randomKey));
+ .computeIfAbsent(randomKey, k -> createConnection(address, randomKey, connectionLifetimeInSecond));
}
- private CompletableFuture createConnection(InetSocketAddress address, int connectionKey) {
+ private CompletableFuture createConnection(InetSocketAddress address, int connectionKey,
+ long connectionLifetimeInSecond) {
if (log.isDebugEnabled()) {
log.debug("Connection for {} not found in cache", address);
}
-
final CompletableFuture cnxFuture = new CompletableFuture();
// Trigger async connect to broker
@@ -170,14 +189,35 @@ private CompletableFuture createConnection(InetSocketAddress address,
cnx.ctx().close();
return null;
});
+
+ // close the connection after connection-lifetime completes
+ if (connectionLifetimeInSecond > 0) {
+ scheduleCloseConnection(cnx, connectionLifetimeInSecond);
+ }
});
return cnxFuture;
}
+ private void scheduleCloseConnection(ClientCnx cnx, long connectionLifetimeInSecond) {
+ scheduler.schedule(() -> {
+ if (cnx != null && cnx.channel().isActive()) {
+ if (System.nanoTime() - cnx.lastLookupRequestTime > TimeUnit.SECONDS
+ .toNanos(connectionLifetimeInSecond)) {
+ cnx.channel().disconnect();
+ } else {
+ // connection is active as received lookup request in last connectionLifetimeInSecond, schedule
+ // retry and check later again
+ scheduleCloseConnection(cnx, connectionLifetimeInSecond);
+ }
+ }
+ }, connectionLifetimeInSecond, TimeUnit.SECONDS);
+ }
+
@Override
public void close() throws IOException {
eventLoopGroup.shutdownGracefully();
+ scheduler.shutdown();
}
private void cleanupConnection(InetSocketAddress address, int connectionKey,