com.microsoft.azure.servicebus.MessageSender Maven / Gradle / Ivy
/*
* Copyright (c) Microsoft. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project root for full license information.
*/
package com.microsoft.azure.servicebus;
import java.io.IOException;
import java.nio.BufferOverflowException;
import java.time.Duration;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.PriorityQueue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.qpid.proton.Proton;
import org.apache.qpid.proton.amqp.Binary;
import org.apache.qpid.proton.amqp.Symbol;
import org.apache.qpid.proton.amqp.messaging.Accepted;
import org.apache.qpid.proton.amqp.messaging.ApplicationProperties;
import org.apache.qpid.proton.amqp.messaging.Data;
import org.apache.qpid.proton.amqp.messaging.MessageAnnotations;
import org.apache.qpid.proton.amqp.messaging.Rejected;
import org.apache.qpid.proton.amqp.messaging.Released;
import org.apache.qpid.proton.amqp.messaging.Source;
import org.apache.qpid.proton.amqp.messaging.Target;
import org.apache.qpid.proton.amqp.transport.DeliveryState;
import org.apache.qpid.proton.amqp.transport.ErrorCondition;
import org.apache.qpid.proton.amqp.transport.SenderSettleMode;
import org.apache.qpid.proton.engine.BaseHandler;
import org.apache.qpid.proton.engine.Connection;
import org.apache.qpid.proton.engine.Delivery;
import org.apache.qpid.proton.engine.EndpointState;
import org.apache.qpid.proton.engine.Sender;
import org.apache.qpid.proton.engine.Session;
import org.apache.qpid.proton.engine.impl.DeliveryImpl;
import org.apache.qpid.proton.message.Message;
import com.microsoft.azure.servicebus.amqp.AmqpConstants;
import com.microsoft.azure.servicebus.amqp.DispatchHandler;
import com.microsoft.azure.servicebus.amqp.IAmqpSender;
import com.microsoft.azure.servicebus.amqp.SendLinkHandler;
import com.microsoft.azure.servicebus.amqp.SessionHandler;
/**
* Abstracts all amqp related details
* translates event-driven reactor model into async send Api
*/
public class MessageSender extends ClientEntity implements IAmqpSender, IErrorContextProvider
{
private static final Logger TRACE_LOGGER = Logger.getLogger(ClientConstants.SERVICEBUS_CLIENT_TRACE);
private static final String SEND_TIMED_OUT = "Send operation timed out";
private final MessagingFactory underlyingFactory;
private final String sendPath;
private final Duration operationTimeout;
private final RetryPolicy retryPolicy;
private final CompletableFuture linkClose;
private final Object pendingSendLock;
private final ConcurrentHashMap> pendingSendsData;
private final PriorityQueue pendingSends;
private final DispatchHandler sendWork;
private Sender sendLink;
private CompletableFuture linkFirstOpen;
private int linkCredit;
private TimeoutTracker openLinkTracker;
private Exception lastKnownLinkError;
private Instant lastKnownErrorReportedAt;
public static CompletableFuture create(
final MessagingFactory factory,
final String sendLinkName,
final String senderPath)
{
final MessageSender msgSender = new MessageSender(factory, sendLinkName, senderPath);
msgSender.openLinkTracker = TimeoutTracker.create(factory.getOperationTimeout());
msgSender.initializeLinkOpen(msgSender.openLinkTracker);
try
{
msgSender.underlyingFactory.scheduleOnReactorThread(new DispatchHandler()
{
@Override
public void onEvent()
{
msgSender.createSendLink();
}
});
}
catch (IOException ioException)
{
msgSender.linkFirstOpen.completeExceptionally(new ServiceBusException(false, "Failed to create Sender, see cause for more details.", ioException));
}
return msgSender.linkFirstOpen;
}
private MessageSender(final MessagingFactory factory, final String sendLinkName, final String senderPath)
{
super(sendLinkName, factory);
this.sendPath = senderPath;
this.underlyingFactory = factory;
this.operationTimeout = factory.getOperationTimeout();
this.lastKnownLinkError = null;
this.lastKnownErrorReportedAt = Instant.EPOCH;
this.retryPolicy = factory.getRetryPolicy();
this.pendingSendLock = new Object();
this.pendingSendsData = new ConcurrentHashMap>();
this.pendingSends = new PriorityQueue(1000, new DeliveryTagComparator());
this.linkCredit = 0;
this.linkClose = new CompletableFuture();
this.sendWork = new DispatchHandler()
{
@Override
public void onEvent()
{
MessageSender.this.processSendWork();
}
};
}
public String getSendPath()
{
return this.sendPath;
}
private CompletableFuture send(byte[] bytes, int arrayOffset, int messageFormat)
{
return this.send(bytes, arrayOffset, messageFormat, null, null);
}
private CompletableFuture sendCore(
final byte[] bytes,
final int arrayOffset,
final int messageFormat,
final CompletableFuture onSend,
final TimeoutTracker tracker,
final String deliveryTag,
final Exception lastKnownError,
final ScheduledFuture> timeoutTask)
{
this.throwIfClosed(this.lastKnownLinkError);
if (tracker != null && onSend != null && (tracker.remaining().isNegative() || tracker.remaining().isZero()))
{
if (TRACE_LOGGER.isLoggable(Level.FINE))
{
TRACE_LOGGER.log(Level.FINE,
String.format(Locale.US,
"path[%s], linkName[%s], deliveryTag[%s] - timed out at sendCore", this.sendPath, this.sendLink.getName(), deliveryTag));
}
if (timeoutTask != null)
{
timeoutTask.cancel(false);
}
this.throwSenderTimeout(onSend, null);
return onSend;
}
final boolean isRetrySend = (onSend != null);
final String tag = (deliveryTag == null) ? UUID.randomUUID().toString().replace("-", StringUtil.EMPTY) : deliveryTag;
final CompletableFuture onSendFuture = (onSend == null) ? new CompletableFuture() : onSend;
final ReplayableWorkItem sendWaiterData = (tracker == null) ?
new ReplayableWorkItem(bytes, arrayOffset, messageFormat, onSendFuture, this.operationTimeout) :
new ReplayableWorkItem(bytes, arrayOffset, messageFormat, onSendFuture, tracker);
if (lastKnownError != null)
{
sendWaiterData.setLastKnownException(lastKnownError);
}
synchronized (this.pendingSendLock)
{
this.pendingSendsData.put(tag, sendWaiterData);
this.pendingSends.offer(new WeightedDeliveryTag(tag, isRetrySend ? 1 : 0));
}
try
{
this.underlyingFactory.scheduleOnReactorThread(this.sendWork);
}
catch (IOException ioException)
{
onSendFuture.completeExceptionally(
new ServiceBusException(false, "Send failed while dispatching to Reactor, see cause for more details.", ioException));
}
return onSendFuture;
}
private CompletableFuture send(
final byte[] bytes,
final int arrayOffset,
final int messageFormat,
final CompletableFuture onSend,
final TimeoutTracker tracker)
{
return this.sendCore(bytes, arrayOffset, messageFormat, onSend, tracker, null, null, null);
}
private int getPayloadSize(Message msg)
{
if (msg == null || msg.getBody() == null)
{
return 0;
}
Data payloadSection = (Data) msg.getBody();
if (payloadSection == null)
{
return 0;
}
Binary payloadBytes = payloadSection.getValue();
if (payloadBytes == null)
{
return 0;
}
return payloadBytes.getLength();
}
private int getDataSerializedSize(Message amqpMessage)
{
if (amqpMessage == null)
{
return 0;
}
int payloadSize = this.getPayloadSize(amqpMessage);
// EventData - accepts only PartitionKey - which is a String & stuffed into MessageAnnotation
MessageAnnotations messageAnnotations = amqpMessage.getMessageAnnotations();
ApplicationProperties applicationProperties = amqpMessage.getApplicationProperties();
int annotationsSize = 0;
int applicationPropertiesSize = 0;
if (messageAnnotations != null)
{
for(Symbol value: messageAnnotations.getValue().keySet())
{
annotationsSize += Util.sizeof(value);
}
for(Object value: messageAnnotations.getValue().values())
{
annotationsSize += Util.sizeof(value);
}
}
if (applicationProperties != null)
{
for(Object value: applicationProperties.getValue().keySet())
{
applicationPropertiesSize += Util.sizeof(value);
}
for(Object value: applicationProperties.getValue().values())
{
applicationPropertiesSize += Util.sizeof(value);
}
}
return annotationsSize + applicationPropertiesSize + payloadSize;
}
public CompletableFuture send(final Iterable messages)
{
if (messages == null || IteratorUtil.sizeEquals(messages, 0))
{
throw new IllegalArgumentException("Sending Empty batch of messages is not allowed.");
}
Message firstMessage = messages.iterator().next();
if (IteratorUtil.sizeEquals(messages, 1))
{
return this.send(firstMessage);
}
// proton-j doesn't support multiple dataSections to be part of AmqpMessage
// here's the alternate approach provided by them: https://github.com/apache/qpid-proton/pull/54
Message batchMessage = Proton.message();
batchMessage.setMessageAnnotations(firstMessage.getMessageAnnotations());
byte[] bytes = new byte[ClientConstants.MAX_MESSAGE_LENGTH_BYTES];
int encodedSize = batchMessage.encode(bytes, 0, ClientConstants.MAX_MESSAGE_LENGTH_BYTES);
int byteArrayOffset = encodedSize;
for(Message amqpMessage: messages)
{
Message messageWrappedByData = Proton.message();
int payloadSize = this.getDataSerializedSize(amqpMessage);
int allocationSize = Math.min(payloadSize + ClientConstants.MAX_EVENTHUB_AMQP_HEADER_SIZE_BYTES, ClientConstants.MAX_MESSAGE_LENGTH_BYTES);
byte[] messageBytes = new byte[allocationSize];
int messageSizeBytes = amqpMessage.encode(messageBytes, 0, allocationSize);
messageWrappedByData.setBody(new Data(new Binary(messageBytes, 0, messageSizeBytes)));
try
{
encodedSize = messageWrappedByData.encode(bytes, byteArrayOffset, ClientConstants.MAX_MESSAGE_LENGTH_BYTES - byteArrayOffset - 1);
}
catch(BufferOverflowException exception)
{
final CompletableFuture sendTask = new CompletableFuture();
sendTask.completeExceptionally(new PayloadSizeExceededException(String.format("Size of the payload exceeded Maximum message size: %s kb", ClientConstants.MAX_MESSAGE_LENGTH_BYTES / 1024), exception));
return sendTask;
}
byteArrayOffset = byteArrayOffset + encodedSize;
}
return this.send(bytes, byteArrayOffset, AmqpConstants.AMQP_BATCH_MESSAGE_FORMAT);
}
public CompletableFuture send(Message msg)
{
int payloadSize = this.getDataSerializedSize(msg);
int allocationSize = Math.min(payloadSize + ClientConstants.MAX_EVENTHUB_AMQP_HEADER_SIZE_BYTES, ClientConstants.MAX_MESSAGE_LENGTH_BYTES);
byte[] bytes = new byte[allocationSize];
int encodedSize = 0;
try
{
encodedSize = msg.encode(bytes, 0, allocationSize);
}
catch(BufferOverflowException exception)
{
final CompletableFuture sendTask = new CompletableFuture();
sendTask.completeExceptionally(new PayloadSizeExceededException(String.format("Size of the payload exceeded Maximum message size: %s kb", ClientConstants.MAX_MESSAGE_LENGTH_BYTES / 1024), exception));
return sendTask;
}
return this.send(bytes, encodedSize, DeliveryImpl.DEFAULT_MESSAGE_FORMAT);
}
@Override
public void onOpenComplete(Exception completionException)
{
if (completionException == null)
{
this.openLinkTracker = null;
this.lastKnownLinkError = null;
this.retryPolicy.resetRetryCount(this.getClientId());
if (!this.linkFirstOpen.isDone())
{
this.linkFirstOpen.complete(this);
}
else
{
synchronized (this.pendingSendLock)
{
if (!this.pendingSendsData.isEmpty())
{
LinkedList unacknowledgedSends = new LinkedList();
unacknowledgedSends.addAll(this.pendingSendsData.keySet());
if (unacknowledgedSends.size() > 0)
{
Iterator reverseReader = unacknowledgedSends.iterator();
while (reverseReader.hasNext())
{
String unacknowledgedSend = reverseReader.next();
if (this.pendingSendsData.get(unacknowledgedSend).isWaitingForAck())
{
this.pendingSends.offer(new WeightedDeliveryTag(unacknowledgedSend, 1));
}
}
}
unacknowledgedSends.clear();
}
}
}
}
else
{
if (!this.linkFirstOpen.isDone())
{
this.setClosed();
ExceptionUtil.completeExceptionally(this.linkFirstOpen, completionException, this);
}
}
}
@Override
public void onClose(ErrorCondition condition)
{
Exception completionException = condition != null ? ExceptionUtil.toException(condition)
: new ServiceBusException(ClientConstants.DEFAULT_IS_TRANSIENT,
"The entity has been close due to transient failures (underlying link closed), please retry the operation.");
this.onError(completionException);
}
@Override
public void onError(Exception completionException)
{
this.linkCredit = 0;
if (this.getIsClosingOrClosed())
{
synchronized (this.pendingSendLock)
{
for (Map.Entry> pendingSend: this.pendingSendsData.entrySet())
{
ExceptionUtil.completeExceptionally(pendingSend.getValue().getWork(),
completionException == null
? new OperationCancelledException("Send cancelled as the Sender instance is Closed before the sendOperation completed.")
: completionException,
this);
}
this.pendingSendsData.clear();
this.pendingSends.clear();
}
this.linkClose.complete(null);
return;
}
else
{
this.lastKnownLinkError = completionException;
this.lastKnownErrorReportedAt = Instant.now();
this.onOpenComplete(completionException);
if (completionException != null &&
(!(completionException instanceof ServiceBusException) || !((ServiceBusException) completionException).getIsTransient()))
{
synchronized (this.pendingSendLock)
{
for (Map.Entry> pendingSend: this.pendingSendsData.entrySet())
{
this.cleanupFailedSend(pendingSend.getValue(), completionException);
}
this.pendingSendsData.clear();
this.pendingSends.clear();
}
}
else
{
final Map.Entry> pendingSendEntry = IteratorUtil.getFirst(this.pendingSendsData.entrySet());
if (pendingSendEntry != null & pendingSendEntry.getValue() != null)
{
final TimeoutTracker tracker = pendingSendEntry.getValue().getTimeoutTracker();
if (tracker != null)
{
final Duration nextRetryInterval = this.retryPolicy.getNextRetryInterval(this.getClientId(), completionException, tracker.remaining());
if (nextRetryInterval != null)
{
try
{
this.underlyingFactory.scheduleOnReactorThread((int) nextRetryInterval.toMillis(), new DispatchHandler()
{
@Override
public void onEvent()
{
if (sendLink.getLocalState() == EndpointState.CLOSED || sendLink.getRemoteState() == EndpointState.CLOSED)
{
createSendLink();
}
}
});
}
catch (IOException ignore)
{
}
}
}
}
}
}
}
@Override
public void onSendComplete(final Delivery delivery)
{
final DeliveryState outcome = delivery.getRemoteState();
final String deliveryTag = new String(delivery.getTag());
if (TRACE_LOGGER.isLoggable(Level.FINEST))
TRACE_LOGGER.log(Level.FINEST,
String.format(Locale.US, "path[%s], linkName[%s], deliveryTag[%s]", MessageSender.this.sendPath, this.sendLink.getName(), deliveryTag));
final ReplayableWorkItem pendingSendWorkItem = this.pendingSendsData.remove(deliveryTag);
if (pendingSendWorkItem != null)
{
if (outcome instanceof Accepted)
{
this.lastKnownLinkError = null;
this.retryPolicy.resetRetryCount(this.getClientId());
pendingSendWorkItem.getTimeoutTask().cancel(false);
pendingSendWorkItem.getWork().complete(null);
}
else if (outcome instanceof Rejected)
{
Rejected rejected = (Rejected) outcome;
ErrorCondition error = rejected.getError();
Exception exception = ExceptionUtil.toException(error);
if (ExceptionUtil.isGeneralSendError(error.getCondition()))
{
this.lastKnownLinkError = exception;
this.lastKnownErrorReportedAt = Instant.now();
}
Duration retryInterval = this.retryPolicy.getNextRetryInterval(
this.getClientId(), exception, pendingSendWorkItem.getTimeoutTracker().remaining());
if (retryInterval == null)
{
this.cleanupFailedSend(pendingSendWorkItem, exception);
}
else
{
pendingSendWorkItem.setLastKnownException(exception);
try
{
this.underlyingFactory.scheduleOnReactorThread((int) retryInterval.toMillis(),
new DispatchHandler()
{
@Override
public void onEvent()
{
MessageSender.this.reSend(deliveryTag, pendingSendWorkItem, false);
}
});
}
catch (IOException ioException)
{
exception.initCause(ioException);
this.cleanupFailedSend(
pendingSendWorkItem,
new ServiceBusException(false, "Send operation failed while scheduling a retry on Reactor, see cause for more details.", ioException));
}
}
}
else if (outcome instanceof Released)
{
this.cleanupFailedSend(pendingSendWorkItem, new OperationCancelledException(outcome.toString()));
}
else
{
this.cleanupFailedSend(pendingSendWorkItem, new ServiceBusException(false, outcome.toString()));
}
}
else
{
if (TRACE_LOGGER.isLoggable(Level.WARNING))
TRACE_LOGGER.log(Level.WARNING,
String.format(Locale.US, "path[%s], linkName[%s], delivery[%s] - mismatch", this.sendPath, this.sendLink.getName(), deliveryTag));
}
}
private void reSend(final String deliveryTag, final ReplayableWorkItem pendingSend, boolean reuseDeliveryTag)
{
if (pendingSend != null)
{
this.sendCore(pendingSend.getMessage(),
pendingSend.getEncodedMessageSize(),
pendingSend.getMessageFormat(),
pendingSend.getWork(),
pendingSend.getTimeoutTracker(),
reuseDeliveryTag ? deliveryTag : null,
pendingSend.getLastKnownException(),
pendingSend.getTimeoutTask());
}
}
private void cleanupFailedSend(final ReplayableWorkItem failedSend, final Exception exception)
{
if (failedSend.getTimeoutTask() != null)
failedSend.getTimeoutTask().cancel(false);
ExceptionUtil.completeExceptionally(failedSend.getWork(), exception, this);
}
private void createSendLink()
{
final Connection connection = this.underlyingFactory.getConnection();
final Session session = connection.session();
session.setOutgoingWindow(Integer.MAX_VALUE);
session.open();
BaseHandler.setHandler(session, new SessionHandler(sendPath));
final String sendLinkNamePrefix = StringUtil.getRandomString();
final String sendLinkName = !StringUtil.isNullOrEmpty(connection.getRemoteContainer()) ?
sendLinkNamePrefix.concat(TrackingUtil.TRACKING_ID_TOKEN_SEPARATOR).concat(connection.getRemoteContainer()) :
sendLinkNamePrefix;
final Sender sender = session.sender(sendLinkName);
final Target target = new Target();
target.setAddress(sendPath);
sender.setTarget(target);
final Source source = new Source();
sender.setSource(source);
sender.setSenderSettleMode(SenderSettleMode.UNSETTLED);
SendLinkHandler handler = new SendLinkHandler(MessageSender.this);
BaseHandler.setHandler(sender, handler);
this.underlyingFactory.registerForConnectionError(sender);
sender.open();
if (this.sendLink != null)
{
final Sender oldSender = this.sendLink;
this.underlyingFactory.deregisterForConnectionError(oldSender);
}
MessageSender.this.sendLink = sender;
}
// TODO: consolidate common-code written for timeouts in Sender/Receiver
private void initializeLinkOpen(TimeoutTracker timeout)
{
this.linkFirstOpen = new CompletableFuture();
// timer to signal a timeout if exceeds the operationTimeout on MessagingFactory
Timer.schedule(
new Runnable()
{
public void run()
{
if (!MessageSender.this.linkFirstOpen.isDone())
{
Exception operationTimedout = new TimeoutException(
String.format(Locale.US, "Open operation on SendLink(%s) on Entity(%s) timed out at %s.", MessageSender.this.sendLink.getName(), MessageSender.this.getSendPath(), ZonedDateTime.now().toString()),
MessageSender.this.lastKnownErrorReportedAt.isAfter(Instant.now().minusSeconds(ClientConstants.SERVER_BUSY_BASE_SLEEP_TIME_IN_SECS)) ? MessageSender.this.lastKnownLinkError : null);
if (TRACE_LOGGER.isLoggable(Level.WARNING))
{
TRACE_LOGGER.log(Level.WARNING,
String.format(Locale.US, "path[%s], linkName[%s], open call timedout", MessageSender.this.sendPath, MessageSender.this.sendLink.getName()),
operationTimedout);
}
ExceptionUtil.completeExceptionally(MessageSender.this.linkFirstOpen, operationTimedout, MessageSender.this);
}
}
}
, timeout.remaining()
, TimerType.OneTimeRun);
}
@Override
public ErrorContext getContext()
{
final boolean isLinkOpened = this.linkFirstOpen != null && this.linkFirstOpen.isDone();
final String referenceId = this.sendLink != null && this.sendLink.getRemoteProperties() != null && this.sendLink.getRemoteProperties().containsKey(ClientConstants.TRACKING_ID_PROPERTY)
? this.sendLink.getRemoteProperties().get(ClientConstants.TRACKING_ID_PROPERTY).toString()
: ((this.sendLink != null) ? this.sendLink.getName() : null);
SenderContext errorContext = new SenderContext(
this.underlyingFactory!=null ? this.underlyingFactory.getHostName() : null,
this.sendPath,
referenceId,
isLinkOpened && this.sendLink != null ? this.sendLink.getCredit() : null);
return errorContext;
}
@Override
public void onFlow(final int creditIssued)
{
this.lastKnownLinkError = null;
if (creditIssued <= 0)
return;
if (TRACE_LOGGER.isLoggable(Level.FINE))
{
int numberOfSendsWaitingforCredit = this.pendingSends.size();
TRACE_LOGGER.log(Level.FINE, String.format(Locale.US, "path[%s], linkName[%s], remoteLinkCredit[%s], pendingSendsWaitingForCredit[%s], pendingSendsWaitingDelivery[%s]",
this.sendPath, this.sendLink.getName(), creditIssued, numberOfSendsWaitingforCredit, this.pendingSendsData.size() - numberOfSendsWaitingforCredit));
}
this.linkCredit = this.linkCredit + creditIssued;
this.sendWork.onEvent();
}
private void recreateSendLink()
{
this.createSendLink();
this.retryPolicy.incrementRetryCount(MessageSender.this.getClientId());
}
// actual send on the SenderLink should happen only in this method & should run on Reactor Thread
private void processSendWork()
{
final Sender sendLinkCurrent = this.sendLink;
if (sendLinkCurrent.getLocalState() == EndpointState.CLOSED || sendLinkCurrent.getRemoteState() == EndpointState.CLOSED)
{
this.recreateSendLink();
return;
}
while (sendLinkCurrent != null
&& sendLinkCurrent.getLocalState() == EndpointState.ACTIVE && sendLinkCurrent.getRemoteState() == EndpointState.ACTIVE
&& this.linkCredit > 0)
{
final WeightedDeliveryTag deliveryTag;
final ReplayableWorkItem sendData;
synchronized (this.pendingSendLock)
{
deliveryTag = this.pendingSends.poll();
sendData = deliveryTag != null
? this.pendingSendsData.get(deliveryTag.getDeliveryTag())
: null;
}
if (sendData != null)
{
if (sendData.getWork() != null && sendData.getWork().isDone())
{
// CoreSend could enque Sends into PendingSends Queue and can fail the SendCompletableFuture
// (when It fails to schedule the ProcessSendWork on reactor Thread)
this.pendingSendsData.remove(sendData);
continue;
}
Delivery delivery = null;
boolean linkAdvance = false;
int sentMsgSize = 0;
Exception sendException = null;
try
{
delivery = sendLinkCurrent.delivery(deliveryTag.getDeliveryTag().getBytes());
delivery.setMessageFormat(sendData.getMessageFormat());
sentMsgSize = sendLinkCurrent.send(sendData.getMessage(), 0, sendData.getEncodedMessageSize());
assert sentMsgSize == sendData.getEncodedMessageSize() : "Contract of the ProtonJ library for Sender.Send API changed";
linkAdvance = sendLinkCurrent.advance();
}
catch(Exception exception)
{
sendException = exception;
}
if (linkAdvance)
{
this.linkCredit--;
ScheduledFuture> timeoutTask = Timer.schedule(new Runnable()
{
@Override
public void run()
{
if (!sendData.getWork().isDone())
{
MessageSender.this.pendingSendsData.remove(deliveryTag);
MessageSender.this.throwSenderTimeout(sendData.getWork(), sendData.getLastKnownException());
}
}
}, this.operationTimeout, TimerType.OneTimeRun);
sendData.setTimeoutTask(timeoutTask);
sendData.setWaitingForAck();
}
else
{
if (TRACE_LOGGER.isLoggable(Level.FINE))
{
TRACE_LOGGER.log(Level.FINE,
String.format(Locale.US, "path[%s], linkName[%s], deliveryTag[%s], sentMessageSize[%s], payloadActualSize[%s] - sendlink advance failed",
this.sendPath, this.sendLink.getName(), deliveryTag, sentMsgSize, sendData.getEncodedMessageSize()));
}
if (delivery != null)
{
delivery.free();
}
sendData.getWork().completeExceptionally(
sendException != null
? new OperationCancelledException("Send operation failed. Please see cause for more details", sendException)
: new OperationCancelledException(
String.format(Locale.US, "Send operation failed while advancing delivery(tag: %s) on SendLink(path: %s).", this.sendPath, deliveryTag)));
}
}
else
{
if (deliveryTag != null)
{
if (TRACE_LOGGER.isLoggable(Level.SEVERE))
{
TRACE_LOGGER.log(Level.SEVERE,
String.format(Locale.US, "path[%s], linkName[%s], deliveryTag[%s] - sendData not found for this delivery.",
this.sendPath, this.sendLink.getName(), deliveryTag));
}
}
break;
}
}
}
private void throwSenderTimeout(CompletableFuture pendingSendWork, Exception lastKnownException)
{
Exception cause = lastKnownException;
if (lastKnownException == null && this.lastKnownLinkError != null)
{
boolean isServerBusy = ((this.lastKnownLinkError instanceof ServerBusyException)
&& (this.lastKnownErrorReportedAt.isAfter(Instant.now().minusSeconds(ClientConstants.SERVER_BUSY_BASE_SLEEP_TIME_IN_SECS))));
cause = isServerBusy || (this.lastKnownErrorReportedAt.isAfter(Instant.now().minusMillis(this.operationTimeout.toMillis())))
? this.lastKnownLinkError
: null;
}
boolean isClientSideTimeout = (cause == null || !(cause instanceof ServiceBusException));
ServiceBusException exception = isClientSideTimeout
? new TimeoutException(String.format(Locale.US, "%s %s %s.", MessageSender.SEND_TIMED_OUT, " at ", ZonedDateTime.now(), cause))
: (ServiceBusException) cause;
ExceptionUtil.completeExceptionally(pendingSendWork, exception, this);
}
private void scheduleLinkCloseTimeout(final TimeoutTracker timeout)
{
// timer to signal a timeout if exceeds the operationTimeout on MessagingFactory
Timer.schedule(
new Runnable()
{
public void run()
{
if (!linkClose.isDone())
{
Exception operationTimedout = new TimeoutException(String.format(Locale.US, "%s operation on Receive Link(%s) timed out at %s", "Close", MessageSender.this.sendLink.getName(), ZonedDateTime.now()));
if (TRACE_LOGGER.isLoggable(Level.WARNING))
{
TRACE_LOGGER.log(Level.WARNING,
String.format(Locale.US, "message recever(linkName: %s, path: %s) %s call timedout", MessageSender.this.sendLink.getName(), MessageSender.this.sendPath, "Close"),
operationTimedout);
}
ExceptionUtil.completeExceptionally(linkClose, operationTimedout, MessageSender.this);
}
}
}
, timeout.remaining()
, TimerType.OneTimeRun);
}
@Override
protected CompletableFuture onClose()
{
if (!this.getIsClosed())
{
if (this.sendLink != null && this.sendLink.getLocalState() != EndpointState.CLOSED)
{
this.sendLink.close();
this.scheduleLinkCloseTimeout(TimeoutTracker.create(this.operationTimeout));
}
else if (this.sendLink == null || this.sendLink.getRemoteState() == EndpointState.CLOSED)
{
this.linkClose.complete(null);
}
}
return this.linkClose;
}
private static class WeightedDeliveryTag
{
private final String deliveryTag;
private final int priority;
WeightedDeliveryTag(final String deliveryTag, final int priority)
{
this.deliveryTag = deliveryTag;
this.priority = priority;
}
public String getDeliveryTag()
{
return this.deliveryTag;
}
public int getPriority()
{
return this.priority;
}
}
private static class DeliveryTagComparator implements Comparator
{
@Override
public int compare(WeightedDeliveryTag deliveryTag0, WeightedDeliveryTag deliveryTag1)
{
return deliveryTag1.getPriority() - deliveryTag0.getPriority();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy