org.apache.pulsar.client.impl.ClientCnx Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* http://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 org.apache.pulsar.client.impl;
import static org.apache.pulsar.shade.com.google.common.base.Preconditions.checkArgument;
import static org.apache.pulsar.shade.com.google.common.base.Preconditions.checkState;
import static org.apache.pulsar.client.impl.TransactionMetaStoreHandler.getExceptionByServerError;
import static org.apache.pulsar.common.util.Runnables.catchingAndLoggingThrowables;
import org.apache.pulsar.shade.com.google.common.annotations.VisibleForTesting;
import org.apache.pulsar.shade.com.google.common.collect.Queues;
import org.apache.pulsar.shade.io.netty.buffer.ByteBuf;
import org.apache.pulsar.shade.io.netty.channel.Channel;
import org.apache.pulsar.shade.io.netty.channel.ChannelHandlerContext;
import org.apache.pulsar.shade.io.netty.channel.EventLoopGroup;
import org.apache.pulsar.shade.io.netty.channel.unix.Errors.NativeIoException;
import org.apache.pulsar.shade.io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import org.apache.pulsar.shade.io.netty.handler.ssl.SslHandshakeCompletionEvent;
import org.apache.pulsar.shade.io.netty.util.concurrent.Promise;
import io.opentelemetry.api.common.Attributes;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.channels.ClosedChannelException;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicLong;
import lombok.AccessLevel;
import lombok.Getter;
import org.apache.pulsar.shade.org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.pulsar.shade.org.apache.commons.lang3.tuple.Pair;
import org.apache.pulsar.PulsarVersion;
import org.apache.pulsar.client.api.Authentication;
import org.apache.pulsar.client.api.AuthenticationDataProvider;
import org.apache.pulsar.client.api.PulsarClientException;
import org.apache.pulsar.client.api.PulsarClientException.ConnectException;
import org.apache.pulsar.client.api.PulsarClientException.TimeoutException;
import org.apache.pulsar.client.impl.BinaryProtoLookupService.LookupDataResult;
import org.apache.pulsar.client.impl.conf.ClientConfigurationData;
import org.apache.pulsar.client.impl.metrics.Counter;
import org.apache.pulsar.client.impl.metrics.InstrumentProvider;
import org.apache.pulsar.client.impl.metrics.Unit;
import org.apache.pulsar.client.impl.schema.SchemaInfoUtil;
import org.apache.pulsar.client.impl.transaction.TransactionBufferHandler;
import org.apache.pulsar.client.util.TimedCompletableFuture;
import org.apache.pulsar.common.api.AuthData;
import org.apache.pulsar.common.api.proto.BaseCommand;
import org.apache.pulsar.common.api.proto.CommandAckResponse;
import org.apache.pulsar.common.api.proto.CommandActiveConsumerChange;
import org.apache.pulsar.common.api.proto.CommandAddPartitionToTxnResponse;
import org.apache.pulsar.common.api.proto.CommandAddSubscriptionToTxnResponse;
import org.apache.pulsar.common.api.proto.CommandAuthChallenge;
import org.apache.pulsar.common.api.proto.CommandCloseConsumer;
import org.apache.pulsar.common.api.proto.CommandCloseProducer;
import org.apache.pulsar.common.api.proto.CommandConnected;
import org.apache.pulsar.common.api.proto.CommandEndTxnOnPartitionResponse;
import org.apache.pulsar.common.api.proto.CommandEndTxnOnSubscriptionResponse;
import org.apache.pulsar.common.api.proto.CommandEndTxnResponse;
import org.apache.pulsar.common.api.proto.CommandError;
import org.apache.pulsar.common.api.proto.CommandGetLastMessageIdResponse;
import org.apache.pulsar.common.api.proto.CommandGetOrCreateSchemaResponse;
import org.apache.pulsar.common.api.proto.CommandGetSchemaResponse;
import org.apache.pulsar.common.api.proto.CommandGetTopicsOfNamespaceResponse;
import org.apache.pulsar.common.api.proto.CommandLookupTopicResponse;
import org.apache.pulsar.common.api.proto.CommandMessage;
import org.apache.pulsar.common.api.proto.CommandNewTxnResponse;
import org.apache.pulsar.common.api.proto.CommandPartitionedTopicMetadataResponse;
import org.apache.pulsar.common.api.proto.CommandProducerSuccess;
import org.apache.pulsar.common.api.proto.CommandReachedEndOfTopic;
import org.apache.pulsar.common.api.proto.CommandSendError;
import org.apache.pulsar.common.api.proto.CommandSendReceipt;
import org.apache.pulsar.common.api.proto.CommandSuccess;
import org.apache.pulsar.common.api.proto.CommandTcClientConnectResponse;
import org.apache.pulsar.common.api.proto.CommandTopicMigrated;
import org.apache.pulsar.common.api.proto.CommandTopicMigrated.ResourceType;
import org.apache.pulsar.common.api.proto.CommandWatchTopicListSuccess;
import org.apache.pulsar.common.api.proto.CommandWatchTopicUpdate;
import org.apache.pulsar.common.api.proto.ServerError;
import org.apache.pulsar.common.lookup.GetTopicsResult;
import org.apache.pulsar.common.protocol.Commands;
import org.apache.pulsar.common.protocol.PulsarHandler;
import org.apache.pulsar.common.protocol.schema.SchemaVersion;
import org.apache.pulsar.common.schema.SchemaInfo;
import org.apache.pulsar.common.util.FutureUtil;
import org.apache.pulsar.common.util.collections.ConcurrentLongHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Channel handler for the Pulsar client.
*
* Please see {@link org.apache.pulsar.common.protocol.PulsarDecoder} javadoc for important details about handle* method
* parameter instance lifecycle.
*/
@SuppressWarnings("unchecked")
public class ClientCnx extends PulsarHandler {
protected final Authentication authentication;
protected State state;
private AtomicLong duplicatedResponseCounter = new AtomicLong(0);
@Getter
private final ConcurrentLongHashMap> pendingRequests =
ConcurrentLongHashMap.>newBuilder()
.expectedItems(16)
.concurrencyLevel(1)
.build();
// LookupRequests that waiting in client side.
private final Queue>>> waitingLookupRequests;
@VisibleForTesting
final ConcurrentLongHashMap> producers =
ConcurrentLongHashMap.>newBuilder()
.expectedItems(16)
.concurrencyLevel(1)
.build();
@VisibleForTesting
final ConcurrentLongHashMap> consumers =
ConcurrentLongHashMap.>newBuilder()
.expectedItems(16)
.concurrencyLevel(1)
.build();
private final ConcurrentLongHashMap transactionMetaStoreHandlers =
ConcurrentLongHashMap.newBuilder()
.expectedItems(16)
.concurrencyLevel(1)
.build();
@Getter(AccessLevel.PACKAGE)
private final ConcurrentLongHashMap topicListWatchers =
ConcurrentLongHashMap.newBuilder()
.expectedItems(16)
.concurrencyLevel(1)
.build();
private final CompletableFuture connectionFuture = new CompletableFuture();
private final ConcurrentLinkedQueue requestTimeoutQueue = new ConcurrentLinkedQueue<>();
@VisibleForTesting
@Getter(AccessLevel.PACKAGE)
private final Semaphore pendingLookupRequestSemaphore;
private final Semaphore maxLookupRequestSemaphore;
private final EventLoopGroup eventLoopGroup;
private static final AtomicIntegerFieldUpdater NUMBER_OF_REJECTED_REQUESTS_UPDATER =
AtomicIntegerFieldUpdater.newUpdater(ClientCnx.class, "numberOfRejectRequests");
@SuppressWarnings("unused")
private volatile int numberOfRejectRequests = 0;
@Getter
private int maxMessageSize = Commands.DEFAULT_MAX_MESSAGE_SIZE;
private final int maxNumberOfRejectedRequestPerConnection;
private final int rejectedRequestResetTimeSec = 60;
protected final int protocolVersion;
private final long operationTimeoutMs;
protected String proxyToTargetBrokerAddress = null;
// Remote hostName with which client is connected
protected String remoteHostName = null;
private ScheduledFuture> timeoutTask;
private SocketAddress localAddress;
private SocketAddress remoteAddress;
// Added for mutual authentication.
@Getter
protected AuthenticationDataProvider authenticationDataProvider;
private TransactionBufferHandler transactionBufferHandler;
private boolean supportsTopicWatchers;
@Getter
private boolean supportsGetPartitionedMetadataWithoutAutoCreation;
/** Idle stat. **/
@Getter
private final ClientCnxIdleState idleState;
@Getter
private long lastDisconnectedTimestamp;
private final String clientVersion;
protected enum State {
None, SentConnectFrame, Ready, Failed, Connecting
}
private final Counter connectionsOpenedCounter;
private final Counter connectionsClosedCounter;
private static class RequestTime {
private final long creationTimeNanos;
final long requestId;
final RequestType requestType;
RequestTime(long requestId, RequestType requestType) {
this.creationTimeNanos = System.nanoTime();
this.requestId = requestId;
this.requestType = requestType;
}
boolean isTimedOut(long timeoutMillis) {
long requestAgeMillis = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - creationTimeNanos);
return requestAgeMillis > timeoutMillis;
}
}
private enum RequestType {
Command,
GetLastMessageId,
GetTopics,
GetSchema,
GetOrCreateSchema,
AckResponse,
Lookup;
String getDescription() {
if (this == Command) {
return "request";
} else {
return name() + " request";
}
}
}
public ClientCnx(InstrumentProvider instrumentProvider,
ClientConfigurationData conf, EventLoopGroup eventLoopGroup) {
this(instrumentProvider, conf, eventLoopGroup, Commands.getCurrentProtocolVersion());
}
public ClientCnx(InstrumentProvider instrumentProvider, ClientConfigurationData conf, EventLoopGroup eventLoopGroup,
int protocolVersion) {
super(conf.getKeepAliveIntervalSeconds(), TimeUnit.SECONDS);
checkArgument(conf.getMaxLookupRequest() > conf.getConcurrentLookupRequest());
this.pendingLookupRequestSemaphore = new Semaphore(conf.getConcurrentLookupRequest(), false);
this.maxLookupRequestSemaphore =
new Semaphore(conf.getMaxLookupRequest() - conf.getConcurrentLookupRequest(), false);
this.waitingLookupRequests = Queues.newConcurrentLinkedQueue();
this.authentication = conf.getAuthentication();
this.eventLoopGroup = eventLoopGroup;
this.maxNumberOfRejectedRequestPerConnection = conf.getMaxNumberOfRejectedRequestPerConnection();
this.operationTimeoutMs = conf.getOperationTimeoutMs();
this.state = State.None;
this.protocolVersion = protocolVersion;
this.idleState = new ClientCnxIdleState(this);
this.clientVersion = "Pulsar-Java-v" + PulsarVersion.getVersion()
+ (conf.getDescription() == null ? "" : ("-" + conf.getDescription()));
this.connectionsOpenedCounter =
instrumentProvider.newCounter("pulsar.client.connection.opened", Unit.Connections,
"The number of connections opened", null, Attributes.empty());
this.connectionsClosedCounter =
instrumentProvider.newCounter("pulsar.client.connection.closed", Unit.Connections,
"The number of connections closed", null, Attributes.empty());
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
connectionsOpenedCounter.increment();
this.localAddress = ctx.channel().localAddress();
this.remoteAddress = ctx.channel().remoteAddress();
this.timeoutTask = this.eventLoopGroup
.scheduleAtFixedRate(catchingAndLoggingThrowables(this::checkRequestTimeout), operationTimeoutMs,
operationTimeoutMs, TimeUnit.MILLISECONDS);
if (proxyToTargetBrokerAddress == null) {
if (log.isDebugEnabled()) {
log.debug("{} Connected to broker", ctx.channel());
}
} else {
log.info("{} Connected through proxy to target broker at {}", ctx.channel(), proxyToTargetBrokerAddress);
}
// Send CONNECT command
ctx.writeAndFlush(newConnectCommand())
.addListener(future -> {
if (future.isSuccess()) {
if (log.isDebugEnabled()) {
log.debug("Complete: {}", future.isSuccess());
}
state = State.SentConnectFrame;
} else {
log.warn("Error during handshake", future.cause());
ctx.close();
}
});
}
protected ByteBuf newConnectCommand() throws Exception {
// mutual authentication is to auth between `remoteHostName` and this client for this channel.
// each channel will have a mutual client/server pair, mutual client evaluateChallenge with init data,
// and return authData to server.
authenticationDataProvider = authentication.getAuthData(remoteHostName);
AuthData authData = authenticationDataProvider.authenticate(AuthData.INIT_AUTH_DATA);
return Commands.newConnect(authentication.getAuthMethodName(), authData, this.protocolVersion,
clientVersion, proxyToTargetBrokerAddress, null, null, null);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx);
connectionsClosedCounter.increment();
lastDisconnectedTimestamp = System.currentTimeMillis();
log.info("{} Disconnected", ctx.channel());
if (!connectionFuture.isDone()) {
connectionFuture.completeExceptionally(new PulsarClientException("Connection already closed"));
}
ConnectException e = new ConnectException(
"Disconnected from server at " + ctx.channel().remoteAddress());
// Fail out all the pending ops
pendingRequests.forEach((key, future) -> {
if (pendingRequests.remove(key, future) && !future.isDone()) {
future.completeExceptionally(e);
}
});
waitingLookupRequests.forEach(pair -> pair.getRight().getRight().completeExceptionally(e));
// Notify all attached producers/consumers so they have a chance to reconnect
producers.forEach((id, producer) -> producer.connectionClosed(this, Optional.empty(), Optional.empty()));
consumers.forEach((id, consumer) -> consumer.connectionClosed(this, Optional.empty(), Optional.empty()));
transactionMetaStoreHandlers.forEach((id, handler) -> handler.connectionClosed(this));
topicListWatchers.forEach((__, watcher) -> watcher.connectionClosed(this));
waitingLookupRequests.clear();
producers.clear();
consumers.clear();
topicListWatchers.clear();
timeoutTask.cancel(true);
}
// Command Handlers
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (state != State.Failed) {
// No need to report stack trace for known exceptions that happen in disconnections
log.warn("[{}] Got exception {}", remoteAddress,
ClientCnx.isKnownException(cause) ? cause : ExceptionUtils.getStackTrace(cause));
state = State.Failed;
} else {
// At default info level, suppress all subsequent exceptions that are thrown when the connection has already
// failed
if (log.isDebugEnabled()) {
log.debug("[{}] Got exception: {}", remoteAddress, cause.getMessage(), cause);
}
}
ctx.close();
}
public static boolean isKnownException(Throwable t) {
return t instanceof NativeIoException || t instanceof ClosedChannelException;
}
@VisibleForTesting
public long getDuplicatedResponseCount() {
return duplicatedResponseCounter.get();
}
@Override
protected void handleConnected(CommandConnected connected) {
checkArgument(state == State.SentConnectFrame || state == State.Connecting);
if (connected.hasMaxMessageSize()) {
if (log.isDebugEnabled()) {
log.debug("{} Connection has max message size setting, replace old frameDecoder with "
+ "server frame size {}", ctx.channel(), connected.getMaxMessageSize());
}
maxMessageSize = connected.getMaxMessageSize();
ctx.pipeline().replace("frameDecoder", "newFrameDecoder", new LengthFieldBasedFrameDecoder(
connected.getMaxMessageSize() + Commands.MESSAGE_SIZE_FRAME_PADDING, 0, 4, 0, 4));
}
if (log.isDebugEnabled()) {
log.debug("{} Connection is ready", ctx.channel());
}
supportsTopicWatchers =
connected.hasFeatureFlags() && connected.getFeatureFlags().isSupportsTopicWatchers();
supportsGetPartitionedMetadataWithoutAutoCreation =
connected.hasFeatureFlags()
&& connected.getFeatureFlags().isSupportsGetPartitionedMetadataWithoutAutoCreation();
// set remote protocol version to the correct version before we complete the connection future
setRemoteEndpointProtocolVersion(connected.getProtocolVersion());
connectionFuture.complete(null);
state = State.Ready;
}
@Override
protected void handleAuthChallenge(CommandAuthChallenge authChallenge) {
checkArgument(authChallenge.hasChallenge());
checkArgument(authChallenge.getChallenge().hasAuthData());
if (Arrays.equals(AuthData.REFRESH_AUTH_DATA_BYTES, authChallenge.getChallenge().getAuthData())) {
try {
authenticationDataProvider = authentication.getAuthData(remoteHostName);
} catch (PulsarClientException e) {
log.error("{} Error when refreshing authentication data provider: {}", ctx.channel(), e);
connectionFuture.completeExceptionally(e);
return;
}
}
// mutual authn. If auth not complete, continue auth; if auth complete, complete connectionFuture.
try {
AuthData authData = authenticationDataProvider
.authenticate(AuthData.of(authChallenge.getChallenge().getAuthData()));
checkState(!authData.isComplete());
ByteBuf request = Commands.newAuthResponse(authentication.getAuthMethodName(),
authData,
this.protocolVersion,
clientVersion);
if (log.isDebugEnabled()) {
log.debug("{} Mutual auth {}", ctx.channel(), authentication.getAuthMethodName());
}
ctx.writeAndFlush(request).addListener(writeFuture -> {
if (!writeFuture.isSuccess()) {
log.warn("{} Failed to send request for mutual auth to broker: {}", ctx.channel(),
writeFuture.cause().getMessage());
connectionFuture.completeExceptionally(writeFuture.cause());
}
});
if (state == State.SentConnectFrame) {
state = State.Connecting;
}
} catch (Exception e) {
log.error("{} Error mutual verify: {}", ctx.channel(), e);
connectionFuture.completeExceptionally(e);
return;
}
}
@Override
protected void handleSendReceipt(CommandSendReceipt sendReceipt) {
checkArgument(state == State.Ready);
long producerId = sendReceipt.getProducerId();
long sequenceId = sendReceipt.getSequenceId();
long highestSequenceId = sendReceipt.getHighestSequenceId();
long ledgerId = -1;
long entryId = -1;
if (sendReceipt.hasMessageId()) {
ledgerId = sendReceipt.getMessageId().getLedgerId();
entryId = sendReceipt.getMessageId().getEntryId();
}
if (ledgerId == -1 && entryId == -1) {
log.warn("{} Message with sequence-id {} published by producer {} has been dropped", ctx.channel(),
sequenceId, producerId);
}
if (log.isDebugEnabled()) {
log.debug("{} Got receipt for producer: {} -- msg: {} -- id: {}:{}", ctx.channel(), producerId, sequenceId,
ledgerId, entryId);
}
ProducerImpl> producer = producers.get(producerId);
if (producer != null) {
producer.ackReceived(this, sequenceId, highestSequenceId, ledgerId, entryId);
} else {
if (log.isDebugEnabled()) {
log.debug("Producer is {} already closed, ignore published message [{}-{}]", producerId, ledgerId,
entryId);
}
}
}
@Override
protected void handleAckResponse(CommandAckResponse ackResponse) {
checkArgument(state == State.Ready);
checkArgument(ackResponse.getRequestId() >= 0);
CompletableFuture> completableFuture = pendingRequests.remove(ackResponse.getRequestId());
if (completableFuture != null && !completableFuture.isDone()) {
if (!ackResponse.hasError()) {
completableFuture.complete(null);
} else {
completableFuture.completeExceptionally(
getPulsarClientException(ackResponse.getError(),
buildError(ackResponse.getRequestId(), ackResponse.getMessage())));
}
} else {
duplicatedResponseCounter.incrementAndGet();
log.warn("AckResponse has complete when receive response! requestId : {}, consumerId : {}",
ackResponse.getRequestId(), ackResponse.hasConsumerId());
}
}
@Override
protected void handleMessage(CommandMessage cmdMessage, ByteBuf headersAndPayload) {
checkArgument(state == State.Ready);
if (log.isDebugEnabled()) {
log.debug("{} Received a message from the server: {}", ctx.channel(), cmdMessage);
}
ConsumerImpl> consumer = consumers.get(cmdMessage.getConsumerId());
if (consumer != null) {
consumer.messageReceived(cmdMessage, headersAndPayload, this);
}
}
@Override
protected void handleActiveConsumerChange(CommandActiveConsumerChange change) {
checkArgument(state == State.Ready);
if (log.isDebugEnabled()) {
log.debug("{} Received a consumer group change message from the server : {}", ctx.channel(), change);
}
ConsumerImpl> consumer = consumers.get(change.getConsumerId());
if (consumer != null) {
consumer.activeConsumerChanged(change.isIsActive());
}
}
@Override
protected void handleSuccess(CommandSuccess success) {
checkArgument(state == State.Ready);
if (log.isDebugEnabled()) {
log.debug("{} Received success response from server: {}", ctx.channel(), success.getRequestId());
}
long requestId = success.getRequestId();
CompletableFuture> requestFuture = pendingRequests.remove(requestId);
if (requestFuture != null) {
requestFuture.complete(null);
} else {
duplicatedResponseCounter.incrementAndGet();
log.warn("{} Received unknown request id from server: {}", ctx.channel(), success.getRequestId());
}
}
@Override
protected void handleGetLastMessageIdSuccess(CommandGetLastMessageIdResponse success) {
checkArgument(state == State.Ready);
if (log.isDebugEnabled()) {
log.debug("{} Received success GetLastMessageId response from server: {}",
ctx.channel(), success.getRequestId());
}
long requestId = success.getRequestId();
CompletableFuture requestFuture =
(CompletableFuture) pendingRequests.remove(requestId);
if (requestFuture != null) {
requestFuture.complete(new CommandGetLastMessageIdResponse().copyFrom(success));
} else {
duplicatedResponseCounter.incrementAndGet();
log.warn("{} Received unknown request id from server: {}", ctx.channel(), success.getRequestId());
}
}
@Override
protected void handleProducerSuccess(CommandProducerSuccess success) {
checkArgument(state == State.Ready);
if (log.isDebugEnabled()) {
log.debug("{} Received producer success response from server: {} - producer-name: {}", ctx.channel(),
success.getRequestId(), success.getProducerName());
}
long requestId = success.getRequestId();
if (!success.isProducerReady()) {
// We got a success operation but the producer is not ready. This means that the producer has been queued up
// in broker. We need to leave the future pending until we get the final confirmation. We just mark that
// we have received a response, in order to avoid the timeout.
TimedCompletableFuture> requestFuture = pendingRequests.get(requestId);
if (requestFuture != null) {
log.info("{} Producer {} has been queued up at broker. request: {}", ctx.channel(),
success.getProducerName(), requestId);
requestFuture.markAsResponded();
}
return;
}
CompletableFuture requestFuture =
(CompletableFuture) pendingRequests.remove(requestId);
if (requestFuture != null) {
ProducerResponse pr = new ProducerResponse(success.getProducerName(),
success.getLastSequenceId(),
success.getSchemaVersion(),
success.hasTopicEpoch() ? Optional.of(success.getTopicEpoch()) : Optional.empty());
requestFuture.complete(pr);
} else {
duplicatedResponseCounter.incrementAndGet();
log.warn("{} Received unknown request id from server: {}", ctx.channel(), success.getRequestId());
}
}
@Override
protected void handleLookupResponse(CommandLookupTopicResponse lookupResult) {
if (log.isDebugEnabled()) {
CommandLookupTopicResponse.LookupType response =
lookupResult.hasResponse() ? lookupResult.getResponse() : null;
log.debug("Received Broker lookup response: {} {}", lookupResult.getRequestId(), response);
}
long requestId = lookupResult.getRequestId();
CompletableFuture requestFuture = getAndRemovePendingLookupRequest(requestId);
if (requestFuture != null) {
if (requestFuture.isCompletedExceptionally()) {
if (log.isDebugEnabled()) {
log.debug("{} Request {} already timed-out", ctx.channel(), lookupResult.getRequestId());
}
return;
}
// Complete future with exception if : Result.response=fail/null
if (!lookupResult.hasResponse()
|| CommandLookupTopicResponse.LookupType.Failed.equals(lookupResult.getResponse())) {
if (lookupResult.hasError()) {
checkServerError(lookupResult.getError(), lookupResult.getMessage());
requestFuture.completeExceptionally(
getPulsarClientException(lookupResult.getError(),
buildError(lookupResult.getRequestId(), lookupResult.getMessage())));
} else {
requestFuture
.completeExceptionally(new PulsarClientException.LookupException("Empty lookup response"));
}
} else {
requestFuture.complete(new LookupDataResult(lookupResult));
}
} else {
log.warn("{} Received unknown request id from server: {}", ctx.channel(), lookupResult.getRequestId());
}
}
@Override
protected void handlePartitionResponse(CommandPartitionedTopicMetadataResponse lookupResult) {
if (log.isDebugEnabled()) {
CommandPartitionedTopicMetadataResponse.LookupType response =
lookupResult.hasResponse() ? lookupResult.getResponse() : null;
int partitions = lookupResult.hasPartitions() ? lookupResult.getPartitions() : -1;
log.debug("Received Broker Partition response: {} {} {}", lookupResult.getRequestId(), response,
partitions);
}
long requestId = lookupResult.getRequestId();
CompletableFuture requestFuture = getAndRemovePendingLookupRequest(requestId);
if (requestFuture != null) {
if (requestFuture.isCompletedExceptionally()) {
if (log.isDebugEnabled()) {
log.debug("{} Request {} already timed-out", ctx.channel(), lookupResult.getRequestId());
}
return;
}
// Complete future with exception if : Result.response=fail/null
if (!lookupResult.hasResponse()
|| CommandPartitionedTopicMetadataResponse.LookupType.Failed.equals(lookupResult.getResponse())) {
if (lookupResult.hasError()) {
String message = buildError(lookupResult.getRequestId(),
lookupResult.hasMessage() ? lookupResult.getMessage() : null);
checkServerError(lookupResult.getError(), message);
requestFuture.completeExceptionally(
getPulsarClientException(lookupResult.getError(), message));
} else {
requestFuture
.completeExceptionally(new PulsarClientException.LookupException("Empty lookup response"));
}
} else {
// return LookupDataResult when Result.response = success/redirect
requestFuture.complete(new LookupDataResult(lookupResult.getPartitions()));
}
} else {
log.warn("{} Received unknown request id from server: {}", ctx.channel(), lookupResult.getRequestId());
}
}
@Override
protected void handleReachedEndOfTopic(CommandReachedEndOfTopic commandReachedEndOfTopic) {
final long consumerId = commandReachedEndOfTopic.getConsumerId();
log.info("[{}] Broker notification reached the end of topic: {}", remoteAddress, consumerId);
ConsumerImpl> consumer = consumers.get(consumerId);
if (consumer != null) {
consumer.setTerminated();
}
}
@Override
protected void handleTopicMigrated(CommandTopicMigrated commandTopicMigrated) {
final long resourceId = commandTopicMigrated.getResourceId();
final String serviceUrl = commandTopicMigrated.getBrokerServiceUrl();
final String serviceUrlTls = commandTopicMigrated.getBrokerServiceUrlTls();
HandlerState resource = commandTopicMigrated.getResourceType() == ResourceType.Producer
? producers.get(resourceId)
: consumers.get(resourceId);
log.info("{} is migrated to {}/{}", commandTopicMigrated.getResourceType().name(), serviceUrl, serviceUrlTls);
if (resource != null) {
try {
resource.setRedirectedClusterURI(serviceUrl, serviceUrlTls);
} catch (URISyntaxException e) {
log.info("[{}] Invalid redirect url {}/{} for {}", remoteAddress, serviceUrl, serviceUrlTls,
resourceId);
}
}
}
// caller of this method needs to be protected under pendingLookupRequestSemaphore
private void addPendingLookupRequests(long requestId, TimedCompletableFuture future) {
pendingRequests.put(requestId, future);
requestTimeoutQueue.add(new RequestTime(requestId, RequestType.Lookup));
}
private CompletableFuture getAndRemovePendingLookupRequest(long requestId) {
CompletableFuture result =
(CompletableFuture) pendingRequests.remove(requestId);
if (result != null) {
Pair>> firstOneWaiting =
waitingLookupRequests.poll();
if (firstOneWaiting != null) {
maxLookupRequestSemaphore.release();
// schedule a new lookup in.
eventLoopGroup.execute(() -> {
long newId = firstOneWaiting.getLeft();
TimedCompletableFuture newFuture = firstOneWaiting.getRight().getRight();
addPendingLookupRequests(newId, newFuture);
ctx.writeAndFlush(firstOneWaiting.getRight().getLeft()).addListener(writeFuture -> {
if (!writeFuture.isSuccess()) {
log.warn("{} Failed to send request {} to broker: {}", ctx.channel(), newId,
writeFuture.cause().getMessage());
getAndRemovePendingLookupRequest(newId);
newFuture.completeExceptionally(writeFuture.cause());
}
});
});
} else {
pendingLookupRequestSemaphore.release();
}
} else {
duplicatedResponseCounter.incrementAndGet();
}
return result;
}
@Override
protected void handleSendError(CommandSendError sendError) {
log.warn("{} Received send error from server: {} : {}", ctx.channel(), sendError.getError(),
sendError.getMessage());
long producerId = sendError.getProducerId();
long sequenceId = sendError.getSequenceId();
switch (sendError.getError()) {
case ChecksumError:
producers.get(producerId).recoverChecksumError(this, sequenceId);
break;
case TopicTerminatedError:
producers.get(producerId).terminated(this);
break;
case NotAllowedError:
producers.get(producerId).recoverNotAllowedError(sequenceId, sendError.getMessage());
break;
default:
// By default, for transient error, let the reconnection logic
// to take place and re-establish the produce again
ctx.close();
}
}
@Override
protected void handleError(CommandError error) {
checkArgument(state == State.SentConnectFrame || state == State.Ready);
log.warn("{} Received error from server: {}", ctx.channel(), error.getMessage());
long requestId = error.getRequestId();
if (error.getError() == ServerError.ProducerBlockedQuotaExceededError) {
log.warn("{} Producer creation has been blocked because backlog quota exceeded for producer topic",
ctx.channel());
}
if (error.getError() == ServerError.AuthenticationError) {
connectionFuture.completeExceptionally(
new PulsarClientException.AuthenticationException(error.getMessage()));
log.error("{} Failed to authenticate the client", ctx.channel());
}
if (error.getError() == ServerError.NotAllowedError) {
log.error("Get not allowed error, {}", error.getMessage());
connectionFuture.completeExceptionally(new PulsarClientException.NotAllowedException(error.getMessage()));
}
CompletableFuture> requestFuture = pendingRequests.remove(requestId);
if (requestFuture != null) {
requestFuture.completeExceptionally(
getPulsarClientException(error.getError(),
buildError(error.getRequestId(), error.getMessage())));
} else {
duplicatedResponseCounter.incrementAndGet();
log.warn("{} Received unknown request id from server: {}", ctx.channel(), error.getRequestId());
}
}
@Override
protected void handleCloseProducer(CommandCloseProducer closeProducer) {
final long producerId = closeProducer.getProducerId();
log.info("[{}] Broker notification of closed producer: {}, assignedBrokerUrl: {}, assignedBrokerUrlTls: {}",
remoteAddress, producerId,
closeProducer.hasAssignedBrokerServiceUrl() ? closeProducer.getAssignedBrokerServiceUrl() : null,
closeProducer.hasAssignedBrokerServiceUrlTls() ? closeProducer.getAssignedBrokerServiceUrlTls() : null);
ProducerImpl> producer = producers.remove(producerId);
if (producer != null) {
String brokerServiceUrl = getBrokerServiceUrl(closeProducer, producer);
Optional hostUri = parseUri(brokerServiceUrl,
closeProducer.hasRequestId() ? closeProducer.getRequestId() : null);
Optional initialConnectionDelayMs = hostUri.map(__ -> 0L);
producer.connectionClosed(this, initialConnectionDelayMs, hostUri);
} else {
log.warn("[{}] Producer with id {} not found while closing producer", remoteAddress, producerId);
}
}
private static String getBrokerServiceUrl(CommandCloseProducer closeProducer, ProducerImpl> producer) {
if (producer.getClient().getConfiguration().isUseTls()) {
if (closeProducer.hasAssignedBrokerServiceUrlTls()) {
return closeProducer.getAssignedBrokerServiceUrlTls();
}
} else if (closeProducer.hasAssignedBrokerServiceUrl()) {
return closeProducer.getAssignedBrokerServiceUrl();
}
return null;
}
@Override
protected void handleCloseConsumer(CommandCloseConsumer closeConsumer) {
final long consumerId = closeConsumer.getConsumerId();
log.info("[{}] Broker notification of closed consumer: {}, assignedBrokerUrl: {}, assignedBrokerUrlTls: {}",
remoteAddress, consumerId,
closeConsumer.hasAssignedBrokerServiceUrl() ? closeConsumer.getAssignedBrokerServiceUrl() : null,
closeConsumer.hasAssignedBrokerServiceUrlTls() ? closeConsumer.getAssignedBrokerServiceUrlTls() : null);
ConsumerImpl> consumer = consumers.remove(consumerId);
if (consumer != null) {
String brokerServiceUrl = getBrokerServiceUrl(closeConsumer, consumer);
Optional hostUri = parseUri(brokerServiceUrl,
closeConsumer.hasRequestId() ? closeConsumer.getRequestId() : null);
Optional initialConnectionDelayMs = hostUri.map(__ -> 0L);
consumer.connectionClosed(this, initialConnectionDelayMs, hostUri);
} else {
log.warn("[{}] Consumer with id {} not found while closing consumer", remoteAddress, consumerId);
}
}
private static String getBrokerServiceUrl(CommandCloseConsumer closeConsumer, ConsumerImpl> consumer) {
if (consumer.getClient().getConfiguration().isUseTls()) {
if (closeConsumer.hasAssignedBrokerServiceUrlTls()) {
return closeConsumer.getAssignedBrokerServiceUrlTls();
}
} else if (closeConsumer.hasAssignedBrokerServiceUrl()) {
return closeConsumer.getAssignedBrokerServiceUrl();
}
return null;
}
private Optional parseUri(String url, Long requestId) {
try {
if (url != null) {
return Optional.of(new URI(url));
}
} catch (URISyntaxException e) {
log.warn("[{}] Invalid redirect URL {}, requestId {}: ", remoteAddress, url, requestId, e);
}
return Optional.empty();
}
@Override
protected boolean isHandshakeCompleted() {
return state == State.Ready;
}
public CompletableFuture newLookup(ByteBuf request, long requestId) {
TimedCompletableFuture future = new TimedCompletableFuture<>();
if (pendingLookupRequestSemaphore.tryAcquire()) {
future.whenComplete((lookupDataResult, throwable) -> {
if (throwable instanceof ConnectException
|| throwable instanceof PulsarClientException.LookupException) {
pendingLookupRequestSemaphore.release();
}
});
addPendingLookupRequests(requestId, future);
ctx.writeAndFlush(request).addListener(writeFuture -> {
if (!writeFuture.isSuccess()) {
log.warn("{} Failed to send request {} to broker: {}", ctx.channel(), requestId,
writeFuture.cause().getMessage());
getAndRemovePendingLookupRequest(requestId);
future.completeExceptionally(writeFuture.cause());
}
});
} else {
if (log.isDebugEnabled()) {
log.debug("{} Failed to add lookup-request into pending queue", requestId);
}
if (maxLookupRequestSemaphore.tryAcquire()) {
waitingLookupRequests.add(Pair.of(requestId, Pair.of(request, future)));
} else {
if (log.isDebugEnabled()) {
log.debug("{} Failed to add lookup-request into waiting queue", requestId);
}
future.completeExceptionally(new PulsarClientException.TooManyRequestsException(String.format(
"Requests number out of config: There are {%s} lookup requests outstanding and {%s} requests"
+ " pending.",
pendingLookupRequestSemaphore.getQueueLength(),
waitingLookupRequests.size())));
}
}
return future;
}
public CompletableFuture newGetTopicsOfNamespace(ByteBuf request, long requestId) {
return sendRequestAndHandleTimeout(request, requestId, RequestType.GetTopics, true);
}
public CompletableFuture newAckForReceipt(ByteBuf request, long requestId) {
return sendRequestAndHandleTimeout(request, requestId, RequestType.AckResponse, true);
}
public void newAckForReceiptWithFuture(ByteBuf request, long requestId,
TimedCompletableFuture future) {
sendRequestAndHandleTimeout(request, requestId, RequestType.AckResponse, false, future);
}
@Override
protected void handleGetTopicsOfNamespaceSuccess(CommandGetTopicsOfNamespaceResponse success) {
checkArgument(state == State.Ready);
long requestId = success.getRequestId();
List topics = success.getTopicsList();
if (log.isDebugEnabled()) {
log.debug("{} Received get topics of namespace success response from server: {} - topics.size: {}",
ctx.channel(), success.getRequestId(), topics.size());
}
CompletableFuture requestFuture =
(CompletableFuture) pendingRequests.remove(requestId);
if (requestFuture != null) {
requestFuture.complete(new GetTopicsResult(topics,
success.hasTopicsHash() ? success.getTopicsHash() : null,
success.isFiltered(),
success.isChanged()));
} else {
duplicatedResponseCounter.incrementAndGet();
log.warn("{} Received unknown request id from server: {}", ctx.channel(), success.getRequestId());
}
}
@Override
protected void handleGetSchemaResponse(CommandGetSchemaResponse commandGetSchemaResponse) {
checkArgument(state == State.Ready);
long requestId = commandGetSchemaResponse.getRequestId();
CompletableFuture future =
(CompletableFuture) pendingRequests.remove(requestId);
if (future == null) {
duplicatedResponseCounter.incrementAndGet();
log.warn("{} Received unknown request id from server: {}", ctx.channel(), requestId);
return;
}
future.complete(new CommandGetSchemaResponse().copyFrom(commandGetSchemaResponse));
}
@Override
protected void handleGetOrCreateSchemaResponse(CommandGetOrCreateSchemaResponse commandGetOrCreateSchemaResponse) {
checkArgument(state == State.Ready);
long requestId = commandGetOrCreateSchemaResponse.getRequestId();
CompletableFuture future =
(CompletableFuture) pendingRequests.remove(requestId);
if (future == null) {
duplicatedResponseCounter.incrementAndGet();
log.warn("{} Received unknown request id from server: {}", ctx.channel(), requestId);
return;
}
future.complete(new CommandGetOrCreateSchemaResponse().copyFrom(commandGetOrCreateSchemaResponse));
}
Promise newPromise() {
return ctx.newPromise();
}
public ChannelHandlerContext ctx() {
return ctx;
}
Channel channel() {
return ctx.channel();
}
CompletableFuture connectionFuture() {
return connectionFuture;
}
CompletableFuture sendRequestWithId(ByteBuf cmd, long requestId) {
return sendRequestAndHandleTimeout(cmd, requestId, RequestType.Command, true);
}
private void sendRequestAndHandleTimeout(ByteBuf requestMessage, long requestId,
RequestType requestType, boolean flush,
TimedCompletableFuture future) {
pendingRequests.put(requestId, future);
if (flush) {
ctx.writeAndFlush(requestMessage).addListener(writeFuture -> {
if (!writeFuture.isSuccess()) {
if (pendingRequests.remove(requestId, future) && !future.isDone()) {
log.warn("{} Failed to send {} to broker: {}", ctx.channel(),
requestType.getDescription(), writeFuture.cause().getMessage());
future.completeExceptionally(writeFuture.cause());
}
}
});
} else {
ctx.write(requestMessage, ctx().voidPromise());
}
requestTimeoutQueue.add(new RequestTime(requestId, requestType));
}
private CompletableFuture sendRequestAndHandleTimeout(ByteBuf requestMessage, long requestId,
RequestType requestType, boolean flush) {
TimedCompletableFuture future = new TimedCompletableFuture<>();
sendRequestAndHandleTimeout(requestMessage, requestId, requestType, flush, future);
return future;
}
public CompletableFuture sendGetLastMessageId(ByteBuf request, long requestId) {
return sendRequestAndHandleTimeout(request, requestId, RequestType.GetLastMessageId, true);
}
public CompletableFuture> sendGetSchema(ByteBuf request, long requestId) {
return sendGetRawSchema(request, requestId).thenCompose(commandGetSchemaResponse -> {
if (commandGetSchemaResponse.hasErrorCode()) {
// Request has failed
ServerError rc = commandGetSchemaResponse.getErrorCode();
if (rc == ServerError.TopicNotFound) {
return CompletableFuture.completedFuture(Optional.empty());
} else {
return FutureUtil.failedFuture(
getPulsarClientException(rc,
buildError(requestId, commandGetSchemaResponse.getErrorMessage())));
}
} else {
return CompletableFuture.completedFuture(
Optional.of(SchemaInfoUtil.newSchemaInfo(commandGetSchemaResponse.getSchema())));
}
});
}
public CompletableFuture sendGetRawSchema(ByteBuf request, long requestId) {
return sendRequestAndHandleTimeout(request, requestId, RequestType.GetSchema, true);
}
public CompletableFuture sendGetOrCreateSchema(ByteBuf request, long requestId) {
CompletableFuture future = sendRequestAndHandleTimeout(request, requestId,
RequestType.GetOrCreateSchema, true);
return future.thenCompose(response -> {
if (response.hasErrorCode()) {
// Request has failed
ServerError rc = response.getErrorCode();
if (rc == ServerError.TopicNotFound) {
return CompletableFuture.completedFuture(SchemaVersion.Empty.bytes());
} else {
return FutureUtil.failedFuture(getPulsarClientException(
rc, buildError(requestId, response.getErrorMessage())));
}
} else {
return CompletableFuture.completedFuture(response.getSchemaVersion());
}
});
}
@Override
protected void handleNewTxnResponse(CommandNewTxnResponse command) {
TransactionMetaStoreHandler handler = checkAndGetTransactionMetaStoreHandler(command.getTxnidMostBits());
if (handler != null) {
handler.handleNewTxnResponse(command);
}
}
@Override
protected void handleAddPartitionToTxnResponse(CommandAddPartitionToTxnResponse command) {
TransactionMetaStoreHandler handler = checkAndGetTransactionMetaStoreHandler(command.getTxnidMostBits());
if (handler != null) {
handler.handleAddPublishPartitionToTxnResponse(command);
}
}
@Override
protected void handleAddSubscriptionToTxnResponse(CommandAddSubscriptionToTxnResponse command) {
TransactionMetaStoreHandler handler = checkAndGetTransactionMetaStoreHandler(command.getTxnidMostBits());
if (handler != null) {
handler.handleAddSubscriptionToTxnResponse(command);
}
}
@Override
protected void handleEndTxnOnPartitionResponse(CommandEndTxnOnPartitionResponse command) {
TransactionBufferHandler handler = checkAndGetTransactionBufferHandler();
if (handler != null) {
handler.handleEndTxnOnTopicResponse(command.getRequestId(), command);
}
}
@Override
protected void handleEndTxnOnSubscriptionResponse(CommandEndTxnOnSubscriptionResponse command) {
TransactionBufferHandler handler = checkAndGetTransactionBufferHandler();
if (handler != null) {
handler.handleEndTxnOnSubscriptionResponse(command.getRequestId(), command);
}
}
@Override
protected void handleEndTxnResponse(CommandEndTxnResponse command) {
TransactionMetaStoreHandler handler = checkAndGetTransactionMetaStoreHandler(command.getTxnidMostBits());
if (handler != null) {
handler.handleEndTxnResponse(command);
}
}
@Override
protected void handleTcClientConnectResponse(CommandTcClientConnectResponse response) {
checkArgument(state == State.Ready);
if (log.isDebugEnabled()) {
log.debug("{} Received tc client connect response "
+ "from server: {}", ctx.channel(), response.getRequestId());
}
long requestId = response.getRequestId();
CompletableFuture> requestFuture = pendingRequests.remove(requestId);
if (requestFuture != null && !requestFuture.isDone()) {
if (!response.hasError()) {
requestFuture.complete(null);
} else {
ServerError error = response.getError();
log.error("Got tc client connect response for request: {}, error: {}, errorMessage: {}",
response.getRequestId(), response.getError(), response.getMessage());
requestFuture.completeExceptionally(getExceptionByServerError(error, response.getMessage()));
}
} else {
duplicatedResponseCounter.incrementAndGet();
log.warn("Tc client connect command has been completed and get response for request: {}",
response.getRequestId());
}
}
private TransactionMetaStoreHandler checkAndGetTransactionMetaStoreHandler(long tcId) {
TransactionMetaStoreHandler handler = transactionMetaStoreHandlers.get(tcId);
if (handler == null) {
channel().close();
log.warn("Close the channel since can't get the transaction meta store handler, will reconnect later.");
}
return handler;
}
private TransactionBufferHandler checkAndGetTransactionBufferHandler() {
if (transactionBufferHandler == null) {
channel().close();
log.warn("Close the channel since can't get the transaction buffer handler.");
}
return transactionBufferHandler;
}
public CompletableFuture newWatchTopicList(
BaseCommand commandWatchTopicList, long requestId) {
if (!supportsTopicWatchers) {
return FutureUtil.failedFuture(
new PulsarClientException.NotAllowedException(
"Broker does not allow broker side pattern evaluation."));
}
return sendRequestAndHandleTimeout(Commands.serializeWithSize(commandWatchTopicList), requestId,
RequestType.Command, true);
}
public CompletableFuture newWatchTopicListClose(
BaseCommand commandWatchTopicListClose, long requestId) {
return sendRequestAndHandleTimeout(
Commands.serializeWithSize(commandWatchTopicListClose), requestId, RequestType.Command, true);
}
@Override
protected void handleCommandWatchTopicListSuccess(CommandWatchTopicListSuccess commandWatchTopicListSuccess) {
checkArgument(state == State.Ready);
if (log.isDebugEnabled()) {
log.debug("{} Received watchTopicListSuccess response from server: {}",
ctx.channel(), commandWatchTopicListSuccess.getRequestId());
}
long requestId = commandWatchTopicListSuccess.getRequestId();
CompletableFuture requestFuture =
(CompletableFuture) pendingRequests.remove(requestId);
if (requestFuture != null) {
requestFuture.complete(commandWatchTopicListSuccess);
} else {
duplicatedResponseCounter.incrementAndGet();
log.warn("{} Received unknown request id from server: {}",
ctx.channel(), commandWatchTopicListSuccess.getRequestId());
}
}
@Override
protected void handleCommandWatchTopicUpdate(CommandWatchTopicUpdate commandWatchTopicUpdate) {
checkArgument(state == State.Ready);
if (log.isDebugEnabled()) {
log.debug("{} Received watchTopicUpdate command from server: {}",
ctx.channel(), commandWatchTopicUpdate.getWatcherId());
}
long watcherId = commandWatchTopicUpdate.getWatcherId();
TopicListWatcher watcher = topicListWatchers.get(watcherId);
if (watcher != null) {
watcher.handleCommandWatchTopicUpdate(commandWatchTopicUpdate);
} else {
log.warn("{} Received topic list update for unknown watcher from server: {}", ctx.channel(), watcherId);
}
}
/**
* check serverError and take appropriate action.
*
* - InternalServerError: close connection immediately
* - TooManyRequest: received error count is more than maxNumberOfRejectedRequestPerConnection in
* #rejectedRequestResetTimeSec
*
*
* @param error
* @param errMsg
*/
private void checkServerError(ServerError error, String errMsg) {
if (ServerError.ServiceNotReady.equals(error)) {
log.error("{} Close connection because received internal-server error {}", ctx.channel(), errMsg);
ctx.close();
} else if (ServerError.TooManyRequests.equals(error)) {
incrementRejectsAndMaybeClose();
}
}
private void incrementRejectsAndMaybeClose() {
long rejectedRequests = NUMBER_OF_REJECTED_REQUESTS_UPDATER.getAndIncrement(this);
if (rejectedRequests == 0) {
// schedule timer
eventLoopGroup.schedule(() -> NUMBER_OF_REJECTED_REQUESTS_UPDATER.set(ClientCnx.this, 0),
rejectedRequestResetTimeSec, TimeUnit.SECONDS);
} else if (rejectedRequests >= maxNumberOfRejectedRequestPerConnection) {
log.error("{} Close connection because received {} rejected request in {} seconds ", ctx.channel(),
NUMBER_OF_REJECTED_REQUESTS_UPDATER.get(ClientCnx.this), rejectedRequestResetTimeSec);
ctx.close();
}
}
void registerConsumer(final long consumerId, final ConsumerImpl> consumer) {
consumers.put(consumerId, consumer);
}
void registerProducer(final long producerId, final ProducerImpl> producer) {
producers.put(producerId, producer);
}
void registerTransactionMetaStoreHandler(final long transactionMetaStoreId,
final TransactionMetaStoreHandler handler) {
transactionMetaStoreHandlers.put(transactionMetaStoreId, handler);
}
void registerTopicListWatcher(final long watcherId, final TopicListWatcher watcher) {
topicListWatchers.put(watcherId, watcher);
}
public void registerTransactionBufferHandler(final TransactionBufferHandler handler) {
transactionBufferHandler = handler;
}
void removeProducer(final long producerId) {
producers.remove(producerId);
}
void removeConsumer(final long consumerId) {
consumers.remove(consumerId);
}
void removeTopicListWatcher(final long watcherId) {
topicListWatchers.remove(watcherId);
}
void setTargetBroker(InetSocketAddress targetBrokerAddress) {
this.proxyToTargetBrokerAddress = String.format("%s:%d", targetBrokerAddress.getHostString(),
targetBrokerAddress.getPort());
}
void setRemoteHostName(String remoteHostName) {
this.remoteHostName = remoteHostName;
}
private String buildError(long requestId, String errorMsg) {
return new StringBuilder().append("{\"errorMsg\":\"").append(errorMsg)
.append("\",\"reqId\":").append(requestId)
.append(", \"remote\":\"").append(remoteAddress)
.append("\", \"local\":\"").append(localAddress)
.append("\"}").toString();
}
public static PulsarClientException getPulsarClientException(ServerError error, String errorMsg) {
switch (error) {
case AuthenticationError:
return new PulsarClientException.AuthenticationException(errorMsg);
case AuthorizationError:
return new PulsarClientException.AuthorizationException(errorMsg);
case ProducerBusy:
return new PulsarClientException.ProducerBusyException(errorMsg);
case ConsumerBusy:
return new PulsarClientException.ConsumerBusyException(errorMsg);
case MetadataError:
return new PulsarClientException.BrokerMetadataException(errorMsg);
case PersistenceError:
return new PulsarClientException.BrokerPersistenceException(errorMsg);
case ServiceNotReady:
return new PulsarClientException.LookupException(errorMsg);
case TooManyRequests:
return new PulsarClientException.TooManyRequestsException(errorMsg);
case ProducerBlockedQuotaExceededError:
return new PulsarClientException.ProducerBlockedQuotaExceededError(errorMsg);
case ProducerBlockedQuotaExceededException:
return new PulsarClientException.ProducerBlockedQuotaExceededException(errorMsg);
case TopicTerminatedError:
return new PulsarClientException.TopicTerminatedException(errorMsg);
case IncompatibleSchema:
return new PulsarClientException.IncompatibleSchemaException(errorMsg);
case TopicNotFound:
return new PulsarClientException.TopicDoesNotExistException(errorMsg);
case SubscriptionNotFound:
return new PulsarClientException.SubscriptionNotFoundException(errorMsg);
case ConsumerAssignError:
return new PulsarClientException.ConsumerAssignException(errorMsg);
case NotAllowedError:
return new PulsarClientException.NotAllowedException(errorMsg);
case TransactionConflict:
return new PulsarClientException.TransactionConflictException(errorMsg);
case ProducerFenced:
return new PulsarClientException.ProducerFencedException(errorMsg);
case UnknownError:
default:
return new PulsarClientException(errorMsg);
}
}
public void close() {
if (ctx != null) {
ctx.close();
}
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof SslHandshakeCompletionEvent) {
SslHandshakeCompletionEvent sslHandshakeCompletionEvent = (SslHandshakeCompletionEvent) evt;
if (sslHandshakeCompletionEvent.cause() != null) {
log.warn("{} Got ssl handshake exception {}", ctx.channel(),
sslHandshakeCompletionEvent);
}
}
ctx.fireUserEventTriggered(evt);
}
protected void closeWithException(Throwable e) {
if (ctx != null) {
connectionFuture.completeExceptionally(e);
ctx.close();
}
}
private void checkRequestTimeout() {
while (!requestTimeoutQueue.isEmpty()) {
RequestTime request = requestTimeoutQueue.peek();
if (request == null || !request.isTimedOut(operationTimeoutMs)) {
// if there is no request that is timed out then exit the loop
break;
}
if (!requestTimeoutQueue.remove(request)) {
// the request has been removed by another thread
continue;
}
TimedCompletableFuture> requestFuture = pendingRequests.get(request.requestId);
if (requestFuture != null
&& !requestFuture.hasGotResponse()) {
pendingRequests.remove(request.requestId, requestFuture);
if (!requestFuture.isDone()) {
String timeoutMessage = String.format(
"%s timeout {'durationMs': '%d', 'reqId':'%d', 'remote':'%s', 'local':'%s'}",
request.requestType.getDescription(), operationTimeoutMs,
request.requestId, remoteAddress, localAddress);
if (requestFuture.completeExceptionally(new TimeoutException(timeoutMessage))) {
if (request.requestType == RequestType.Lookup) {
incrementRejectsAndMaybeClose();
}
log.warn("{} {}", ctx.channel(), timeoutMessage);
}
}
}
}
}
private static final Logger log = LoggerFactory.getLogger(ClientCnx.class);
/**
* Check client connection is now free. This method will not change the state to idle.
* @return true if the connection is eligible.
*/
public boolean idleCheck() {
if (pendingRequests != null && !pendingRequests.isEmpty()) {
return false;
}
if (waitingLookupRequests != null && !waitingLookupRequests.isEmpty()) {
return false;
}
if (!consumers.isEmpty()) {
return false;
}
if (!producers.isEmpty()) {
return false;
}
if (!transactionMetaStoreHandlers.isEmpty()) {
return false;
}
return true;
}
}