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.nio.BufferOverflowException;
import java.time.Duration;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
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.messaging.Accepted;
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.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 ITimeoutErrorHandler timeoutErrorHandler;
private final String sendPath;
private final Duration operationTimeout;
private final RetryPolicy retryPolicy;
private final Runnable operationTimer;
private final Duration timerTimeout;
private final CompletableFuture linkClose;
private ConcurrentHashMap> pendingSendsData;
private ConcurrentLinkedDeque pendingSendsWaitingForCredit;
private Sender sendLink;
private CompletableFuture linkFirstOpen;
private AtomicInteger linkCredit;
private TimeoutTracker openLinkTracker;
private boolean linkCreateScheduled;
private Object linkCreateLock;
private Exception lastKnownLinkError;
private Instant lastKnownErrorReportedAt;
private Object sendCall;
public static CompletableFuture create(
final MessagingFactory factory,
final String sendLinkName,
final String senderPath)
{
final MessageSender msgSender = new MessageSender(factory, factory, sendLinkName, senderPath);
msgSender.openLinkTracker = TimeoutTracker.create(factory.getOperationTimeout());
msgSender.initializeLinkOpen(msgSender.openLinkTracker);
msgSender.linkCreateScheduled = true;
Timer.schedule(new Runnable()
{
@Override
public void run()
{
msgSender.sendLink = msgSender.createSendLink();
}
}, Duration.ofSeconds(0), TimerType.OneTimeRun);
return msgSender.linkFirstOpen;
}
private MessageSender(final MessagingFactory factory, final ITimeoutErrorHandler timeoutErrorHandler, final String sendLinkName, final String senderPath)
{
super(sendLinkName, factory);
this.sendPath = senderPath;
this.underlyingFactory = factory;
this.timeoutErrorHandler = timeoutErrorHandler;
this.operationTimeout = factory.getOperationTimeout();
this.timerTimeout = this.operationTimeout.getSeconds() > 1 ? this.operationTimeout.dividedBy(3) : this.operationTimeout;
this.lastKnownLinkError = null;
this.lastKnownErrorReportedAt = Instant.EPOCH;
this.retryPolicy = factory.getRetryPolicy();
this.pendingSendsData = new ConcurrentHashMap>();
this.pendingSendsWaitingForCredit = new ConcurrentLinkedDeque();
new AtomicLong(0);
this.linkCredit = new AtomicInteger(0);
this.linkCreateLock = new Object();
this.sendCall = new Object();
this.linkClose = new CompletableFuture();
this.operationTimer = new Runnable()
{
@Override
public void run()
{
if (MessageSender.this.pendingSendsData != null)
{
Iterator>> pendingDeliveries = MessageSender.this.pendingSendsData.entrySet().iterator();
while(pendingDeliveries.hasNext())
{
Entry> pendingSend = pendingDeliveries.next();
if (pendingSend == null)
{
break;
}
ReplayableWorkItem pendingSendWork = pendingSend.getValue();
if (pendingSendWork.getTimeoutTracker().remaining().compareTo(ClientConstants.TIMER_TOLERANCE) < 0)
{
pendingSendsWaitingForCredit.remove(pendingSend.getKey());
pendingDeliveries.remove();
if (TRACE_LOGGER.isLoggable(Level.FINE))
{
TRACE_LOGGER.log(Level.FINE,
String.format(Locale.US,
"path[%s], linkName[%s], deliveryTag[%s] - send timedout", MessageSender.this.sendPath, MessageSender.this.sendLink.getName(), pendingSend.getKey()));
}
MessageSender.this.throwSenderTimeout(pendingSendWork.getWork(), pendingSendWork.getLastKnownException());
}
}
}
}
};
}
public String getSendPath()
{
return this.sendPath;
}
private CompletableFuture send(byte[] bytes, int arrayOffset, int messageFormat)
{
return this.send(bytes, arrayOffset, messageFormat, null, null);
}
// contract:
// 1. actual send on the SenderLink should happen only in this method
// 2. If there is any PendingSend waiting for Service to sendCreditFLow
// - this will not Send - & only Enqueue's the message
// - except if the msgToBeSent is the PendingSend waiting for Credit
private CompletableFuture sendCore(
final byte[] bytes,
final int arrayOffset,
final int messageFormat,
final CompletableFuture onSend,
final TimeoutTracker tracker,
final String deliveryTag,
final Exception lastKnownError)
{
this.throwIfClosed(this.lastKnownLinkError);
if (tracker != null && onSend != null && (tracker.remaining().isNegative() || tracker.remaining().isZero()))
{
if (deliveryTag != null)
{
this.pendingSendsData.remove(deliveryTag);
}
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));
}
this.throwSenderTimeout(onSend, null);
return onSend;
}
final String tag = (deliveryTag == null) ? UUID.randomUUID().toString().replace("-", StringUtil.EMPTY) : deliveryTag;
boolean messageSent = false;
Delivery dlv = null;
int sentMsgSize = 0;
synchronized (this.sendCall)
{
if (this.sendLink != null && this.sendLink.getLocalState() != EndpointState.CLOSED && this.sendLink.getRemoteState() != EndpointState.CLOSED
&& this.linkCredit.get() > 0
&& (this.pendingSendsWaitingForCredit.isEmpty() || this.pendingSendsWaitingForCredit.contains(tag)))
{
this.linkCredit.decrementAndGet();
dlv = this.sendLink.delivery(tag.getBytes());
dlv.setMessageFormat(messageFormat);
sentMsgSize = this.sendLink.send(bytes, 0, arrayOffset);
assert sentMsgSize == arrayOffset : "Contract of the ProtonJ library for Sender.Send API changed";
if (this.sendLink.advance())
messageSent = true;
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(), tag, sentMsgSize, arrayOffset));
}
dlv.free();
}
}
}
if (!messageSent)
{
if (!this.pendingSendsWaitingForCredit.contains(tag))
{
if (onSend != null)
this.pendingSendsWaitingForCredit.offerFirst(tag);
else
this.pendingSendsWaitingForCredit.offer(tag);
}
}
else
{
this.pendingSendsWaitingForCredit.remove(tag);
if (TRACE_LOGGER.isLoggable(Level.FINEST))
{
TRACE_LOGGER.log(Level.FINEST,
String.format(Locale.US, "path[%s], linkName[%s], deliveryTag[%s], deliverySettled[%s], sentMessageSize[%s], payloadActualSize[%s]",
this.sendPath, this.sendLink.getName(), tag, dlv.isSettled(), sentMsgSize, arrayOffset));
}
}
CompletableFuture onSendFuture = (onSend == null) ? new CompletableFuture() : onSend;
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);
}
this.pendingSendsData.put(tag, sendWaiterData);
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);
}
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 msgAnnotations = amqpMessage.getMessageAnnotations();
if (msgAnnotations == null)
{
return payloadSize;
}
int annotationsSize = 0;
for(Object value: msgAnnotations.getValue().values())
{
annotationsSize += value.toString().length();
}
return annotationsSize + 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)
{
synchronized(this.linkCreateLock)
{
this.linkCreateScheduled = false;
}
if (completionException == null)
{
this.openLinkTracker = null;
this.lastKnownLinkError = null;
this.retryPolicy.resetRetryCount(this.getClientId());
// assumption here is that TimeoutErrorHandler reset's the link
this.timeoutErrorHandler.resetTimeoutErrorTracking();
if (!this.linkFirstOpen.isDone())
{
this.linkFirstOpen.complete(this);
Timer.schedule(this.operationTimer, this.timerTimeout, TimerType.RepeatRun);
}
else if (!this.pendingSendsData.isEmpty())
{
LinkedList unacknowledgedSends = new LinkedList();
unacknowledgedSends.addAll(this.pendingSendsData.keySet());
if (unacknowledgedSends.size() > 0)
{
Iterator reverseReader = unacknowledgedSends.descendingIterator();
while (reverseReader.hasNext())
{
String unacknowledgedSend = reverseReader.next();
if (!this.pendingSendsWaitingForCredit.contains(unacknowledgedSend))
{
this.pendingSendsWaitingForCredit.addFirst(unacknowledgedSend);
}
}
}
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)
{
if (this.getIsClosingOrClosed())
{
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.pendingSendsWaitingForCredit.clear();
this.linkClose.complete(null);
return;
}
else
{
this.lastKnownLinkError = completionException;
this.lastKnownErrorReportedAt = Instant.now();
if (this.sendLink.getLocalState() != EndpointState.CLOSED)
{
this.sendLink.close();
}
this.onOpenComplete(completionException);
if (!this.getIsClosingOrClosed())
this.scheduleRecreate(Duration.ofSeconds(0));
}
}
private void scheduleRecreate(Duration runAfter)
{
synchronized(this.linkCreateLock)
{
if (this.linkCreateScheduled)
{
return;
}
this.linkCreateScheduled = true;
Timer.schedule(
new Runnable()
{
@Override
public void run()
{
if (MessageSender.this.sendLink.getLocalState() != EndpointState.CLOSED)
{
return;
}
Sender oldSender = MessageSender.this.sendLink;
Sender sender = MessageSender.this.createSendLink();
if (sender != null)
{
MessageSender.this.underlyingFactory.deregisterForConnectionError(oldSender);
MessageSender.this.sendLink = sender;
}
else
{
synchronized (MessageSender.this.linkCreateLock)
{
MessageSender.this.linkCreateScheduled = false;
}
}
MessageSender.this.retryPolicy.incrementRetryCount(MessageSender.this.getClientId());
}
},
runAfter,
TimerType.OneTimeRun);
}
}
@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));
ReplayableWorkItem pendingSendWorkItem = this.pendingSendsData.get(deliveryTag);
if (pendingSendWorkItem != null)
{
CompletableFuture pendingSend = pendingSendWorkItem.getWork();
if (outcome instanceof Accepted)
{
this.lastKnownLinkError = null;
this.retryPolicy.resetRetryCount(this.getClientId());
this.timeoutErrorHandler.resetTimeoutErrorTracking();
this.pendingSendsData.remove(deliveryTag);
pendingSend.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.pendingSendsData.remove(deliveryTag);
ExceptionUtil.completeExceptionally(pendingSend, exception, this);
}
else
{
pendingSendWorkItem.setLastKnownException(exception);
Timer.schedule(new Runnable()
{
@Override
public void run()
{
MessageSender.this.reSend(deliveryTag, false);
}
}, retryInterval, TimerType.OneTimeRun);
}
}
else if (outcome instanceof Released)
{
ExceptionUtil.completeExceptionally(pendingSend, new OperationCancelledException(outcome.toString()), this);
}
else
{
this.pendingSendsData.remove(deliveryTag);
ExceptionUtil.completeExceptionally(pendingSend, new ServiceBusException(false, outcome.toString()), this);
}
}
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));
}
synchronized (this.sendCall)
{
delivery.settle();
}
}
private void reSend(final String deliveryTag, boolean reuseDeliveryTag)
{
ReplayableWorkItem pendingSend = this.pendingSendsData.remove(deliveryTag);
if (pendingSend != null)
{
this.sendCore(pendingSend.getMessage(),
pendingSend.getEncodedMessageSize(),
pendingSend.getMessageFormat(),
pendingSend.getWork(),
pendingSend.getTimeoutTracker(),
reuseDeliveryTag ? deliveryTag : null,
pendingSend.getLastKnownException());
}
}
private Sender createSendLink()
{
Connection connection = null;
try
{
connection = this.underlyingFactory.getConnection().get(this.operationTimeout.getSeconds(), TimeUnit.SECONDS);
}
catch (InterruptedException|ExecutionException exception)
{
Throwable throwable = exception.getCause();
if (throwable != null && throwable instanceof Exception)
{
this.onError((Exception) throwable);
}
if (exception instanceof InterruptedException)
{
Thread.currentThread().interrupt();
}
return null;
}
catch (java.util.concurrent.TimeoutException exception)
{
this.onError(new TimeoutException("Connection creation timed out.", exception));
return null;
}
Session session = connection.session();
session.setOutgoingWindow(Integer.MAX_VALUE);
session.open();
BaseHandler.setHandler(session, new SessionHandler(this.sendPath));
String sendLinkName = StringUtil.getRandomString();
sendLinkName = sendLinkName.concat(TrackingUtil.TRACKING_ID_TOKEN_SEPARATOR).concat(connection.getRemoteContainer());
Sender sender = session.sender(sendLinkName);
Target target = new Target();
target.setAddress(this.sendPath);
sender.setTarget(target);
Source source = new Source();
sender.setSource(source);
sender.setSenderSettleMode(SenderSettleMode.UNSETTLED);
SendLinkHandler handler = new SendLinkHandler(this);
BaseHandler.setHandler(sender, handler);
this.underlyingFactory.registerForConnectionError(sender);
sender.open();
return 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.getCredit() : null);
return errorContext;
}
@Override
public void onFlow()
{
this.timeoutErrorHandler.resetTimeoutErrorTracking();
this.lastKnownLinkError = null;
int updatedCredit = 0;
synchronized (this.sendCall)
{
updatedCredit = this.sendLink.getRemoteCredit();
}
if (updatedCredit <= 0)
return;
if (TRACE_LOGGER.isLoggable(Level.FINE))
{
int numberOfSendsWaitingforCredit = this.pendingSendsWaitingForCredit.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(), updatedCredit, numberOfSendsWaitingforCredit, this.pendingSendsData.size() - numberOfSendsWaitingforCredit));
}
this.linkCredit.addAndGet(updatedCredit);
while (!this.pendingSendsWaitingForCredit.isEmpty() && this.linkCredit.get() > 0)
{
final String deliveryTag = this.pendingSendsWaitingForCredit.peek();
if (deliveryTag != null)
{
this.reSend(deliveryTag, true);
}
}
}
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;
if (isClientSideTimeout)
{
this.timeoutErrorHandler.reportTimeoutError();
}
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;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy