Fixes several issues in the reactor related components (#411)

This pull request contains the following changes.

1) Finish pending tasks when recreating the reactor and make sure pending calls scheduled on the old reactor get complete.
2) Fix the session open timeout issue which can result in NPE in proton-J engine.
3) Make session open timeout configurable and use the value of OperationTimeout.
4) Update the message of exceptions and include an entity name in the exception message.
5) API change - use ScheduledExecutorService.
6) Improve tracing.
This commit is contained in:
SJ 2018-12-21 00:30:59 -08:00 коммит произвёл GitHub
Родитель 8f92153e12
Коммит bf8e6c1561
36 изменённых файлов: 557 добавлений и 397 удалений

Просмотреть файл

@ -42,11 +42,11 @@ For a simple event consumer, you'll need to import the *com.microsoft.azure.even
Event Hubs client library uses qpid proton reactor framework which exposes AMQP connection and message delivery related Event Hubs client library uses qpid proton reactor framework which exposes AMQP connection and message delivery related
state transitions as reactive events. In the process, state transitions as reactive events. In the process,
the library will need to run many asynchronous tasks while sending and receiving messages to Event Hubs. the library will need to run many asynchronous tasks while sending and receiving messages to Event Hubs.
So, `EventHubClient` requires an instance of `Executor`, where all these tasks are run. So, `EventHubClient` requires an instance of `ScheduledExecutorService`, where all these tasks are run.
```Java ```Java
ExecutorService executor = Executors.newCachedThreadPool(); ScheduledExecutorService executor = Executors.newScheduledThreadPool(8)
``` ```
The receiver code creates an *EventHubClient* from a given connecting string The receiver code creates an *EventHubClient* from a given connecting string

Просмотреть файл

@ -36,11 +36,11 @@ which is quite simple in a Maven build [as we explain in the guide](PublishingEv
Event Hubs client library uses qpid proton reactor framework which exposes AMQP connection and message delivery related Event Hubs client library uses qpid proton reactor framework which exposes AMQP connection and message delivery related
state transitions as reactive events. In the process, state transitions as reactive events. In the process,
the library will need to run many asynchronous tasks while sending and receiving messages to Event Hubs. the library will need to run many asynchronous tasks while sending and receiving messages to Event Hubs.
So, `EventHubClient` requires an instance of `Executor`, where all these tasks are run. So, `EventHubClient` requires an instance of `ScheduledExecutorService`, where all these tasks are run.
```Java ```Java
ExecutorService executor = Executors.newCachedThreadPool(); ScheduledExecutorService executor = Executors.newScheduledThreadPool(8)
``` ```
Using an Event Hub connection string, which holds all required connection information, including an authorization key or token, Using an Event Hub connection string, which holds all required connection information, including an authorization key or token,

Просмотреть файл

@ -34,7 +34,7 @@ So, `EventHubClient` requires an instance of `Executor`, where all these tasks a
```Java ```Java
ExecutorService executor = Executors.newCachedThreadPool(); ScheduledExecutorService executor = Executors.newScheduledThreadPool(8)
``` ```
Using an Event Hub connection string, which holds all required connection information including an authorization key or token Using an Event Hub connection string, which holds all required connection information including an authorization key or token

Просмотреть файл

@ -27,7 +27,7 @@ class PartitionManager extends Closable {
private ScheduledFuture<?> scanFuture = null; private ScheduledFuture<?> scanFuture = null;
PartitionManager(HostContext hostContext) { PartitionManager(HostContext hostContext) {
super(null); super(null);
this.hostContext = hostContext; this.hostContext = hostContext;
} }
@ -41,17 +41,17 @@ class PartitionManager extends Closable {
// EventHubException or IOException, in addition to whatever failures may occur when the result of // EventHubException or IOException, in addition to whatever failures may occur when the result of
// the CompletableFuture is evaluated. // the CompletableFuture is evaluated.
try { try {
final CompletableFuture<Void> cleanupFuture = new CompletableFuture<Void>(); final CompletableFuture<Void> cleanupFuture = new CompletableFuture<Void>();
// Stage 0A: get EventHubClient for the event hub // Stage 0A: get EventHubClient for the event hub
retval = EventHubClient.create(this.hostContext.getEventHubConnectionString(), this.hostContext.getRetryPolicy(), this.hostContext.getExecutor()) retval = EventHubClient.create(this.hostContext.getEventHubConnectionString(), this.hostContext.getRetryPolicy(), this.hostContext.getExecutor())
// Stage 0B: set up a way to close the EventHubClient when we're done // Stage 0B: set up a way to close the EventHubClient when we're done
.thenApplyAsync((ehClient) -> .thenApplyAsync((ehClient) ->
{ {
final EventHubClient saveForCleanupClient = ehClient; final EventHubClient saveForCleanupClient = ehClient;
cleanupFuture.thenComposeAsync((empty) -> saveForCleanupClient.close(), this.hostContext.getExecutor()); cleanupFuture.thenComposeAsync((empty) -> saveForCleanupClient.close(), this.hostContext.getExecutor());
return ehClient; return ehClient;
}, this.hostContext.getExecutor()) }, this.hostContext.getExecutor())
// Stage 1: use the client to get runtime info for the event hub // Stage 1: use the client to get runtime info for the event hub
.thenComposeAsync((ehClient) -> ehClient.getRuntimeInformation(), this.hostContext.getExecutor()) .thenComposeAsync((ehClient) -> ehClient.getRuntimeInformation(), this.hostContext.getExecutor())
// Stage 2: extract the partition ids from the runtime info or throw on null (timeout) // Stage 2: extract the partition ids from the runtime info or throw on null (timeout)
@ -71,7 +71,7 @@ class PartitionManager extends Closable {
// Stage 3: RUN REGARDLESS OF EXCEPTIONS -- if there was an error, wrap it in IllegalEntityException and throw // Stage 3: RUN REGARDLESS OF EXCEPTIONS -- if there was an error, wrap it in IllegalEntityException and throw
.handleAsync((empty, e) -> .handleAsync((empty, e) ->
{ {
cleanupFuture.complete(null); // trigger client cleanup cleanupFuture.complete(null); // trigger client cleanup
if (e != null) { if (e != null) {
Throwable notifyWith = e; Throwable notifyWith = e;
if (e instanceof CompletionException) { if (e instanceof CompletionException) {
@ -104,8 +104,8 @@ class PartitionManager extends Closable {
} }
CompletableFuture<Void> stopPartitions() { CompletableFuture<Void> stopPartitions() {
setClosing(); setClosing();
// If the lease scanner is between runs, cancel so it doesn't run again. // If the lease scanner is between runs, cancel so it doesn't run again.
synchronized (this.scanFutureSynchronizer) { synchronized (this.scanFutureSynchronizer) {
if (this.scanFuture != null) { if (this.scanFuture != null) {
@ -119,20 +119,20 @@ class PartitionManager extends Closable {
if (this.pumpManager != null) { if (this.pumpManager != null) {
TRACE_LOGGER.info(this.hostContext.withHost("Shutting down all pumps")); TRACE_LOGGER.info(this.hostContext.withHost("Shutting down all pumps"));
stopping = this.pumpManager.removeAllPumps(CloseReason.Shutdown) stopping = this.pumpManager.removeAllPumps(CloseReason.Shutdown)
.whenCompleteAsync((empty, e) -> { .whenCompleteAsync((empty, e) -> {
if (e != null) { if (e != null) {
Throwable notifyWith = LoggingUtils.unwrapException(e, null); Throwable notifyWith = LoggingUtils.unwrapException(e, null);
TRACE_LOGGER.warn(this.hostContext.withHost("Failure during shutdown"), notifyWith); TRACE_LOGGER.warn(this.hostContext.withHost("Failure during shutdown"), notifyWith);
if (notifyWith instanceof Exception) { if (notifyWith instanceof Exception) {
this.hostContext.getEventProcessorOptions().notifyOfException(this.hostContext.getHostName(), (Exception) notifyWith, this.hostContext.getEventProcessorOptions().notifyOfException(this.hostContext.getHostName(), (Exception) notifyWith,
EventProcessorHostActionStrings.PARTITION_MANAGER_CLEANUP); EventProcessorHostActionStrings.PARTITION_MANAGER_CLEANUP);
} }
} }
}, this.hostContext.getExecutor()); }, this.hostContext.getExecutor());
} }
// else no pumps to shut down // else no pumps to shut down
stopping = stopping.whenCompleteAsync((empty, e) -> { stopping = stopping.whenCompleteAsync((empty, e) -> {
TRACE_LOGGER.info(this.hostContext.withHost("Partition manager exiting")); TRACE_LOGGER.info(this.hostContext.withHost("Partition manager exiting"));
setClosed(); setClosed();
@ -287,14 +287,14 @@ class PartitionManager extends Closable {
// Return Void so it can be called from a lambda. // Return Void so it can be called from a lambda.
// throwOnFailure is true // throwOnFailure is true
private Void scan(boolean isFirst) { private Void scan(boolean isFirst) {
TRACE_LOGGER.info(this.hostContext.withHost("Starting lease scan")); TRACE_LOGGER.debug(this.hostContext.withHost("Starting lease scan"));
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
(new PartitionScanner(this.hostContext, (lease) -> this.pumpManager.addPump(lease), this)).scan(isFirst) (new PartitionScanner(this.hostContext, (lease) -> this.pumpManager.addPump(lease), this)).scan(isFirst)
.whenCompleteAsync((didSteal, e) -> .whenCompleteAsync((didSteal, e) ->
{ {
TRACE_LOGGER.debug(this.hostContext.withHost("Scanning took " + (System.currentTimeMillis() - start))); TRACE_LOGGER.debug(this.hostContext.withHost("Scanning took " + (System.currentTimeMillis() - start)));
onPartitionCheckCompleteTestHook(); onPartitionCheckCompleteTestHook();
// Schedule the next scan unless we are shutting down. // Schedule the next scan unless we are shutting down.
@ -305,11 +305,11 @@ class PartitionManager extends Closable {
seconds = this.hostContext.getPartitionManagerOptions().getStartupScanDelayInSeconds(); seconds = this.hostContext.getPartitionManagerOptions().getStartupScanDelayInSeconds();
} }
synchronized (this.scanFutureSynchronizer) { synchronized (this.scanFutureSynchronizer) {
this.scanFuture = this.hostContext.getExecutor().schedule(() -> scan(false), seconds, TimeUnit.SECONDS); this.scanFuture = this.hostContext.getExecutor().schedule(() -> scan(false), seconds, TimeUnit.SECONDS);
} }
TRACE_LOGGER.debug(this.hostContext.withHost("Scheduling lease scanner in " + seconds)); TRACE_LOGGER.debug(this.hostContext.withHost("Scheduling lease scanner in " + seconds));
} else { } else {
TRACE_LOGGER.debug(this.hostContext.withHost("Not scheduling lease scanner due to shutdown")); TRACE_LOGGER.debug(this.hostContext.withHost("Not scheduling lease scanner due to shutdown"));
} }
}, this.hostContext.getExecutor()); }, this.hostContext.getExecutor());

Просмотреть файл

@ -165,7 +165,7 @@ class PartitionPump extends Closable implements PartitionReceiveHandler {
if (!getIsClosingOrClosed()) { if (!getIsClosingOrClosed()) {
int seconds = this.hostContext.getPartitionManagerOptions().getLeaseRenewIntervalInSeconds(); int seconds = this.hostContext.getPartitionManagerOptions().getLeaseRenewIntervalInSeconds();
this.leaseRenewerFuture = this.hostContext.getExecutor().schedule(() -> leaseRenewer(), seconds, TimeUnit.SECONDS); this.leaseRenewerFuture = this.hostContext.getExecutor().schedule(() -> leaseRenewer(), seconds, TimeUnit.SECONDS);
TRACE_LOGGER.info(this.hostContext.withHostAndPartition(this.lease, "scheduling leaseRenewer in " + seconds)); TRACE_LOGGER.debug(this.hostContext.withHostAndPartition(this.lease, "scheduling leaseRenewer in " + seconds));
} }
} }

Просмотреть файл

@ -7,35 +7,35 @@ package com.microsoft.azure.eventprocessorhost;
import org.junit.Assume; import org.junit.Assume;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
final class TestUtilities { final class TestUtilities {
static final ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor(); static final ScheduledExecutorService EXECUTOR_SERVICE = Executors.newScheduledThreadPool(1);
static void skipIfAppveyor() { static void skipIfAppveyor() {
String appveyor = System.getenv("APPVEYOR"); // Set to "true" by Appveyor String appveyor = System.getenv("APPVEYOR"); // Set to "true" by Appveyor
if (appveyor != null) { if (appveyor != null) {
TestBase.logInfo("SKIPPING - APPVEYOR DETECTED"); TestBase.logInfo("SKIPPING - APPVEYOR DETECTED");
} }
Assume.assumeTrue(appveyor == null); Assume.assumeTrue(appveyor == null);
} }
static String getStorageConnectionString() { static String getStorageConnectionString() {
TestUtilities.skipIfAppveyor(); TestUtilities.skipIfAppveyor();
String retval = System.getenv("EPHTESTSTORAGE"); String retval = System.getenv("EPHTESTSTORAGE");
// if EPHTESTSTORAGE is not set - we cannot run integration tests // if EPHTESTSTORAGE is not set - we cannot run integration tests
if (retval == null) { if (retval == null) {
TestBase.logInfo("SKIPPING - NO STORAGE CONNECTION STRING"); TestBase.logInfo("SKIPPING - NO STORAGE CONNECTION STRING");
} }
Assume.assumeTrue(retval != null); Assume.assumeTrue(retval != null);
return ((retval != null) ? retval : ""); return ((retval != null) ? retval : "");
} }
static Boolean isRunningOnAzure() { static Boolean isRunningOnAzure() {
return (System.getenv("EVENT_HUB_CONNECTION_STRING") != null); return (System.getenv("EVENT_HUB_CONNECTION_STRING") != null);
} }
} }

Просмотреть файл

@ -9,13 +9,13 @@ import com.microsoft.azure.eventhubs.EventHubClient;
import com.microsoft.azure.eventhubs.EventHubException; import com.microsoft.azure.eventhubs.EventHubException;
import org.apache.logging.log4j.core.appender.AbstractManager; import org.apache.logging.log4j.core.appender.AbstractManager;
import java.io.*; import java.io.IOException;
import java.util.*; import java.util.LinkedList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
public final class EventHubsManager extends AbstractManager { public final class EventHubsManager extends AbstractManager {
private static final ExecutorService EXECUTOR_SERVICE = Executors.newCachedThreadPool(); private static final ScheduledExecutorService EXECUTOR_SERVICE = Executors.newScheduledThreadPool(1);
private final String eventHubConnectionString; private final String eventHubConnectionString;
private EventHubClient eventHubSender; private EventHubClient eventHubSender;

Просмотреть файл

@ -4,12 +4,12 @@
*/ */
package com.microsoft.azure.eventhubs; package com.microsoft.azure.eventhubs;
import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService;
/** /**
* Authorization failed exception is thrown when error is encountered during authorizing user's permission to run the intended operations. * Authorization failed exception is thrown when error is encountered during authorizing user's permission to run the intended operations.
* When encountered this exception user should check whether the token/key provided in the connection string (e.g. one passed to * When encountered this exception user should check whether the token/key provided in the connection string (e.g. one passed to
* {@link EventHubClient#create(String, Executor)}) is valid, and has correct execution right for the intended operations (e.g. * {@link EventHubClient#create(String, ScheduledExecutorService)}) is valid, and has correct execution right for the intended operations (e.g.
* Receive call will need Listen claim associated with the key/token). * Receive call will need Listen claim associated with the key/token).
* *
* @see <a href="http://go.microsoft.com/fwlink/?LinkId=761101">http://go.microsoft.com/fwlink/?LinkId=761101</a> * @see <a href="http://go.microsoft.com/fwlink/?LinkId=761101">http://go.microsoft.com/fwlink/?LinkId=761101</a>

Просмотреть файл

@ -12,7 +12,7 @@ import java.io.Serializable;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.time.Instant; import java.time.Instant;
import java.util.*; import java.util.*;
import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService;
/** /**
* The data structure encapsulating the Event being sent-to and received-from EventHubs. * The data structure encapsulating the Event being sent-to and received-from EventHubs.
@ -48,7 +48,7 @@ public interface EventData extends Serializable {
* *
* @param data the actual payload of data in bytes to be Sent to EventHubs. * @param data the actual payload of data in bytes to be Sent to EventHubs.
* @return EventData the created {@link EventData} to send to EventHubs. * @return EventData the created {@link EventData} to send to EventHubs.
* @see EventHubClient#create(String, Executor) * @see EventHubClient#create(String, ScheduledExecutorService)
*/ */
static EventData create(final byte[] data) { static EventData create(final byte[] data) {
return new EventDataImpl(data); return new EventDataImpl(data);
@ -72,7 +72,7 @@ public interface EventData extends Serializable {
* @param offset Offset in the byte[] to read from ; inclusive index * @param offset Offset in the byte[] to read from ; inclusive index
* @param length length of the byte[] to be read, starting from offset * @param length length of the byte[] to be read, starting from offset
* @return EventData the created {@link EventData} to send to EventHubs. * @return EventData the created {@link EventData} to send to EventHubs.
* @see EventHubClient#create(String, Executor) * @see EventHubClient#create(String, ScheduledExecutorService)
*/ */
static EventData create(final byte[] data, final int offset, final int length) { static EventData create(final byte[] data, final int offset, final int length) {
return new EventDataImpl(data, offset, length); return new EventDataImpl(data, offset, length);
@ -94,7 +94,7 @@ public interface EventData extends Serializable {
* *
* @param buffer ByteBuffer which references the payload of the Event to be sent to EventHubs * @param buffer ByteBuffer which references the payload of the Event to be sent to EventHubs
* @return EventData the created {@link EventData} to send to EventHubs. * @return EventData the created {@link EventData} to send to EventHubs.
* @see EventHubClient#create(String, Executor) * @see EventHubClient#create(String, ScheduledExecutorService)
*/ */
static EventData create(final ByteBuffer buffer) { static EventData create(final ByteBuffer buffer) {
return new EventDataImpl(buffer); return new EventDataImpl(buffer);

Просмотреть файл

@ -11,41 +11,42 @@ import java.io.IOException;
import java.nio.channels.UnresolvedAddressException; import java.nio.channels.UnresolvedAddressException;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
/** /**
* Anchor class - all EventHub client operations STARTS here. * Anchor class - all EventHub client operations STARTS here.
* *
* @see EventHubClient#create(String, Executor) * @see EventHubClient#create(String, ScheduledExecutorService)
*/ */
public interface EventHubClient { public interface EventHubClient {
String DEFAULT_CONSUMER_GROUP_NAME = "$Default"; String DEFAULT_CONSUMER_GROUP_NAME = "$Default";
/** /**
* Synchronous version of {@link #create(String, Executor)}. * Synchronous version of {@link #create(String, ScheduledExecutorService)}.
* *
* @param connectionString The connection string to be used. See {@link ConnectionStringBuilder} to construct a connectionString. * @param connectionString The connection string to be used. See {@link ConnectionStringBuilder} to construct a connectionString.
* @param executor An {@link Executor} to run all tasks performed by {@link EventHubClient}. * @param executor An {@link ScheduledExecutorService} to run all tasks performed by {@link EventHubClient}.
* @return EventHubClient which can be used to create Senders and Receivers to EventHub * @return EventHubClient which can be used to create Senders and Receivers to EventHub
* @throws EventHubException If Service Bus service encountered problems during connection creation. * @throws EventHubException If Service Bus service encountered problems during connection creation.
* @throws IOException If the underlying Proton-J layer encounter network errors. * @throws IOException If the underlying Proton-J layer encounter network errors.
*/ */
static EventHubClient createSync(final String connectionString, final Executor executor) static EventHubClient createSync(final String connectionString, final ScheduledExecutorService executor)
throws EventHubException, IOException { throws EventHubException, IOException {
return EventHubClient.createSync(connectionString, null, executor); return EventHubClient.createSync(connectionString, null, executor);
} }
/** /**
* Synchronous version of {@link #create(String, Executor)}. * Synchronous version of {@link #create(String, ScheduledExecutorService)}.
* *
* @param connectionString The connection string to be used. See {@link ConnectionStringBuilder} to construct a connectionString. * @param connectionString The connection string to be used. See {@link ConnectionStringBuilder} to construct a connectionString.
* @param retryPolicy A custom {@link RetryPolicy} to be used when communicating with EventHub. * @param retryPolicy A custom {@link RetryPolicy} to be used when communicating with EventHub.
* @param executor An {@link Executor} to run all tasks performed by {@link EventHubClient}. * @param executor An {@link ScheduledExecutorService} to run all tasks performed by {@link EventHubClient}.
* @return EventHubClient which can be used to create Senders and Receivers to EventHub * @return EventHubClient which can be used to create Senders and Receivers to EventHub
* @throws EventHubException If Service Bus service encountered problems during connection creation. * @throws EventHubException If Service Bus service encountered problems during connection creation.
* @throws IOException If the underlying Proton-J layer encounter network errors. * @throws IOException If the underlying Proton-J layer encounter network errors.
*/ */
static EventHubClient createSync(final String connectionString, final RetryPolicy retryPolicy, final Executor executor) static EventHubClient createSync(final String connectionString, final RetryPolicy retryPolicy, final ScheduledExecutorService executor)
throws EventHubException, IOException { throws EventHubException, IOException {
return ExceptionUtil.syncWithIOException(() -> create(connectionString, retryPolicy, executor).get()); return ExceptionUtil.syncWithIOException(() -> create(connectionString, retryPolicy, executor).get());
} }
@ -56,12 +57,12 @@ public interface EventHubClient {
* <p>The {@link EventHubClient} created from this method creates a Sender instance internally, which is used by the {@link #send(EventData)} methods. * <p>The {@link EventHubClient} created from this method creates a Sender instance internally, which is used by the {@link #send(EventData)} methods.
* *
* @param connectionString The connection string to be used. See {@link ConnectionStringBuilder} to construct a connectionString. * @param connectionString The connection string to be used. See {@link ConnectionStringBuilder} to construct a connectionString.
* @param executor An {@link Executor} to run all tasks performed by {@link EventHubClient}. * @param executor An {@link ScheduledExecutorService} to run all tasks performed by {@link EventHubClient}.
* @return CompletableFuture{@literal <EventHubClient>} which can be used to create Senders and Receivers to EventHub * @return CompletableFuture{@literal <EventHubClient>} which can be used to create Senders and Receivers to EventHub
* @throws EventHubException If Service Bus service encountered problems during connection creation. * @throws EventHubException If Service Bus service encountered problems during connection creation.
* @throws IOException If the underlying Proton-J layer encounter network errors. * @throws IOException If the underlying Proton-J layer encounter network errors.
*/ */
static CompletableFuture<EventHubClient> create(final String connectionString, final Executor executor) static CompletableFuture<EventHubClient> create(final String connectionString, final ScheduledExecutorService executor)
throws EventHubException, IOException { throws EventHubException, IOException {
return EventHubClient.create(connectionString, null, executor); return EventHubClient.create(connectionString, null, executor);
} }
@ -73,13 +74,13 @@ public interface EventHubClient {
* *
* @param connectionString The connection string to be used. See {@link ConnectionStringBuilder} to construct a connectionString. * @param connectionString The connection string to be used. See {@link ConnectionStringBuilder} to construct a connectionString.
* @param retryPolicy A custom {@link RetryPolicy} to be used when communicating with EventHub. * @param retryPolicy A custom {@link RetryPolicy} to be used when communicating with EventHub.
* @param executor An {@link Executor} to run all tasks performed by {@link EventHubClient}. * @param executor An {@link ScheduledExecutorService} to run all tasks performed by {@link EventHubClient}.
* @return CompletableFuture{@literal <EventHubClient>} which can be used to create Senders and Receivers to EventHub * @return CompletableFuture{@literal <EventHubClient>} which can be used to create Senders and Receivers to EventHub
* @throws EventHubException If Service Bus service encountered problems during connection creation. * @throws EventHubException If Service Bus service encountered problems during connection creation.
* @throws IOException If the underlying Proton-J layer encounter network errors. * @throws IOException If the underlying Proton-J layer encounter network errors.
*/ */
static CompletableFuture<EventHubClient> create( static CompletableFuture<EventHubClient> create(
final String connectionString, final RetryPolicy retryPolicy, final Executor executor) final String connectionString, final RetryPolicy retryPolicy, final ScheduledExecutorService executor)
throws EventHubException, IOException { throws EventHubException, IOException {
return EventHubClientImpl.create(connectionString, retryPolicy, executor); return EventHubClientImpl.create(connectionString, retryPolicy, executor);
} }

Просмотреть файл

@ -7,14 +7,14 @@ package com.microsoft.azure.eventhubs;
import com.microsoft.azure.eventhubs.impl.ExceptionUtil; import com.microsoft.azure.eventhubs.impl.ExceptionUtil;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService;
/** /**
* This sender class is a logical representation of sending events to a specific EventHub partition. Do not use this class * This sender class is a logical representation of sending events to a specific EventHub partition. Do not use this class
* if you do not care about sending events to specific partitions. Instead, use {@link EventHubClient#send} method. * if you do not care about sending events to specific partitions. Instead, use {@link EventHubClient#send} method.
* *
* @see EventHubClient#createPartitionSender(String) * @see EventHubClient#createPartitionSender(String)
* @see EventHubClient#create(String, Executor) * @see EventHubClient#create(String, ScheduledExecutorService)
*/ */
public interface PartitionSender { public interface PartitionSender {

Просмотреть файл

@ -25,7 +25,7 @@ public class BaseLinkHandler extends BaseHandler {
public void onLinkLocalClose(Event event) { public void onLinkLocalClose(Event event) {
final Link link = event.getLink(); final Link link = event.getLink();
if (TRACE_LOGGER.isInfoEnabled()) { if (TRACE_LOGGER.isInfoEnabled()) {
TRACE_LOGGER.info(String.format("linkName[%s]", link.getName())); TRACE_LOGGER.info(String.format("onLinkLocalClose linkName[%s]", link.getName()));
} }
closeSession(link); closeSession(link);
@ -33,18 +33,30 @@ public class BaseLinkHandler extends BaseHandler {
@Override @Override
public void onLinkRemoteClose(Event event) { public void onLinkRemoteClose(Event event) {
final Link link = event.getLink();
if (TRACE_LOGGER.isInfoEnabled()) {
TRACE_LOGGER.info(String.format("onLinkRemoteClose linkName[%s]", link.getName()));
}
handleRemoteLinkClosed(event); handleRemoteLinkClosed(event);
} }
@Override @Override
public void onLinkRemoteDetach(Event event) { public void onLinkRemoteDetach(Event event) {
final Link link = event.getLink();
if (TRACE_LOGGER.isInfoEnabled()) {
TRACE_LOGGER.info(String.format("onLinkRemoteDetach linkName[%s]", link.getName()));
}
handleRemoteLinkClosed(event); handleRemoteLinkClosed(event);
} }
public void processOnClose(Link link, ErrorCondition condition) { public void processOnClose(Link link, ErrorCondition condition) {
if (TRACE_LOGGER.isInfoEnabled()) { if (TRACE_LOGGER.isInfoEnabled()) {
TRACE_LOGGER.info("linkName[" + link.getName() + TRACE_LOGGER.info(String.format("processOnClose linkName[%s], errorCondition[%s], errorDescription[%s]",
(condition != null ? "], ErrorCondition[" + condition.getCondition() + ", " + condition.getDescription() + "]" : "], condition[null]")); link.getName(),
condition != null ? condition.getCondition() : "n/a",
condition != null ? condition.getDescription() : "n/a"));
} }
this.underlyingEntity.onClose(condition); this.underlyingEntity.onClose(condition);

Просмотреть файл

@ -31,7 +31,6 @@ public final class ClientConstants {
public final static Duration TOKEN_VALIDITY = Duration.ofMinutes(20); public final static Duration TOKEN_VALIDITY = Duration.ofMinutes(20);
public final static int DEFAULT_MAX_RETRY_COUNT = 10; public final static int DEFAULT_MAX_RETRY_COUNT = 10;
public final static boolean DEFAULT_IS_TRANSIENT = true; public final static boolean DEFAULT_IS_TRANSIENT = true;
public final static int SESSION_OPEN_TIMEOUT_IN_MS = 15000;
public final static int REACTOR_IO_POLL_TIMEOUT = 20; public final static int REACTOR_IO_POLL_TIMEOUT = 20;
public final static int SERVER_BUSY_BASE_SLEEP_TIME_IN_SECS = 4; public final static int SERVER_BUSY_BASE_SLEEP_TIME_IN_SECS = 4;
public final static int MGMT_CHANNEL_MIN_RETRY_IN_MILLIS = 5; public final static int MGMT_CHANNEL_MIN_RETRY_IN_MILLIS = 5;

Просмотреть файл

@ -11,7 +11,7 @@ import org.slf4j.LoggerFactory;
import java.util.Locale; import java.util.Locale;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService;
/** /**
* Contract for all client entities with Open-Close/Abort state m/c * Contract for all client entities with Open-Close/Abort state m/c
@ -21,7 +21,7 @@ import java.util.concurrent.Executor;
abstract class ClientEntity { abstract class ClientEntity {
private static final Logger TRACE_LOGGER = LoggerFactory.getLogger(ClientEntity.class); private static final Logger TRACE_LOGGER = LoggerFactory.getLogger(ClientEntity.class);
protected final Executor executor; protected final ScheduledExecutorService executor;
private final String clientId; private final String clientId;
private final Object syncClose; private final Object syncClose;
private final ClientEntity parent; private final ClientEntity parent;
@ -29,7 +29,7 @@ abstract class ClientEntity {
private boolean isClosing; private boolean isClosing;
private boolean isClosed; private boolean isClosed;
protected ClientEntity(final String clientId, final ClientEntity parent, final Executor executor) { protected ClientEntity(final String clientId, final ClientEntity parent, final ScheduledExecutorService executor) {
this.clientId = clientId; this.clientId = clientId;
this.parent = parent; this.parent = parent;
this.executor = executor; this.executor = executor;

Просмотреть файл

@ -8,18 +8,14 @@ import com.microsoft.azure.eventhubs.TransportType;
import org.apache.qpid.proton.Proton; import org.apache.qpid.proton.Proton;
import org.apache.qpid.proton.amqp.Symbol; import org.apache.qpid.proton.amqp.Symbol;
import org.apache.qpid.proton.amqp.transport.ErrorCondition; import org.apache.qpid.proton.amqp.transport.ErrorCondition;
import org.apache.qpid.proton.engine.BaseHandler; import org.apache.qpid.proton.engine.*;
import org.apache.qpid.proton.engine.Connection;
import org.apache.qpid.proton.engine.EndpointState;
import org.apache.qpid.proton.engine.Event;
import org.apache.qpid.proton.engine.SslDomain;
import org.apache.qpid.proton.engine.Transport;
import org.apache.qpid.proton.engine.impl.TransportInternal; import org.apache.qpid.proton.engine.impl.TransportInternal;
import org.apache.qpid.proton.reactor.Handshaker; import org.apache.qpid.proton.reactor.Handshaker;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale;
import java.util.Map; import java.util.Map;
// ServiceBus <-> ProtonReactor interaction handles all // ServiceBus <-> ProtonReactor interaction handles all
@ -50,10 +46,6 @@ public class ConnectionHandler extends BaseHandler {
} }
} }
protected AmqpConnection getAmqpConnection() {
return this.amqpConnection;
}
private static SslDomain makeDomain(SslDomain.Mode mode) { private static SslDomain makeDomain(SslDomain.Mode mode) {
final SslDomain domain = Proton.sslDomain(); final SslDomain domain = Proton.sslDomain();
@ -64,14 +56,21 @@ public class ConnectionHandler extends BaseHandler {
return domain; return domain;
} }
protected AmqpConnection getAmqpConnection() {
return this.amqpConnection;
}
@Override @Override
public void onConnectionInit(Event event) { public void onConnectionInit(Event event) {
if (TRACE_LOGGER.isInfoEnabled()) {
TRACE_LOGGER.info(String.format(Locale.US, "onConnectionInit hostname[%s]", this.amqpConnection.getHostName()));
}
final Connection connection = event.getConnection(); final Connection connection = event.getConnection();
final String hostName = new StringBuilder(this.amqpConnection.getHostName()) final String hostName = new StringBuilder(this.amqpConnection.getHostName())
.append(":") .append(":")
.append(String.valueOf(this.getProtocolPort())) .append(String.valueOf(this.getProtocolPort()))
.toString(); .toString();
connection.setHostname(hostName); connection.setHostname(hostName);
connection.setContainer(StringUtil.getRandomString()); connection.setContainer(StringUtil.getRandomString());
@ -105,6 +104,7 @@ public class ConnectionHandler extends BaseHandler {
/** /**
* HostName to be used for socket creation. * HostName to be used for socket creation.
* for ex: in case of proxy server - this could be proxy ip address * for ex: in case of proxy server - this could be proxy ip address
*
* @return host name * @return host name
*/ */
public String getRemoteHostName() { public String getRemoteHostName() {
@ -114,6 +114,7 @@ public class ConnectionHandler extends BaseHandler {
/** /**
* port used to create socket. * port used to create socket.
* for ex: in case of talking to event hubs service via proxy - use proxy port * for ex: in case of talking to event hubs service via proxy - use proxy port
*
* @return port * @return port
*/ */
protected int getRemotePort() { protected int getRemotePort() {
@ -122,6 +123,7 @@ public class ConnectionHandler extends BaseHandler {
/** /**
* Port used on connection open frame * Port used on connection open frame
*
* @return port * @return port
*/ */
protected int getProtocolPort() { protected int getProtocolPort() {
@ -134,6 +136,9 @@ public class ConnectionHandler extends BaseHandler {
@Override @Override
public void onConnectionBound(Event event) { public void onConnectionBound(Event event) {
if (TRACE_LOGGER.isInfoEnabled()) {
TRACE_LOGGER.info(String.format(Locale.US, "onConnectionBound hostname[%s]", this.amqpConnection.getHostName()));
}
final Transport transport = event.getTransport(); final Transport transport = event.getTransport();
@ -145,8 +150,8 @@ public class ConnectionHandler extends BaseHandler {
final Connection connection = event.getConnection(); final Connection connection = event.getConnection();
if (TRACE_LOGGER.isInfoEnabled()) { if (TRACE_LOGGER.isInfoEnabled()) {
TRACE_LOGGER.info( TRACE_LOGGER.info(String.format(Locale.US, "onConnectionUnbound: hostname[%s], state[%s], remoteState[%s]",
"onConnectionUnbound: hostname[" + connection.getHostname() + "], state[" + connection.getLocalState() + "], remoteState[" + connection.getRemoteState() + "]"); connection.getHostname(), connection.getLocalState(), connection.getRemoteState()));
} }
// if failure happened while establishing transport - nothing to free up. // if failure happened while establishing transport - nothing to free up.
@ -162,7 +167,9 @@ public class ConnectionHandler extends BaseHandler {
final ErrorCondition condition = transport.getCondition(); final ErrorCondition condition = transport.getCondition();
if (TRACE_LOGGER.isWarnEnabled()) { if (TRACE_LOGGER.isWarnEnabled()) {
TRACE_LOGGER.warn("onTransportClosed: hostname[" + (connection != null ? connection.getHostname() : "n/a") + "], error[" + (condition != null ? condition.getDescription() : "n/a") + "]"); TRACE_LOGGER.warn(String.format(Locale.US, "onTransportError: hostname[%s], error[%s]",
connection != null ? connection.getHostname() : "n/a",
condition != null ? condition.getDescription() : "n/a"));
} }
if (connection != null && connection.getRemoteState() != EndpointState.CLOSED) { if (connection != null && connection.getRemoteState() != EndpointState.CLOSED) {
@ -185,7 +192,8 @@ public class ConnectionHandler extends BaseHandler {
final ErrorCondition condition = transport.getCondition(); final ErrorCondition condition = transport.getCondition();
if (TRACE_LOGGER.isInfoEnabled()) { if (TRACE_LOGGER.isInfoEnabled()) {
TRACE_LOGGER.info("onTransportClosed: hostname[" + (connection != null ? connection.getHostname() : "n/a") + "], error[" + (condition != null ? condition.getDescription() : "n/a") + "]"); TRACE_LOGGER.info(String.format(Locale.US, "onTransportClosed: hostname[%s], error[%s]",
connection != null ? connection.getHostname() : "n/a", (condition != null ? condition.getDescription() : "n/a")));
} }
if (connection != null && connection.getRemoteState() != EndpointState.CLOSED) { if (connection != null && connection.getRemoteState() != EndpointState.CLOSED) {
@ -195,11 +203,25 @@ public class ConnectionHandler extends BaseHandler {
} }
} }
@Override
public void onConnectionLocalOpen(Event event) {
final Connection connection = event.getConnection();
final ErrorCondition error = connection.getCondition();
if (TRACE_LOGGER.isInfoEnabled()) {
TRACE_LOGGER.info(String.format(Locale.US, "onConnectionLocalOpen: hostname[%s], errorCondition[%s], errorDescription[%s]",
connection.getHostname(),
error != null ? error.getCondition() : "n/a",
error != null ? error.getDescription() : "n/a"));
}
}
@Override @Override
public void onConnectionRemoteOpen(Event event) { public void onConnectionRemoteOpen(Event event) {
if (TRACE_LOGGER.isInfoEnabled()) { if (TRACE_LOGGER.isInfoEnabled()) {
TRACE_LOGGER.info("onConnectionRemoteOpen: hostname[" + event.getConnection().getHostname() + ", " + event.getConnection().getRemoteContainer() + "]"); TRACE_LOGGER.info(String.format(Locale.US, "onConnectionRemoteOpen: hostname[%s], remoteContainer[%s]",
event.getConnection().getHostname(), event.getConnection().getRemoteContainer()));
} }
this.amqpConnection.onOpenComplete(null); this.amqpConnection.onOpenComplete(null);
@ -209,13 +231,13 @@ public class ConnectionHandler extends BaseHandler {
public void onConnectionLocalClose(Event event) { public void onConnectionLocalClose(Event event) {
final Connection connection = event.getConnection(); final Connection connection = event.getConnection();
final ErrorCondition error = connection.getCondition(); final ErrorCondition error = connection.getCondition();
if (TRACE_LOGGER.isInfoEnabled()) { if (TRACE_LOGGER.isInfoEnabled()) {
TRACE_LOGGER.info("onConnectionLocalClose: hostname[" + connection.getHostname() + TRACE_LOGGER.info(String.format(Locale.US, "onConnectionLocalClose: hostname[%s], errorCondition[%s], errorDescription[%s]",
(error != null connection.getHostname(),
? "], errorCondition[" + error.getCondition() + ", " + error.getDescription() + "]" error != null ? error.getCondition() : "n/a",
: "]")); error != null ? error.getDescription() : "n/a"));
} }
if (connection.getRemoteState() == EndpointState.CLOSED) { if (connection.getRemoteState() == EndpointState.CLOSED) {
@ -234,12 +256,25 @@ public class ConnectionHandler extends BaseHandler {
final ErrorCondition error = connection.getRemoteCondition(); final ErrorCondition error = connection.getRemoteCondition();
if (TRACE_LOGGER.isInfoEnabled()) { if (TRACE_LOGGER.isInfoEnabled()) {
TRACE_LOGGER.info("onConnectionRemoteClose: hostname[" + connection.getHostname() + TRACE_LOGGER.info(String.format(Locale.US, "onConnectionRemoteClose: hostname[%s], errorCondition[%s], errorDescription[%s]",
(error != null connection.getHostname(),
? "], errorCondition[" + error.getCondition() + ", " + error.getDescription() + "]" error != null ? error.getCondition() : "n/a",
: "]")); error != null ? error.getDescription() : "n/a"));
} }
this.amqpConnection.onConnectionError(error); this.amqpConnection.onConnectionError(error);
} }
@Override
public void onConnectionFinal(Event event) {
final Connection connection = event.getConnection();
final ErrorCondition error = connection.getCondition();
if (TRACE_LOGGER.isInfoEnabled()) {
TRACE_LOGGER.info(String.format(Locale.US, "onConnectionFinal: hostname[%s], errorCondition[%s], errorDescription[%s]",
connection.getHostname(),
error != null ? error.getCondition() : "n/a",
error != null ? error.getDescription() : "n/a"));
}
}
} }

Просмотреть файл

@ -4,15 +4,25 @@ import org.apache.qpid.proton.engine.Connection;
import org.apache.qpid.proton.engine.Event; import org.apache.qpid.proton.engine.Event;
import org.apache.qpid.proton.engine.Transport; import org.apache.qpid.proton.engine.Transport;
import org.apache.qpid.proton.reactor.impl.IOHandler; import org.apache.qpid.proton.reactor.impl.IOHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Locale;
public class CustomIOHandler extends IOHandler { public class CustomIOHandler extends IOHandler {
private static final Logger TRACE_LOGGER = LoggerFactory.getLogger(CustomIOHandler.class);
@Override @Override
public void onTransportClosed(Event event) { public void onTransportClosed(Event event) {
final Transport transport = event.getTransport(); final Transport transport = event.getTransport();
final Connection connection = event.getConnection(); final Connection connection = event.getConnection();
if (TRACE_LOGGER.isInfoEnabled()) {
TRACE_LOGGER.info(String.format(Locale.US, "onTransportClosed hostname[%s]",
(connection != null ? connection.getHostname() : "n/a")));
}
if (transport != null && connection != null && connection.getTransport() != null) { if (transport != null && connection != null && connection.getTransport() != null) {
transport.unbind(); transport.unbind();
} }

Просмотреть файл

@ -17,7 +17,7 @@ import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
@ -37,15 +37,15 @@ public final class EventHubClientImpl extends ClientEntity implements EventHubCl
private CompletableFuture<Void> createSender; private CompletableFuture<Void> createSender;
private EventHubClientImpl(final ConnectionStringBuilder connectionString, final Executor executor) { private EventHubClientImpl(final ConnectionStringBuilder connectionString, final ScheduledExecutorService executor) {
super(StringUtil.getRandomString(), null, executor); super("EventHubClientImpl".concat(StringUtil.getRandomString()), null, executor);
this.eventHubName = connectionString.getEventHubName(); this.eventHubName = connectionString.getEventHubName();
this.senderCreateSync = new Object(); this.senderCreateSync = new Object();
} }
public static CompletableFuture<EventHubClient> create( public static CompletableFuture<EventHubClient> create(
final String connectionString, final RetryPolicy retryPolicy, final Executor executor) final String connectionString, final RetryPolicy retryPolicy, final ScheduledExecutorService executor)
throws EventHubException, IOException { throws EventHubException, IOException {
final ConnectionStringBuilder connStr = new ConnectionStringBuilder(connectionString); final ConnectionStringBuilder connStr = new ConnectionStringBuilder(connectionString);
final EventHubClientImpl eventHubClient = new EventHubClientImpl(connStr, executor); final EventHubClientImpl eventHubClient = new EventHubClientImpl(connStr, executor);
@ -220,7 +220,7 @@ public final class EventHubClientImpl extends ClientEntity implements EventHubCl
if (!this.isSenderCreateStarted) { if (!this.isSenderCreateStarted) {
synchronized (this.senderCreateSync) { synchronized (this.senderCreateSync) {
if (!this.isSenderCreateStarted) { if (!this.isSenderCreateStarted) {
this.createSender = MessageSender.create(this.underlyingFactory, StringUtil.getRandomString(), this.eventHubName) this.createSender = MessageSender.create(this.underlyingFactory, this.getClientId().concat("-InternalSender"), this.eventHubName)
.thenAcceptAsync(new Consumer<MessageSender>() { .thenAcceptAsync(new Consumer<MessageSender>() {
public void accept(MessageSender a) { public void accept(MessageSender a) {
EventHubClientImpl.this.sender = a; EventHubClientImpl.this.sender = a;
@ -290,7 +290,7 @@ public final class EventHubClientImpl extends ClientEntity implements EventHubCl
(long) rawData.get(ClientConstants.MANAGEMENT_RESULT_LAST_ENQUEUED_SEQUENCE_NUMBER), (long) rawData.get(ClientConstants.MANAGEMENT_RESULT_LAST_ENQUEUED_SEQUENCE_NUMBER),
(String) rawData.get(ClientConstants.MANAGEMENT_RESULT_LAST_ENQUEUED_OFFSET), (String) rawData.get(ClientConstants.MANAGEMENT_RESULT_LAST_ENQUEUED_OFFSET),
((Date) rawData.get(ClientConstants.MANAGEMENT_RESULT_LAST_ENQUEUED_TIME_UTC)).toInstant(), ((Date) rawData.get(ClientConstants.MANAGEMENT_RESULT_LAST_ENQUEUED_TIME_UTC)).toInstant(),
(boolean)rawData.get(ClientConstants.MANAGEMENT_RESULT_PARTITION_IS_EMPTY))); (boolean) rawData.get(ClientConstants.MANAGEMENT_RESULT_PARTITION_IS_EMPTY)));
return future2; return future2;
} }
}, this.executor); }, this.executor);
@ -349,7 +349,7 @@ public final class EventHubClientImpl extends ClientEntity implements EventHubCl
public void run() { public void run() {
final long timeLeft = this.timeoutTracker.remaining().toMillis(); final long timeLeft = this.timeoutTracker.remaining().toMillis();
final CompletableFuture<Map<String, Object>> intermediateFuture = this.mf.getManagementChannel() final CompletableFuture<Map<String, Object>> intermediateFuture = this.mf.getManagementChannel()
.request(this.mf.getReactorScheduler(), .request(this.mf.getReactorDispatcher(),
this.request, this.request,
timeLeft > 0 ? timeLeft : 0); timeLeft > 0 ? timeLeft : 0);

Просмотреть файл

@ -114,7 +114,8 @@ public final class MessageReceiver extends ClientEntity implements AmqpReceiver,
if (TRACE_LOGGER.isInfoEnabled()) { if (TRACE_LOGGER.isInfoEnabled()) {
TRACE_LOGGER.info( TRACE_LOGGER.info(
String.format(Locale.US, String.format(Locale.US,
"path[%s], linkName[%s] - Reschedule operation timer, current: [%s], remaining: [%s] secs", "clientId[%s], path[%s], linkName[%s] - Reschedule operation timer, current: [%s], remaining: [%s] secs",
getClientId(),
receivePath, receivePath,
receiveLink.getName(), receiveLink.getName(),
Instant.now(), Instant.now(),
@ -142,7 +143,7 @@ public final class MessageReceiver extends ClientEntity implements AmqpReceiver,
public void run() { public void run() {
try { try {
underlyingFactory.getCBSChannel().sendToken( underlyingFactory.getCBSChannel().sendToken(
underlyingFactory.getReactorScheduler(), underlyingFactory.getReactorDispatcher(),
underlyingFactory.getTokenProvider().getToken(tokenAudience, ClientConstants.TOKEN_VALIDITY), underlyingFactory.getTokenProvider().getToken(tokenAudience, ClientConstants.TOKEN_VALIDITY),
tokenAudience, tokenAudience,
new OperationResult<Void, Exception>() { new OperationResult<Void, Exception>() {
@ -151,7 +152,8 @@ public final class MessageReceiver extends ClientEntity implements AmqpReceiver,
if (TRACE_LOGGER.isDebugEnabled()) { if (TRACE_LOGGER.isDebugEnabled()) {
TRACE_LOGGER.debug( TRACE_LOGGER.debug(
String.format(Locale.US, String.format(Locale.US,
"path[%s], linkName[%s] - token renewed", receivePath, receiveLink.getName())); "clientId[%s], path[%s], linkName[%s] - token renewed",
getClientId(), receivePath, receiveLink.getName()));
} }
} }
@ -160,7 +162,8 @@ public final class MessageReceiver extends ClientEntity implements AmqpReceiver,
if (TRACE_LOGGER.isInfoEnabled()) { if (TRACE_LOGGER.isInfoEnabled()) {
TRACE_LOGGER.info( TRACE_LOGGER.info(
String.format(Locale.US, String.format(Locale.US,
"path[%s], linkName[%s], tokenRenewalFailure[%s]", receivePath, receiveLink.getName(), error.getMessage())); "clientId[%s], path[%s], linkName[%s], tokenRenewalFailure[%s]",
getClientId(), receivePath, receiveLink.getName(), error.getMessage()));
} }
} }
}); });
@ -168,7 +171,8 @@ public final class MessageReceiver extends ClientEntity implements AmqpReceiver,
if (TRACE_LOGGER.isInfoEnabled()) { if (TRACE_LOGGER.isInfoEnabled()) {
TRACE_LOGGER.info( TRACE_LOGGER.info(
String.format(Locale.US, String.format(Locale.US,
"path[%s], linkName[%s], tokenRenewalScheduleFailure[%s]", receivePath, receiveLink.getName(), exception.getMessage())); "clientId[%s], path[%s], linkName[%s], tokenRenewalScheduleFailure[%s]",
getClientId(), receivePath, receiveLink.getName(), exception.getMessage()));
} }
} }
} }
@ -247,8 +251,8 @@ public final class MessageReceiver extends ClientEntity implements AmqpReceiver,
if (maxMessageCount <= 0 || maxMessageCount > this.prefetchCount) { if (maxMessageCount <= 0 || maxMessageCount > this.prefetchCount) {
onReceive.completeExceptionally(new IllegalArgumentException(String.format( onReceive.completeExceptionally(new IllegalArgumentException(String.format(
Locale.US, Locale.US,
"maxEventCount(%s) should be a positive number and should be less than prefetchCount(%s)", "Entity(%s): maxEventCount(%s) should be a positive number and should be less than prefetchCount(%s)",
maxMessageCount, this.prefetchCount))); this.receivePath, maxMessageCount, this.prefetchCount)));
return onReceive; return onReceive;
} }
@ -256,9 +260,10 @@ public final class MessageReceiver extends ClientEntity implements AmqpReceiver,
if (TRACE_LOGGER.isInfoEnabled()) { if (TRACE_LOGGER.isInfoEnabled()) {
TRACE_LOGGER.info( TRACE_LOGGER.info(
String.format(Locale.US, String.format(Locale.US,
"path[%s], linkName[%s] - schedule operation timer, current: [%s], remaining: [%s] secs", "clientId[%s], path[%s], linkName[%s] - schedule operation timer, current: [%s], remaining: [%s] secs",
receivePath, this.getClientId(),
receiveLink.getName(), this.receivePath,
this.receiveLink.getName(),
Instant.now(), Instant.now(),
this.receiveTimeout.getSeconds())); this.receiveTimeout.getSeconds()));
} }
@ -282,17 +287,16 @@ public final class MessageReceiver extends ClientEntity implements AmqpReceiver,
this.creatingLink = false; this.creatingLink = false;
if (exception == null) { if (exception == null) {
if (this.getIsClosingOrClosed()) {
this.receiveLink.close();
return;
}
if (this.linkOpen != null && !this.linkOpen.getWork().isDone()) { if (this.linkOpen != null && !this.linkOpen.getWork().isDone()) {
this.linkOpen.getWork().complete(this); this.linkOpen.getWork().complete(this);
if (this.openTimer != null) if (this.openTimer != null)
this.openTimer.cancel(false); this.openTimer.cancel(false);
} }
if (this.getIsClosingOrClosed()) {
return;
}
synchronized (this.errorConditionLock) { synchronized (this.errorConditionLock) {
this.lastKnownLinkError = null; this.lastKnownLinkError = null;
} }
@ -303,8 +307,8 @@ public final class MessageReceiver extends ClientEntity implements AmqpReceiver,
this.sendFlow(this.prefetchCount - this.prefetchedMessages.size()); this.sendFlow(this.prefetchCount - this.prefetchedMessages.size());
if (TRACE_LOGGER.isInfoEnabled()) { if (TRACE_LOGGER.isInfoEnabled()) {
TRACE_LOGGER.info(String.format("receiverPath[%s], linkname[%s], updated-link-credit[%s], sentCredits[%s]", TRACE_LOGGER.info(String.format("clientId[%s], receiverPath[%s], linkName[%s], updated-link-credit[%s], sentCredits[%s]",
this.receivePath, this.receiveLink.getName(), this.receiveLink.getCredit(), this.prefetchCount)); this.getClientId(), this.receivePath, this.receiveLink.getName(), this.receiveLink.getCredit(), this.prefetchCount));
} }
} else { } else {
synchronized (this.errorConditionLock) { synchronized (this.errorConditionLock) {
@ -333,8 +337,8 @@ public final class MessageReceiver extends ClientEntity implements AmqpReceiver,
} catch (IOException | RejectedExecutionException schedulerException) { } catch (IOException | RejectedExecutionException schedulerException) {
if (TRACE_LOGGER.isWarnEnabled()) { if (TRACE_LOGGER.isWarnEnabled()) {
TRACE_LOGGER.warn( TRACE_LOGGER.warn(
String.format(Locale.US, "receiverPath[%s], scheduling createLink encountered error: %s", String.format(Locale.US, "clientId[%s], receiverPath[%s], scheduling createLink encountered error: %s",
this.receivePath, schedulerException.getLocalizedMessage())); this.getClientId(), this.receivePath, schedulerException.getLocalizedMessage()));
} }
this.cancelOpen(schedulerException); this.cancelOpen(schedulerException);
@ -388,7 +392,8 @@ public final class MessageReceiver extends ClientEntity implements AmqpReceiver,
} }
final Exception completionException = exception == null final Exception completionException = exception == null
? new EventHubException(true, "Client encountered transient error for unknown reasons, please retry the operation.") ? new EventHubException(true, String.format(Locale.US,
"Entity(%s): client encountered transient error for unknown reasons, please retry the operation.", this.receivePath))
: exception; : exception;
this.onOpenComplete(completionException); this.onOpenComplete(completionException);
@ -415,8 +420,9 @@ public final class MessageReceiver extends ClientEntity implements AmqpReceiver,
recreateScheduled = false; recreateScheduled = false;
if (TRACE_LOGGER.isWarnEnabled()) { if (TRACE_LOGGER.isWarnEnabled()) {
TRACE_LOGGER.warn( TRACE_LOGGER.warn(
String.format(Locale.US, "receiverPath[%s], linkName[%s], scheduling createLink encountered error: %s", String.format(Locale.US, "clientId[%s], receiverPath[%s], linkName[%s], scheduling createLink encountered error: %s",
MessageReceiver.this.receivePath, this.getClientId(),
this.receivePath,
this.receiveLink.getName(), ignore.getLocalizedMessage())); this.receiveLink.getName(), ignore.getLocalizedMessage()));
} }
} }
@ -515,7 +521,7 @@ public final class MessageReceiver extends ClientEntity implements AmqpReceiver,
try { try {
this.underlyingFactory.getCBSChannel().sendToken( this.underlyingFactory.getCBSChannel().sendToken(
this.underlyingFactory.getReactorScheduler(), this.underlyingFactory.getReactorDispatcher(),
this.underlyingFactory.getTokenProvider().getToken(tokenAudience, ClientConstants.TOKEN_VALIDITY), this.underlyingFactory.getTokenProvider().getToken(tokenAudience, ClientConstants.TOKEN_VALIDITY),
tokenAudience, tokenAudience,
new OperationResult<Void, Exception>() { new OperationResult<Void, Exception>() {
@ -571,8 +577,8 @@ public final class MessageReceiver extends ClientEntity implements AmqpReceiver,
this.nextCreditToFlow = 0; this.nextCreditToFlow = 0;
if (TRACE_LOGGER.isDebugEnabled()) { if (TRACE_LOGGER.isDebugEnabled()) {
TRACE_LOGGER.debug(String.format("receiverPath[%s], linkname[%s], updated-link-credit[%s], sentCredits[%s], ThreadId[%s]", TRACE_LOGGER.debug(String.format("clientId[%s], receiverPath[%s], linkName[%s], updated-link-credit[%s], sentCredits[%s], ThreadId[%s]",
this.receivePath, this.receiveLink.getName(), this.receiveLink.getCredit(), tempFlow, Thread.currentThread().getId())); this.getClientId(), this.receivePath, this.receiveLink.getName(), this.receiveLink.getCredit(), tempFlow, Thread.currentThread().getId()));
} }
} }
} }
@ -594,10 +600,11 @@ public final class MessageReceiver extends ClientEntity implements AmqpReceiver,
String.format(Locale.US, "Open operation on entity(%s) timed out at %s.", String.format(Locale.US, "Open operation on entity(%s) timed out at %s.",
MessageReceiver.this.receivePath, ZonedDateTime.now()), MessageReceiver.this.receivePath, ZonedDateTime.now()),
lastReportedLinkError); lastReportedLinkError);
if (TRACE_LOGGER.isWarnEnabled()) { if (TRACE_LOGGER.isWarnEnabled()) {
TRACE_LOGGER.warn( TRACE_LOGGER.warn(
String.format(Locale.US, "receiverPath[%s], Open call timedout", MessageReceiver.this.receivePath), String.format(Locale.US, "clientId[%s], receiverPath[%s], Open call timed out",
operationTimedout); MessageReceiver.this.getClientId(), MessageReceiver.this.receivePath), operationTimedout);
} }
ExceptionUtil.completeExceptionally(linkOpen.getWork(), operationTimedout, MessageReceiver.this); ExceptionUtil.completeExceptionally(linkOpen.getWork(), operationTimedout, MessageReceiver.this);
@ -630,10 +637,13 @@ public final class MessageReceiver extends ClientEntity implements AmqpReceiver,
link = MessageReceiver.this.receiveLink; link = MessageReceiver.this.receiveLink;
} }
final Exception operationTimedout = new TimeoutException(String.format(Locale.US, "%s operation on Receive Link(%s) timed out at %s", "Close", link.getName(), ZonedDateTime.now())); final Exception operationTimedout = new TimeoutException(String.format(Locale.US, "Close operation on Receive Link(%s) timed out at %s",
link.getName(), ZonedDateTime.now()));
if (TRACE_LOGGER.isInfoEnabled()) { if (TRACE_LOGGER.isInfoEnabled()) {
TRACE_LOGGER.info( TRACE_LOGGER.info(
String.format(Locale.US, "receiverPath[%s], linkName[%s], %s call timedout", MessageReceiver.this.receivePath, link.getName(), "Close"), String.format(Locale.US, "clientId[%s], receiverPath[%s], linkName[%s], Close call timed out",
MessageReceiver.this.getClientId(), MessageReceiver.this.receivePath, link.getName()),
operationTimedout); operationTimedout);
} }
@ -708,8 +718,9 @@ public final class MessageReceiver extends ClientEntity implements AmqpReceiver,
if (receiveLink != null && receiveLink.getLocalState() != EndpointState.CLOSED) { if (receiveLink != null && receiveLink.getLocalState() != EndpointState.CLOSED) {
receiveLink.close(); receiveLink.close();
} else if (receiveLink == null || receiveLink.getRemoteState() == EndpointState.CLOSED) { } else if (receiveLink == null || receiveLink.getRemoteState() == EndpointState.CLOSED) {
if (closeTimer != null) if (closeTimer != null && !closeTimer.isCancelled()) {
closeTimer.cancel(false); closeTimer.cancel(false);
}
linkClose.complete(null); linkClose.complete(null);
} }

Просмотреть файл

@ -40,7 +40,8 @@ import java.util.function.Consumer;
public final class MessageSender extends ClientEntity implements AmqpSender, ErrorContextProvider { public final class MessageSender extends ClientEntity implements AmqpSender, ErrorContextProvider {
private static final Logger TRACE_LOGGER = LoggerFactory.getLogger(MessageSender.class); private static final Logger TRACE_LOGGER = LoggerFactory.getLogger(MessageSender.class);
private static final String SEND_TIMED_OUT = "Send operation timed out"; private static final String SEND_TIMED_OUT = "Send operation timed out";
// TestHooks for code injection
private static volatile Consumer<MessageSender> onOpenRetry = null;
private final MessagingFactory underlyingFactory; private final MessagingFactory underlyingFactory;
private final String sendPath; private final String sendPath;
private final Duration operationTimeout; private final Duration operationTimeout;
@ -54,7 +55,6 @@ public final class MessageSender extends ClientEntity implements AmqpSender, Err
private final String tokenAudience; private final String tokenAudience;
private final Object errorConditionLock; private final Object errorConditionLock;
private final Timer timer; private final Timer timer;
private volatile int maxMessageSize; private volatile int maxMessageSize;
private volatile Sender sendLink; private volatile Sender sendLink;
private volatile CompletableFuture<MessageSender> linkFirstOpen; private volatile CompletableFuture<MessageSender> linkFirstOpen;
@ -62,13 +62,9 @@ public final class MessageSender extends ClientEntity implements AmqpSender, Err
private volatile boolean creatingLink; private volatile boolean creatingLink;
private volatile CompletableFuture<?> closeTimer; private volatile CompletableFuture<?> closeTimer;
private volatile CompletableFuture<?> openTimer; private volatile CompletableFuture<?> openTimer;
private Exception lastKnownLinkError; private Exception lastKnownLinkError;
private Instant lastKnownErrorReportedAt; private Instant lastKnownErrorReportedAt;
// TestHooks for code injection
private static volatile Consumer<MessageSender> onOpenRetry = null;
private MessageSender(final MessagingFactory factory, final String sendLinkName, final String senderPath) { private MessageSender(final MessagingFactory factory, final String sendLinkName, final String senderPath) {
super(sendLinkName, factory, factory.executor); super(sendLinkName, factory, factory.executor);
@ -106,7 +102,7 @@ public final class MessageSender extends ClientEntity implements AmqpSender, Err
public void run() { public void run() {
try { try {
underlyingFactory.getCBSChannel().sendToken( underlyingFactory.getCBSChannel().sendToken(
underlyingFactory.getReactorScheduler(), underlyingFactory.getReactorDispatcher(),
underlyingFactory.getTokenProvider().getToken(tokenAudience, ClientConstants.TOKEN_VALIDITY), underlyingFactory.getTokenProvider().getToken(tokenAudience, ClientConstants.TOKEN_VALIDITY),
tokenAudience, tokenAudience,
new OperationResult<Void, Exception>() { new OperationResult<Void, Exception>() {
@ -114,7 +110,8 @@ public final class MessageSender extends ClientEntity implements AmqpSender, Err
public void onComplete(Void result) { public void onComplete(Void result) {
if (TRACE_LOGGER.isDebugEnabled()) { if (TRACE_LOGGER.isDebugEnabled()) {
TRACE_LOGGER.debug(String.format(Locale.US, TRACE_LOGGER.debug(String.format(Locale.US,
"path[%s], linkName[%s] - token renewed", sendPath, sendLink.getName())); "clientId[%s], path[%s], linkName[%s] - token renewed",
getClientId(), sendPath, sendLink.getName()));
} }
} }
@ -122,14 +119,16 @@ public final class MessageSender extends ClientEntity implements AmqpSender, Err
public void onError(Exception error) { public void onError(Exception error) {
if (TRACE_LOGGER.isInfoEnabled()) { if (TRACE_LOGGER.isInfoEnabled()) {
TRACE_LOGGER.info(String.format(Locale.US, TRACE_LOGGER.info(String.format(Locale.US,
"path[%s], linkName[%s] - tokenRenewalFailure[%s]", sendPath, sendLink.getName(), error.getMessage())); "clientId[%s], path[%s], linkName[%s] - tokenRenewalFailure[%s]",
getClientId(), sendPath, sendLink.getName(), error.getMessage()));
} }
} }
}); });
} catch (IOException | NoSuchAlgorithmException | InvalidKeyException | RuntimeException exception) { } catch (IOException | NoSuchAlgorithmException | InvalidKeyException | RuntimeException exception) {
if (TRACE_LOGGER.isWarnEnabled()) { if (TRACE_LOGGER.isWarnEnabled()) {
TRACE_LOGGER.warn(String.format(Locale.US, TRACE_LOGGER.warn(String.format(Locale.US,
"path[%s], linkName[%s] - tokenRenewalScheduleFailure[%s]", sendPath, sendLink.getName(), exception.getMessage())); "clientId[%s], path[%s], linkName[%s] - tokenRenewalScheduleFailure[%s]",
getClientId(), sendPath, sendLink.getName(), exception.getMessage()));
} }
} }
} }
@ -210,8 +209,9 @@ public final class MessageSender extends ClientEntity implements AmqpSender, Err
(unUsed, exception) -> { (unUsed, exception) -> {
if (exception != null && !(exception instanceof CancellationException)) if (exception != null && !(exception instanceof CancellationException))
onSendFuture.completeExceptionally( onSendFuture.completeExceptionally(
new OperationCancelledException("Send failed while dispatching to Reactor, see cause for more details.", new OperationCancelledException(String.format(Locale.US,
exception)); "Entity(%s): send failed while dispatching to Reactor, see cause for more details.",
this.sendPath), exception));
return null; return null;
}, this.executor); }, this.executor);
@ -230,7 +230,9 @@ public final class MessageSender extends ClientEntity implements AmqpSender, Err
this.underlyingFactory.scheduleOnReactorThread(this.sendWork); this.underlyingFactory.scheduleOnReactorThread(this.sendWork);
} catch (IOException | RejectedExecutionException schedulerException) { } catch (IOException | RejectedExecutionException schedulerException) {
onSendFuture.completeExceptionally( onSendFuture.completeExceptionally(
new OperationCancelledException("Send failed while dispatching to Reactor, see cause for more details.", schedulerException)); new OperationCancelledException(String.format(Locale.US,
"Entity(%s): send failed while dispatching to Reactor, see cause for more details.",
this.sendPath), schedulerException));
} }
return onSendFuture; return onSendFuture;
@ -247,7 +249,8 @@ public final class MessageSender extends ClientEntity implements AmqpSender, Err
public CompletableFuture<Void> send(final Iterable<Message> messages) { public CompletableFuture<Void> send(final Iterable<Message> messages) {
if (messages == null || IteratorUtil.sizeEquals(messages, 0)) { if (messages == null || IteratorUtil.sizeEquals(messages, 0)) {
throw new IllegalArgumentException("Sending Empty batch of messages is not allowed."); throw new IllegalArgumentException(String.format(Locale.US,
"Entity[%s}: sending Empty batch of messages is not allowed.", this.sendPath));
} }
final Message firstMessage = messages.iterator().next(); final Message firstMessage = messages.iterator().next();
@ -280,7 +283,9 @@ public final class MessageSender extends ClientEntity implements AmqpSender, Err
encodedSize = messageWrappedByData.encode(bytes, byteArrayOffset, maxMessageSizeTemp - byteArrayOffset - 1); encodedSize = messageWrappedByData.encode(bytes, byteArrayOffset, maxMessageSizeTemp - byteArrayOffset - 1);
} catch (BufferOverflowException exception) { } catch (BufferOverflowException exception) {
final CompletableFuture<Void> sendTask = new CompletableFuture<>(); final CompletableFuture<Void> sendTask = new CompletableFuture<>();
sendTask.completeExceptionally(new PayloadSizeExceededException(String.format("Size of the payload exceeded Maximum message size: %s kb", maxMessageSizeTemp / 1024), exception)); sendTask.completeExceptionally(new PayloadSizeExceededException(String.format(Locale.US,
"Entity(%s): size of the payload exceeded Maximum message size: %s kb",
this.sendPath, maxMessageSizeTemp / 1024), exception));
return sendTask; return sendTask;
} }
@ -302,7 +307,9 @@ public final class MessageSender extends ClientEntity implements AmqpSender, Err
encodedSize = msg.encode(bytes, 0, allocationSize); encodedSize = msg.encode(bytes, 0, allocationSize);
} catch (BufferOverflowException exception) { } catch (BufferOverflowException exception) {
final CompletableFuture<Void> sendTask = new CompletableFuture<Void>(); final CompletableFuture<Void> sendTask = new CompletableFuture<Void>();
sendTask.completeExceptionally(new PayloadSizeExceededException(String.format("Size of the payload exceeded Maximum message size: %s kb", maxMessageSizeTemp / 1024), exception)); sendTask.completeExceptionally(new PayloadSizeExceededException(String.format(Locale.US,
"Entity(%s): size of the payload exceeded Maximum message size: %s kb",
this.sendPath, maxMessageSizeTemp / 1024), exception));
return sendTask; return sendTask;
} }
@ -380,14 +387,14 @@ public final class MessageSender extends ClientEntity implements AmqpSender, Err
} catch (IOException | RejectedExecutionException schedulerException) { } catch (IOException | RejectedExecutionException schedulerException) {
if (TRACE_LOGGER.isWarnEnabled()) { if (TRACE_LOGGER.isWarnEnabled()) {
TRACE_LOGGER.warn( TRACE_LOGGER.warn(
String.format(Locale.US, "senderPath[%s], scheduling createLink encountered error: %s", String.format(Locale.US, "clientId[%s], senderPath[%s], scheduling createLink encountered error: %s",
this.sendPath, schedulerException.getLocalizedMessage())); this.getClientId(), this.sendPath, schedulerException.getLocalizedMessage()));
} }
this.cancelOpen(schedulerException); this.cancelOpen(schedulerException);
} }
} else if (completionException instanceof EventHubException } else if (completionException instanceof EventHubException
&& !((EventHubException) completionException).getIsTransient()){ && !((EventHubException) completionException).getIsTransient()) {
this.cancelOpen(completionException); this.cancelOpen(completionException);
} }
} }
@ -419,7 +426,9 @@ public final class MessageSender extends ClientEntity implements AmqpSender, Err
for (Map.Entry<String, ReplayableWorkItem<Void>> pendingSend : this.pendingSendsData.entrySet()) { for (Map.Entry<String, ReplayableWorkItem<Void>> pendingSend : this.pendingSendsData.entrySet()) {
ExceptionUtil.completeExceptionally(pendingSend.getValue().getWork(), ExceptionUtil.completeExceptionally(pendingSend.getValue().getWork(),
completionException == null completionException == null
? new OperationCancelledException("Send cancelled as the Sender instance is Closed before the sendOperation completed.") ? new OperationCancelledException(String.format(Locale.US,
"Entity(%s): send cancelled as the Sender instance is Closed before the sendOperation completed.",
this.sendPath))
: completionException, : completionException,
this); this);
} }
@ -438,7 +447,9 @@ public final class MessageSender extends ClientEntity implements AmqpSender, Err
} }
final Exception finalCompletionException = completionException == null final Exception finalCompletionException = completionException == null
? new EventHubException(true, "Client encountered transient error for unknown reasons, please retry the operation.") : completionException; ? new EventHubException(true, String.format(Locale.US,
"Entity(%s): client encountered transient error for unknown reasons, please retry the operation.",
this.sendPath)) : completionException;
this.onOpenComplete(finalCompletionException); this.onOpenComplete(finalCompletionException);
@ -489,7 +500,8 @@ public final class MessageSender extends ClientEntity implements AmqpSender, Err
TRACE_LOGGER.trace( TRACE_LOGGER.trace(
String.format( String.format(
Locale.US, Locale.US,
"path[%s], linkName[%s], deliveryTag[%s]", MessageSender.this.sendPath, this.sendLink.getName(), deliveryTag)); "clientId[%s], path[%s], linkName[%s], deliveryTag[%s]",
this.getClientId(), this.sendPath, this.sendLink.getName(), deliveryTag));
final ReplayableWorkItem<Void> pendingSendWorkItem = this.pendingSendsData.remove(deliveryTag); final ReplayableWorkItem<Void> pendingSendWorkItem = this.pendingSendsData.remove(deliveryTag);
@ -542,7 +554,10 @@ public final class MessageSender extends ClientEntity implements AmqpSender, Err
exception.initCause(schedulerException); exception.initCause(schedulerException);
this.cleanupFailedSend( this.cleanupFailedSend(
pendingSendWorkItem, pendingSendWorkItem,
new EventHubException(false, "Send operation failed while scheduling a retry on Reactor, see cause for more details.", schedulerException)); new EventHubException(false, String.format(Locale.US,
"Entity(%s): send operation failed while scheduling a retry on Reactor, see cause for more details.",
this.sendPath),
schedulerException));
} }
} }
} else if (outcome instanceof Released) { } else if (outcome instanceof Released) {
@ -553,7 +568,8 @@ public final class MessageSender extends ClientEntity implements AmqpSender, Err
} else { } else {
if (TRACE_LOGGER.isDebugEnabled()) if (TRACE_LOGGER.isDebugEnabled())
TRACE_LOGGER.debug( TRACE_LOGGER.debug(
String.format(Locale.US, "path[%s], linkName[%s], delivery[%s] - mismatch (or send timedout)", this.sendPath, this.sendLink.getName(), deliveryTag)); String.format(Locale.US, "clientId[%s]. path[%s], linkName[%s], delivery[%s] - mismatch (or send timed out)",
this.getClientId(), this.sendPath, this.sendLink.getName(), deliveryTag));
} }
} }
@ -613,7 +629,7 @@ public final class MessageSender extends ClientEntity implements AmqpSender, Err
try { try {
this.underlyingFactory.getCBSChannel().sendToken( this.underlyingFactory.getCBSChannel().sendToken(
this.underlyingFactory.getReactorScheduler(), this.underlyingFactory.getReactorDispatcher(),
this.underlyingFactory.getTokenProvider().getToken(tokenAudience, ClientConstants.TOKEN_VALIDITY), this.underlyingFactory.getTokenProvider().getToken(tokenAudience, ClientConstants.TOKEN_VALIDITY),
tokenAudience, tokenAudience,
new OperationResult<Void, Exception>() { new OperationResult<Void, Exception>() {
@ -670,7 +686,8 @@ public final class MessageSender extends ClientEntity implements AmqpSender, Err
if (TRACE_LOGGER.isWarnEnabled()) { if (TRACE_LOGGER.isWarnEnabled()) {
TRACE_LOGGER.warn( TRACE_LOGGER.warn(
String.format(Locale.US, "path[%s], open call timedout", MessageSender.this.sendPath), String.format(Locale.US, "clientId[%s], path[%s], open call timed out",
MessageSender.this.getClientId(), MessageSender.this.sendPath),
operationTimedout); operationTimedout);
} }
@ -724,8 +741,9 @@ public final class MessageSender extends ClientEntity implements AmqpSender, Err
if (TRACE_LOGGER.isDebugEnabled()) { if (TRACE_LOGGER.isDebugEnabled()) {
int numberOfSendsWaitingforCredit = this.pendingSends.size(); int numberOfSendsWaitingforCredit = this.pendingSends.size();
TRACE_LOGGER.debug(String.format(Locale.US, "path[%s], linkName[%s], remoteLinkCredit[%s], pendingSendsWaitingForCredit[%s], pendingSendsWaitingDelivery[%s]", TRACE_LOGGER.debug(String.format(Locale.US,
this.sendPath, this.sendLink.getName(), creditIssued, numberOfSendsWaitingforCredit, this.pendingSendsData.size() - numberOfSendsWaitingforCredit)); "clientId[%s], path[%s], linkName[%s], remoteLinkCredit[%s], pendingSendsWaitingForCredit[%s], pendingSendsWaitingDelivery[%s]",
this.getClientId(), this.sendPath, this.sendLink.getName(), creditIssued, numberOfSendsWaitingforCredit, this.pendingSendsData.size() - numberOfSendsWaitingforCredit));
} }
this.sendWork.onEvent(); this.sendWork.onEvent();
@ -791,8 +809,8 @@ public final class MessageSender extends ClientEntity implements AmqpSender, Err
} else { } else {
if (TRACE_LOGGER.isDebugEnabled()) { if (TRACE_LOGGER.isDebugEnabled()) {
TRACE_LOGGER.debug( TRACE_LOGGER.debug(
String.format(Locale.US, "path[%s], linkName[%s], deliveryTag[%s], sentMessageSize[%s], payloadActualSize[%s] - sendlink advance failed", String.format(Locale.US, "clientId[%s], path[%s], linkName[%s], deliveryTag[%s], sentMessageSize[%s], payloadActualSize[%s] - sendlink advance failed",
this.sendPath, this.sendLink.getName(), deliveryTag, sentMsgSize, sendData.getEncodedMessageSize())); this.getClientId(), this.sendPath, this.sendLink.getName(), deliveryTag, sentMsgSize, sendData.getEncodedMessageSize()));
} }
if (delivery != null) { if (delivery != null) {
@ -800,16 +818,17 @@ public final class MessageSender extends ClientEntity implements AmqpSender, Err
} }
sendData.getWork().completeExceptionally(sendException != null sendData.getWork().completeExceptionally(sendException != null
? new OperationCancelledException("Send operation failed. Please see cause for more details", sendException) ? new OperationCancelledException(String.format(Locale.US,
"Entity(%s): send operation failed. Please see cause for more details", this.sendPath), sendException)
: new OperationCancelledException( : new OperationCancelledException(
String.format(Locale.US, "Send operation failed while advancing delivery(tag: %s) on SendLink(path: %s).", this.sendPath, deliveryTag))); String.format(Locale.US, "Entity(%s): send operation failed while advancing delivery(tag: %s).", this.sendPath, deliveryTag)));
} }
} else { } else {
if (deliveryTag != null) { if (deliveryTag != null) {
if (TRACE_LOGGER.isDebugEnabled()) { if (TRACE_LOGGER.isDebugEnabled()) {
TRACE_LOGGER.debug( TRACE_LOGGER.debug(
String.format(Locale.US, "path[%s], linkName[%s], deliveryTag[%s] - sendData not found for this delivery.", String.format(Locale.US, "clientId[%s], path[%s], linkName[%s], deliveryTag[%s] - sendData not found for this delivery.",
this.sendPath, this.sendLink.getName(), deliveryTag)); this.getClientId(), this.sendPath, this.sendLink.getName(), deliveryTag));
} }
} }
@ -840,7 +859,8 @@ public final class MessageSender extends ClientEntity implements AmqpSender, Err
final boolean isClientSideTimeout = (cause == null || !(cause instanceof EventHubException)); final boolean isClientSideTimeout = (cause == null || !(cause instanceof EventHubException));
final EventHubException exception = isClientSideTimeout final EventHubException exception = isClientSideTimeout
? new TimeoutException(String.format(Locale.US, "%s %s %s.", MessageSender.SEND_TIMED_OUT, " at ", ZonedDateTime.now()), cause) ? new TimeoutException(String.format(Locale.US, "Entity(%s): %s at %s.",
this.sendPath, MessageSender.SEND_TIMED_OUT, ZonedDateTime.now()), cause)
: (EventHubException) cause; : (EventHubException) cause;
ExceptionUtil.completeExceptionally(pendingSendWork, exception, this); ExceptionUtil.completeExceptionally(pendingSendWork, exception, this);
@ -858,10 +878,11 @@ public final class MessageSender extends ClientEntity implements AmqpSender, Err
} }
final Exception operationTimedout = new TimeoutException(String.format(Locale.US, final Exception operationTimedout = new TimeoutException(String.format(Locale.US,
"%s operation on Sender Link(%s) timed out at %s", "Close", link.getName(), ZonedDateTime.now())); "Entity(%s): close operation timed out at %s", MessageSender.this.sendPath, ZonedDateTime.now()));
if (TRACE_LOGGER.isInfoEnabled()) { if (TRACE_LOGGER.isInfoEnabled()) {
TRACE_LOGGER.info( TRACE_LOGGER.info(
String.format(Locale.US, "message sender(linkName: %s, path: %s) %s call timedout", link.getName(), MessageSender.this.sendPath, "Close"), String.format(Locale.US, "clientId[%s], message sender(linkName: %s, path: %s) close call timed out",
MessageSender.this.getClientId(), link.getName(), MessageSender.this.sendPath),
operationTimedout); operationTimedout);
} }
@ -894,8 +915,9 @@ public final class MessageSender extends ClientEntity implements AmqpSender, Err
if (sendLink != null && sendLink.getLocalState() != EndpointState.CLOSED) { if (sendLink != null && sendLink.getLocalState() != EndpointState.CLOSED) {
sendLink.close(); sendLink.close();
} else if (sendLink == null || sendLink.getRemoteState() == EndpointState.CLOSED) { } else if (sendLink == null || sendLink.getRemoteState() == EndpointState.CLOSED) {
if (closeTimer != null) if (closeTimer != null && !closeTimer.isCancelled()) {
closeTimer.cancel(false); closeTimer.cancel(false);
}
linkClose.complete(null); linkClose.complete(null);
} }

Просмотреть файл

@ -5,16 +5,11 @@
package com.microsoft.azure.eventhubs.impl; package com.microsoft.azure.eventhubs.impl;
import com.microsoft.azure.eventhubs.*;
import com.microsoft.azure.eventhubs.TimeoutException;
import org.apache.qpid.proton.amqp.transport.ErrorCondition; import org.apache.qpid.proton.amqp.transport.ErrorCondition;
import org.apache.qpid.proton.engine.BaseHandler; import org.apache.qpid.proton.engine.*;
import org.apache.qpid.proton.engine.Connection;
import org.apache.qpid.proton.engine.Event;
import org.apache.qpid.proton.engine.EndpointState;
import org.apache.qpid.proton.engine.Handler;
import org.apache.qpid.proton.engine.HandlerException;
import org.apache.qpid.proton.engine.Link;
import org.apache.qpid.proton.reactor.Reactor; import org.apache.qpid.proton.reactor.Reactor;
import org.apache.qpid.proton.engine.Session;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -24,20 +19,10 @@ import java.time.Duration;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.concurrent.CancellationException; import java.util.concurrent.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
import com.microsoft.azure.eventhubs.ConnectionStringBuilder;
import com.microsoft.azure.eventhubs.CommunicationException;
import com.microsoft.azure.eventhubs.EventHubException;
import com.microsoft.azure.eventhubs.OperationCancelledException;
import com.microsoft.azure.eventhubs.RetryPolicy;
import com.microsoft.azure.eventhubs.TimeoutException;
/** /**
* Abstracts all amqp related details and exposes AmqpConnection object * Abstracts all amqp related details and exposes AmqpConnection object
* Manages connection life-cycle * Manages connection life-cycle
@ -57,11 +42,10 @@ public final class MessagingFactory extends ClientEntity implements AmqpConnecti
private final ReactorFactory reactorFactory; private final ReactorFactory reactorFactory;
private Reactor reactor; private Reactor reactor;
private ReactorDispatcher reactorScheduler; private ReactorDispatcher reactorDispatcher;
private Connection connection; private Connection connection;
private CBSChannel cbsChannel; private CBSChannel cbsChannel;
private ManagementChannel mgmtChannel; private ManagementChannel mgmtChannel;
private Duration operationTimeout; private Duration operationTimeout;
private RetryPolicy retryPolicy; private RetryPolicy retryPolicy;
private CompletableFuture<MessagingFactory> open; private CompletableFuture<MessagingFactory> open;
@ -70,13 +54,12 @@ public final class MessagingFactory extends ClientEntity implements AmqpConnecti
MessagingFactory(final ConnectionStringBuilder builder, MessagingFactory(final ConnectionStringBuilder builder,
final RetryPolicy retryPolicy, final RetryPolicy retryPolicy,
final Executor executor, final ScheduledExecutorService executor,
final ReactorFactory reactorFactory) { final ReactorFactory reactorFactory) {
super("MessagingFactory".concat(StringUtil.getRandomString()), null, executor); super("MessagingFactory".concat(StringUtil.getRandomString()), null, executor);
this.hostName = builder.getEndpoint().getHost(); this.hostName = builder.getEndpoint().getHost();
this.reactorFactory = reactorFactory; this.reactorFactory = reactorFactory;
this.operationTimeout = builder.getOperationTimeout(); this.operationTimeout = builder.getOperationTimeout();
this.retryPolicy = retryPolicy; this.retryPolicy = retryPolicy;
this.registeredLinks = new LinkedList<>(); this.registeredLinks = new LinkedList<>();
@ -91,21 +74,21 @@ public final class MessagingFactory extends ClientEntity implements AmqpConnecti
this.closeTask = new CompletableFuture<>(); this.closeTask = new CompletableFuture<>();
} }
public static CompletableFuture<MessagingFactory> createFromConnectionString(final String connectionString, final Executor executor) throws IOException { public static CompletableFuture<MessagingFactory> createFromConnectionString(final String connectionString, final ScheduledExecutorService executor) throws IOException {
return createFromConnectionString(connectionString, RetryPolicy.getDefault(), executor); return createFromConnectionString(connectionString, RetryPolicy.getDefault(), executor);
} }
public static CompletableFuture<MessagingFactory> createFromConnectionString( public static CompletableFuture<MessagingFactory> createFromConnectionString(
final String connectionString, final String connectionString,
final RetryPolicy retryPolicy, final RetryPolicy retryPolicy,
final Executor executor) throws IOException { final ScheduledExecutorService executor) throws IOException {
return createFromConnectionString(connectionString, retryPolicy, executor, new ReactorFactory()); return createFromConnectionString(connectionString, retryPolicy, executor, new ReactorFactory());
} }
public static CompletableFuture<MessagingFactory> createFromConnectionString( public static CompletableFuture<MessagingFactory> createFromConnectionString(
final String connectionString, final String connectionString,
final RetryPolicy retryPolicy, final RetryPolicy retryPolicy,
final Executor executor, final ScheduledExecutorService executor,
final ReactorFactory reactorFactory) throws IOException { final ReactorFactory reactorFactory) throws IOException {
final ConnectionStringBuilder builder = new ConnectionStringBuilder(connectionString); final ConnectionStringBuilder builder = new ConnectionStringBuilder(connectionString);
final MessagingFactory messagingFactory = new MessagingFactory(builder, final MessagingFactory messagingFactory = new MessagingFactory(builder,
@ -153,9 +136,9 @@ public final class MessagingFactory extends ClientEntity implements AmqpConnecti
} }
} }
public ReactorDispatcher getReactorScheduler() { public ReactorDispatcher getReactorDispatcher() {
synchronized (this.reactorLock) { synchronized (this.reactorLock) {
return this.reactorScheduler; return this.reactorDispatcher;
} }
} }
@ -165,26 +148,15 @@ public final class MessagingFactory extends ClientEntity implements AmqpConnecti
private void createConnection() throws IOException { private void createConnection() throws IOException {
this.open = new CompletableFuture<>(); this.open = new CompletableFuture<>();
this.startReactor(new ReactorHandler() { this.startReactor(new ReactorHandlerWithConnection());
@Override
public void onReactorInit(Event e) {
super.onReactorInit(e);
final Reactor r = e.getReactor();
connection = r.connectionToHost(
connectionHandler.getRemoteHostName(),
connectionHandler.getRemotePort(),
connectionHandler);
}
});
} }
private void startReactor(final ReactorHandler reactorHandler) throws IOException { private void startReactor(final ReactorHandler reactorHandler) throws IOException {
final Reactor newReactor = this.reactorFactory.create(reactorHandler, this.connectionHandler.getMaxFrameSize()); final Reactor newReactor = this.reactorFactory.create(reactorHandler, this.connectionHandler.getMaxFrameSize());
synchronized (this.reactorLock) { synchronized (this.reactorLock) {
this.reactor = newReactor; this.reactor = newReactor;
this.reactorScheduler = new ReactorDispatcher(newReactor); this.reactorDispatcher = new ReactorDispatcher(newReactor);
reactorHandler.unsafeSetReactorDispatcher(this.reactorScheduler); reactorHandler.unsafeSetReactorDispatcher(this.reactorDispatcher);
} }
executor.execute(new RunReactor(newReactor, executor)); executor.execute(new RunReactor(newReactor, executor));
@ -226,7 +198,7 @@ public final class MessagingFactory extends ClientEntity implements AmqpConnecti
} }
final Session session = this.connection.session(); final Session session = this.connection.session();
BaseHandler.setHandler(session, new SessionHandler(path, onRemoteSessionOpen, onRemoteSessionOpenError)); BaseHandler.setHandler(session, new SessionHandler(path, onRemoteSessionOpen, onRemoteSessionOpenError, this.operationTimeout));
session.open(); session.open();
return session; return session;
@ -258,16 +230,35 @@ public final class MessagingFactory extends ClientEntity implements AmqpConnecti
@Override @Override
public void onConnectionError(ErrorCondition error) { public void onConnectionError(ErrorCondition error) {
if (TRACE_LOGGER.isWarnEnabled()) {
TRACE_LOGGER.warn(String.format(Locale.US, "onConnectionError: messagingFactory[%s], hostname[%s], error[%s]",
this.getClientId(),
this.hostName,
error != null ? error.getDescription() : "n/a"));
}
if (!this.open.isDone()) { if (!this.open.isDone()) {
if (TRACE_LOGGER.isWarnEnabled()) {
TRACE_LOGGER.warn(String.format(Locale.US, "onConnectionError: messagingFactory[%s], hostname[%s], open hasn't complete, stopping the reactor",
this.getClientId(),
this.hostName));
}
this.getReactor().stop(); this.getReactor().stop();
this.onOpenComplete(ExceptionUtil.toException(error)); this.onOpenComplete(ExceptionUtil.toException(error));
} else { } else {
final Connection currentConnection = this.connection; final Connection oldConnection = this.connection;
final List<Link> registeredLinksCopy = new LinkedList<>(this.registeredLinks); final List<Link> oldRegisteredLinksCopy = new LinkedList<>(this.registeredLinks);
final List<Link> closedLinks = new LinkedList<>(); final List<Link> closedLinks = new LinkedList<>();
for (Link link : registeredLinksCopy) {
for (Link link : oldRegisteredLinksCopy) {
if (link.getLocalState() != EndpointState.CLOSED && link.getRemoteState() != EndpointState.CLOSED) { if (link.getLocalState() != EndpointState.CLOSED && link.getRemoteState() != EndpointState.CLOSED) {
if (TRACE_LOGGER.isWarnEnabled()) {
TRACE_LOGGER.warn(String.format(Locale.US, "onConnectionError: messagingFactory[%s], hostname[%s], closing link [%s]",
this.getClientId(),
this.hostName, link.getName()));
}
link.close(); link.close();
closedLinks.add(link); closedLinks.add(link);
} }
@ -275,11 +266,18 @@ public final class MessagingFactory extends ClientEntity implements AmqpConnecti
// if proton-j detects transport error - onConnectionError is invoked, but, the connection state is not set to closed // if proton-j detects transport error - onConnectionError is invoked, but, the connection state is not set to closed
// in connection recreation we depend on currentConnection state to evaluate need for recreation // in connection recreation we depend on currentConnection state to evaluate need for recreation
if (currentConnection.getLocalState() != EndpointState.CLOSED) { if (oldConnection.getLocalState() != EndpointState.CLOSED) {
if (TRACE_LOGGER.isWarnEnabled()) {
TRACE_LOGGER.warn(String.format(Locale.US, "onConnectionError: messagingFactory[%s], hostname[%s], closing current connection",
this.getClientId(),
this.hostName));
}
// this should ideally be done in Connectionhandler // this should ideally be done in Connectionhandler
// - but, since proton doesn't automatically emit close events // - but, since proton doesn't automatically emit close events
// for all child objects (links & sessions) we are doing it here // for all child objects (links & sessions) we are doing it here
currentConnection.close(); oldConnection.setCondition(error);
oldConnection.close();
} }
for (Link link : closedLinks) { for (Link link : closedLinks) {
@ -300,19 +298,29 @@ public final class MessagingFactory extends ClientEntity implements AmqpConnecti
if (!this.open.isDone()) { if (!this.open.isDone()) {
this.onOpenComplete(cause); this.onOpenComplete(cause);
} else { } else {
final Connection currentConnection = this.connection; if (this.getIsClosingOrClosed()) {
return;
}
TRACE_LOGGER.warn(String.format(Locale.US, "onReactorError messagingFactory[%s], hostName[%s], error[%s]",
this.getClientId(), this.getHostName(),
cause.getMessage()));
final Connection oldConnection = this.connection;
final List<Link> oldRegisteredLinksCopy = new LinkedList<>(this.registeredLinks);
try { try {
if (this.getIsClosingOrClosed()) { TRACE_LOGGER.info(String.format(Locale.US, "onReactorError messagingFactory[%s], hostName[%s], message[%s]",
return; this.getClientId(), this.getHostName(),
} else { "starting new reactor"));
this.startReactor(new ReactorHandler());
} this.startReactor(new ReactorHandlerWithConnection());
} catch (IOException e) { } catch (IOException e) {
TRACE_LOGGER.error(String.format(Locale.US, "messagingFactory[%s], hostName[%s], error[%s]", TRACE_LOGGER.error(String.format(Locale.US, "messagingFactory[%s], hostName[%s], error[%s]",
this.getClientId(), this.getHostName(), this.getClientId(), this.getHostName(),
ExceptionUtil.toStackTraceString(e, "Re-starting reactor failed with error"))); ExceptionUtil.toStackTraceString(e, "Re-starting reactor failed with error")));
// TODO - stop retrying on the error after multiple attempts.
this.onReactorError(cause); this.onReactorError(cause);
} }
@ -320,12 +328,11 @@ public final class MessagingFactory extends ClientEntity implements AmqpConnecti
// below .close() calls (local closes). // below .close() calls (local closes).
// But, we still need to change the states of these to Closed - so that subsequent retries - will // But, we still need to change the states of these to Closed - so that subsequent retries - will
// treat the links and connection as closed and re-establish them and continue running on new Reactor instance. // treat the links and connection as closed and re-establish them and continue running on new Reactor instance.
if (currentConnection.getLocalState() != EndpointState.CLOSED && currentConnection.getRemoteState() != EndpointState.CLOSED) { if (oldConnection.getLocalState() != EndpointState.CLOSED && oldConnection.getRemoteState() != EndpointState.CLOSED) {
currentConnection.close(); oldConnection.close();
} }
final List<Link> registeredLinksCopy = new LinkedList<>(this.registeredLinks); for (final Link link : oldRegisteredLinksCopy) {
for (final Link link : registeredLinksCopy) {
if (link.getLocalState() != EndpointState.CLOSED && link.getRemoteState() != EndpointState.CLOSED) { if (link.getLocalState() != EndpointState.CLOSED && link.getRemoteState() != EndpointState.CLOSED) {
link.close(); link.close();
} }
@ -379,11 +386,11 @@ public final class MessagingFactory extends ClientEntity implements AmqpConnecti
} }
public void scheduleOnReactorThread(final DispatchHandler handler) throws IOException, RejectedExecutionException { public void scheduleOnReactorThread(final DispatchHandler handler) throws IOException, RejectedExecutionException {
this.getReactorScheduler().invoke(handler); this.getReactorDispatcher().invoke(handler);
} }
public void scheduleOnReactorThread(final int delay, final DispatchHandler handler) throws IOException, RejectedExecutionException { public void scheduleOnReactorThread(final int delay, final DispatchHandler handler) throws IOException, RejectedExecutionException {
this.getReactorScheduler().invoke(delay, handler); this.getReactorDispatcher().invoke(delay, handler);
} }
public static class ReactorFactory { public static class ReactorFactory {
@ -396,15 +403,12 @@ public final class MessagingFactory extends ClientEntity implements AmqpConnecti
private class CloseWork extends DispatchHandler { private class CloseWork extends DispatchHandler {
@Override @Override
public void onEvent() { public void onEvent() {
final ReactorDispatcher dispatcher = getReactorScheduler(); final ReactorDispatcher dispatcher = getReactorDispatcher();
synchronized (cbsChannelCreateLock) { synchronized (cbsChannelCreateLock) {
if (cbsChannel != null) { if (cbsChannel != null) {
cbsChannel.close( cbsChannel.close(
dispatcher, dispatcher,
new OperationResult<Void, Exception>() { new OperationResult<Void, Exception>() {
@Override @Override
public void onComplete(Void result) { public void onComplete(Void result) {
if (TRACE_LOGGER.isInfoEnabled()) { if (TRACE_LOGGER.isInfoEnabled()) {
@ -416,7 +420,6 @@ public final class MessagingFactory extends ClientEntity implements AmqpConnecti
@Override @Override
public void onError(Exception error) { public void onError(Exception error) {
if (TRACE_LOGGER.isWarnEnabled()) { if (TRACE_LOGGER.isWarnEnabled()) {
TRACE_LOGGER.warn(String.format(Locale.US, TRACE_LOGGER.warn(String.format(Locale.US,
"messagingFactory[%s], hostName[%s], cbsChannelCloseError[%s]", "messagingFactory[%s], hostName[%s], cbsChannelCloseError[%s]",
@ -428,12 +431,10 @@ public final class MessagingFactory extends ClientEntity implements AmqpConnecti
} }
synchronized (mgmtChannelCreateLock) { synchronized (mgmtChannelCreateLock) {
if (mgmtChannel != null) { if (mgmtChannel != null) {
mgmtChannel.close( mgmtChannel.close(
dispatcher, dispatcher,
new OperationResult<Void, Exception>() { new OperationResult<Void, Exception>() {
@Override @Override
public void onComplete(Void result) { public void onComplete(Void result) {
if (TRACE_LOGGER.isInfoEnabled()) { if (TRACE_LOGGER.isInfoEnabled()) {
@ -456,33 +457,34 @@ public final class MessagingFactory extends ClientEntity implements AmqpConnecti
} }
} }
if (connection != null && connection.getRemoteState() != EndpointState.CLOSED && connection.getLocalState() != EndpointState.CLOSED) if (connection != null && connection.getRemoteState() != EndpointState.CLOSED && connection.getLocalState() != EndpointState.CLOSED) {
connection.close(); connection.close();
}
} }
} }
private class RunReactor implements Runnable { private class RunReactor implements Runnable {
final private Reactor rctr; final private Reactor rctr;
final private Executor executor; final private ScheduledExecutorService executor;
volatile boolean hasStarted; volatile boolean hasStarted;
public RunReactor(final Reactor reactor, final Executor executor) { public RunReactor(final Reactor reactor, final ScheduledExecutorService executor) {
this.rctr = reactor; this.rctr = reactor;
this.executor = executor; this.executor = executor;
this.hasStarted = false; this.hasStarted = false;
} }
public void run() { public void run() {
if (TRACE_LOGGER.isInfoEnabled() && !this.hasStarted) {
TRACE_LOGGER.info(String.format(Locale.US, "messagingFactory[%s], hostName[%s], info[%s]",
getClientId(), getHostName(), "starting reactor instance."));
}
boolean reScheduledReactor = false; boolean reScheduledReactor = false;
try { try {
if (!this.hasStarted) { if (!this.hasStarted) {
if (TRACE_LOGGER.isInfoEnabled()) {
TRACE_LOGGER.info(String.format(Locale.US, "messagingFactory[%s], hostName[%s], info[%s]",
getClientId(), getHostName(), "starting reactor instance."));
}
this.rctr.start(); this.rctr.start();
this.hasStarted = true; this.hasStarted = true;
} }
@ -495,7 +497,7 @@ public final class MessagingFactory extends ClientEntity implements AmqpConnecti
if (TRACE_LOGGER.isWarnEnabled()) { if (TRACE_LOGGER.isWarnEnabled()) {
TRACE_LOGGER.warn(String.format(Locale.US, "messagingFactory[%s], hostName[%s], error[%s]", TRACE_LOGGER.warn(String.format(Locale.US, "messagingFactory[%s], hostName[%s], error[%s]",
getClientId(), getHostName(), getClientId(), getHostName(),
ExceptionUtil.toStackTraceString(exception, "scheduling reactor failed"))); ExceptionUtil.toStackTraceString(exception, "scheduling reactor failed because the executor has been shut down")));
} }
this.rctr.attachments().set(RejectedExecutionException.class, RejectedExecutionException.class, exception); this.rctr.attachments().set(RejectedExecutionException.class, RejectedExecutionException.class, exception);
@ -504,6 +506,12 @@ public final class MessagingFactory extends ClientEntity implements AmqpConnecti
return; return;
} }
if (TRACE_LOGGER.isWarnEnabled()) {
TRACE_LOGGER.warn(String.format(Locale.US, "messagingFactory[%s], hostName[%s], message[%s]",
getClientId(), getHostName(),
"stopping the reactor because thread was interrupted or the reactor has no more events to process."));
}
this.rctr.stop(); this.rctr.stop();
} catch (HandlerException handlerException) { } catch (HandlerException handlerException) {
Throwable cause = handlerException.getCause(); Throwable cause = handlerException.getCause();
@ -543,15 +551,55 @@ public final class MessagingFactory extends ClientEntity implements AmqpConnecti
return; return;
} }
this.rctr.free();
if (getIsClosingOrClosed() && !closeTask.isDone()) { if (getIsClosingOrClosed() && !closeTask.isDone()) {
this.rctr.free();
closeTask.complete(null); closeTask.complete(null);
if (closeTimer != null) {
if (closeTimer != null)
closeTimer.cancel(false); closeTimer.cancel(false);
}
} else {
scheduleCompletePendingTasks();
} }
} }
} }
private void scheduleCompletePendingTasks() {
this.executor.schedule(new Runnable() {
@Override
public void run() {
if (TRACE_LOGGER.isWarnEnabled()) {
TRACE_LOGGER.warn(String.format(Locale.US, "messagingFactory[%s], hostName[%s], message[%s]",
getClientId(), getHostName(),
"Processing all pending tasks and closing old reactor."));
}
try {
rctr.stop();
rctr.process();
} catch (HandlerException e) {
if (TRACE_LOGGER.isWarnEnabled()) {
TRACE_LOGGER.warn(String.format(Locale.US, "messagingFactory[%s], hostName[%s], error[%s]",
getClientId(), getHostName(), ExceptionUtil.toStackTraceString(e,
"scheduleCompletePendingTasks - exception occurred while processing events.")));
}
} finally {
rctr.free();
}
}
}, MessagingFactory.this.getOperationTimeout().getSeconds(), TimeUnit.SECONDS);
}
}
private class ReactorHandlerWithConnection extends ReactorHandler {
@Override
public void onReactorInit(Event e) {
super.onReactorInit(e);
final Reactor r = e.getReactor();
connection = r.connectionToHost(
connectionHandler.getRemoteHostName(),
connectionHandler.getRemotePort(),
connectionHandler);
}
} }
} }

Просмотреть файл

@ -15,7 +15,7 @@ import org.slf4j.LoggerFactory;
import java.time.Duration; import java.time.Duration;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
@ -46,8 +46,8 @@ final class PartitionReceiverImpl extends ClientEntity implements ReceiverSettin
final Long epoch, final Long epoch,
final boolean isEpochReceiver, final boolean isEpochReceiver,
final ReceiverOptions receiverOptions, final ReceiverOptions receiverOptions,
final Executor executor) { final ScheduledExecutorService executor) {
super(null, null, executor); super("PartitionReceiverImpl".concat(StringUtil.getRandomString()), null, executor);
this.underlyingFactory = factory; this.underlyingFactory = factory;
this.eventHubName = eventHubName; this.eventHubName = eventHubName;
@ -72,8 +72,7 @@ final class PartitionReceiverImpl extends ClientEntity implements ReceiverSettin
final long epoch, final long epoch,
final boolean isEpochReceiver, final boolean isEpochReceiver,
ReceiverOptions receiverOptions, ReceiverOptions receiverOptions,
final Executor executor) final ScheduledExecutorService executor) {
throws EventHubException {
if (epoch < NULL_EPOCH) { if (epoch < NULL_EPOCH) {
throw new IllegalArgumentException("epoch cannot be a negative value. Please specify a zero or positive long value."); throw new IllegalArgumentException("epoch cannot be a negative value. Please specify a zero or positive long value.");
} }
@ -96,7 +95,7 @@ final class PartitionReceiverImpl extends ClientEntity implements ReceiverSettin
private CompletableFuture<Void> createInternalReceiver() { private CompletableFuture<Void> createInternalReceiver() {
return MessageReceiver.create(this.underlyingFactory, return MessageReceiver.create(this.underlyingFactory,
StringUtil.getRandomString(), this.getClientId().concat("-InternalReceiver"),
String.format("%s/ConsumerGroups/%s/Partitions/%s", this.eventHubName, this.consumerGroupName, this.partitionId), String.format("%s/ConsumerGroups/%s/Partitions/%s", this.eventHubName, this.consumerGroupName, this.partitionId),
this.receiverOptions.getPrefetchCount(), this) this.receiverOptions.getPrefetchCount(), this)
.thenAcceptAsync(new Consumer<MessageReceiver>() { .thenAcceptAsync(new Consumer<MessageReceiver>() {
@ -179,6 +178,8 @@ final class PartitionReceiverImpl extends ClientEntity implements ReceiverSettin
"Unexpected value for parameter 'receiveHandler'. PartitionReceiver was already registered with a PartitionReceiveHandler instance. Only 1 instance can be registered."); "Unexpected value for parameter 'receiveHandler'. PartitionReceiver was already registered with a PartitionReceiveHandler instance. Only 1 instance can be registered.");
this.receivePump = new ReceivePump( this.receivePump = new ReceivePump(
this.eventHubName,
this.consumerGroupName,
new ReceivePump.IPartitionReceiver() { new ReceivePump.IPartitionReceiver() {
@Override @Override
public CompletableFuture<Iterable<EventData>> receive(int maxBatchSize) { public CompletableFuture<Iterable<EventData>> receive(int maxBatchSize) {

Просмотреть файл

@ -7,7 +7,7 @@ package com.microsoft.azure.eventhubs.impl;
import com.microsoft.azure.eventhubs.*; import com.microsoft.azure.eventhubs.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
@ -18,8 +18,8 @@ final class PartitionSenderImpl extends ClientEntity implements PartitionSender
private volatile MessageSender internalSender; private volatile MessageSender internalSender;
private PartitionSenderImpl(final MessagingFactory factory, final String eventHubName, final String partitionId, final Executor executor) { private PartitionSenderImpl(final MessagingFactory factory, final String eventHubName, final String partitionId, final ScheduledExecutorService executor) {
super(null, null, executor); super("PartitionSenderImpl".concat(StringUtil.getRandomString()), null, executor);
this.partitionId = partitionId; this.partitionId = partitionId;
this.eventHubName = eventHubName; this.eventHubName = eventHubName;
@ -29,7 +29,7 @@ final class PartitionSenderImpl extends ClientEntity implements PartitionSender
static CompletableFuture<PartitionSender> Create(final MessagingFactory factory, static CompletableFuture<PartitionSender> Create(final MessagingFactory factory,
final String eventHubName, final String eventHubName,
final String partitionId, final String partitionId,
final Executor executor) throws EventHubException { final ScheduledExecutorService executor) throws EventHubException {
final PartitionSenderImpl sender = new PartitionSenderImpl(factory, eventHubName, partitionId, executor); final PartitionSenderImpl sender = new PartitionSenderImpl(factory, eventHubName, partitionId, executor);
return sender.createInternalSender() return sender.createInternalSender()
.thenApplyAsync(new Function<Void, PartitionSender>() { .thenApplyAsync(new Function<Void, PartitionSender>() {
@ -40,7 +40,7 @@ final class PartitionSenderImpl extends ClientEntity implements PartitionSender
} }
private CompletableFuture<Void> createInternalSender() throws EventHubException { private CompletableFuture<Void> createInternalSender() throws EventHubException {
return MessageSender.create(this.factory, StringUtil.getRandomString(), return MessageSender.create(this.factory, this.getClientId().concat("-InternalSender"),
String.format("%s/Partitions/%s", this.eventHubName, this.partitionId)) String.format("%s/Partitions/%s", this.eventHubName, this.partitionId))
.thenAcceptAsync(new Consumer<MessageSender>() { .thenAcceptAsync(new Consumer<MessageSender>() {
public void accept(MessageSender a) { public void accept(MessageSender a) {

Просмотреть файл

@ -80,7 +80,7 @@ public final class ReactorDispatcher {
// throw when the pipe is in closed state - in which case, // throw when the pipe is in closed state - in which case,
// signalling the new event-dispatch will fail // signalling the new event-dispatch will fail
if (!this.ioSignal.source().isOpen() || !this.ioSignal.sink().isOpen()) { if (!this.ioSignal.sink().isOpen()) {
throw new RejectedExecutionException("ReactorDispatcher instance is closed."); throw new RejectedExecutionException("ReactorDispatcher instance is closed.");
} }
} }
@ -121,7 +121,7 @@ public final class ReactorDispatcher {
} catch (ClosedChannelException ignorePipeClosedDuringReactorShutdown) { } catch (ClosedChannelException ignorePipeClosedDuringReactorShutdown) {
TRACE_LOGGER.info("ScheduleHandler.run() failed with an error", ignorePipeClosedDuringReactorShutdown); TRACE_LOGGER.info("ScheduleHandler.run() failed with an error", ignorePipeClosedDuringReactorShutdown);
} catch (IOException ioException) { } catch (IOException ioException) {
TRACE_LOGGER.info("ScheduleHandler.run() failed with an error", ioException); TRACE_LOGGER.warn("ScheduleHandler.run() failed with an error", ioException);
throw new RuntimeException(ioException); throw new RuntimeException(ioException);
} }
@ -135,17 +135,11 @@ public final class ReactorDispatcher {
private final class CloseHandler implements Callback { private final class CloseHandler implements Callback {
@Override @Override
public void run(Selectable selectable) { public void run(Selectable selectable) {
try {
selectable.getChannel().close();
} catch (IOException ioException) {
TRACE_LOGGER.info("CloseHandler.run() failed with an error", ioException);
}
try { try {
if (ioSignal.sink().isOpen()) if (ioSignal.sink().isOpen())
ioSignal.sink().close(); ioSignal.sink().close();
} catch (IOException ioException) { } catch (IOException ioException) {
TRACE_LOGGER.info("CloseHandler.run() failed with an error", ioException); TRACE_LOGGER.info("CloseHandler.run() sink().close() failed with an error", ioException);
} }
workScheduler.run(null); workScheduler.run(null);
@ -154,7 +148,7 @@ public final class ReactorDispatcher {
if (ioSignal.source().isOpen()) if (ioSignal.source().isOpen())
ioSignal.source().close(); ioSignal.source().close();
} catch (IOException ioException) { } catch (IOException ioException) {
TRACE_LOGGER.info("CloseHandler.run() failed with an error", ioException); TRACE_LOGGER.info("CloseHandler.run() source().close() failed with an error", ioException);
} }
} }
} }

Просмотреть файл

@ -37,7 +37,7 @@ public final class ReceiveLinkHandler extends BaseLinkHandler {
if (TRACE_LOGGER.isInfoEnabled()) { if (TRACE_LOGGER.isInfoEnabled()) {
TRACE_LOGGER.info( TRACE_LOGGER.info(
String.format("linkName[%s], localSource[%s]", receiver.getName(), receiver.getSource())); String.format("onLinkLocalOpen linkName[%s], localSource[%s]", receiver.getName(), receiver.getSource()));
} }
} }
} }
@ -45,11 +45,12 @@ public final class ReceiveLinkHandler extends BaseLinkHandler {
@Override @Override
public void onLinkRemoteOpen(Event event) { public void onLinkRemoteOpen(Event event) {
Link link = event.getLink(); Link link = event.getLink();
if (link != null && link instanceof Receiver) { if (link instanceof Receiver) {
Receiver receiver = (Receiver) link; Receiver receiver = (Receiver) link;
if (link.getRemoteSource() != null) { if (link.getRemoteSource() != null) {
if (TRACE_LOGGER.isInfoEnabled()) { if (TRACE_LOGGER.isInfoEnabled()) {
TRACE_LOGGER.info(String.format(Locale.US, "linkName[%s], remoteSource[%s]", receiver.getName(), link.getRemoteSource())); TRACE_LOGGER.info(String.format(Locale.US, "onLinkRemoteOpen linkName[%s], remoteSource[%s]",
receiver.getName(), link.getRemoteSource()));
} }
synchronized (this.firstResponse) { synchronized (this.firstResponse) {
@ -59,7 +60,8 @@ public final class ReceiveLinkHandler extends BaseLinkHandler {
} else { } else {
if (TRACE_LOGGER.isInfoEnabled()) { if (TRACE_LOGGER.isInfoEnabled()) {
TRACE_LOGGER.info( TRACE_LOGGER.info(
String.format(Locale.US, "linkName[%s], remoteTarget[null], remoteSource[null], action[waitingForError]", receiver.getName())); String.format(Locale.US, "onLinkRemoteOpen linkName[%s], remoteTarget[null], " +
"remoteSource[null], action[waitingForError]", receiver.getName()));
} }
} }
} }
@ -89,8 +91,9 @@ public final class ReceiveLinkHandler extends BaseLinkHandler {
if (TRACE_LOGGER.isWarnEnabled()) { if (TRACE_LOGGER.isWarnEnabled()) {
TRACE_LOGGER.warn( TRACE_LOGGER.warn(
receiveLink != null receiveLink != null
? String.format(Locale.US, "linkName[%s], updatedLinkCredit[%s], remoteCredit[%s], remoteCondition[%s], delivery.isSettled[%s]", ? String.format(Locale.US, "onDelivery linkName[%s], updatedLinkCredit[%s], remoteCredit[%s], " +
receiveLink.getName(), receiveLink.getCredit(), receiveLink.getRemoteCredit(), receiveLink.getRemoteCondition(), delivery.isSettled()) "remoteCondition[%s], delivery.isSettled[%s]",
receiveLink.getName(), receiveLink.getCredit(), receiveLink.getRemoteCredit(), receiveLink.getRemoteCondition(), delivery.isSettled())
: String.format(Locale.US, "delivery.isSettled[%s]", delivery.isSettled())); : String.format(Locale.US, "delivery.isSettled[%s]", delivery.isSettled()));
} }
} else { } else {
@ -100,7 +103,8 @@ public final class ReceiveLinkHandler extends BaseLinkHandler {
if (TRACE_LOGGER.isTraceEnabled() && receiveLink != null) { if (TRACE_LOGGER.isTraceEnabled() && receiveLink != null) {
TRACE_LOGGER.trace( TRACE_LOGGER.trace(
String.format(Locale.US, "linkName[%s], updatedLinkCredit[%s], remoteCredit[%s], remoteCondition[%s], delivery.isPartial[%s]", String.format(Locale.US, "onDelivery linkName[%s], updatedLinkCredit[%s], remoteCredit[%s], " +
"remoteCondition[%s], delivery.isPartial[%s]",
receiveLink.getName(), receiveLink.getCredit(), receiveLink.getRemoteCredit(), receiveLink.getRemoteCondition(), delivery.isPartial())); receiveLink.getName(), receiveLink.getCredit(), receiveLink.getRemoteCredit(), receiveLink.getRemoteCondition(), delivery.isPartial()));
} }
} }

Просмотреть файл

@ -6,7 +6,6 @@ package com.microsoft.azure.eventhubs.impl;
import com.microsoft.azure.eventhubs.EventData; import com.microsoft.azure.eventhubs.EventData;
import com.microsoft.azure.eventhubs.PartitionReceiveHandler; import com.microsoft.azure.eventhubs.PartitionReceiveHandler;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -25,15 +24,21 @@ public class ReceivePump implements Runnable {
private final CompletableFuture<Void> stopPump; private final CompletableFuture<Void> stopPump;
private final Executor executor; private final Executor executor;
private final ProcessAndReschedule processAndReschedule; private final ProcessAndReschedule processAndReschedule;
private final String eventHubName;
private final String consumerGroupName;
private AtomicBoolean stopPumpRaised; private AtomicBoolean stopPumpRaised;
private volatile boolean isPumpHealthy = true; private volatile boolean isPumpHealthy = true;
public ReceivePump( public ReceivePump(
final String eventHubName,
final String consumerGroupName,
final IPartitionReceiver receiver, final IPartitionReceiver receiver,
final PartitionReceiveHandler receiveHandler, final PartitionReceiveHandler receiveHandler,
final boolean invokeOnReceiveWithNoEvents, final boolean invokeOnReceiveWithNoEvents,
final Executor executor) { final Executor executor) {
this.eventHubName = eventHubName;
this.consumerGroupName = consumerGroupName;
this.receiver = receiver; this.receiver = receiver;
this.onReceiveHandler = receiveHandler; this.onReceiveHandler = receiveHandler;
this.invokeOnTimeout = invokeOnReceiveWithNoEvents; this.invokeOnTimeout = invokeOnReceiveWithNoEvents;
@ -51,8 +56,9 @@ public class ReceivePump implements Runnable {
} catch (final Exception exception) { } catch (final Exception exception) {
if (TRACE_LOGGER.isErrorEnabled()) { if (TRACE_LOGGER.isErrorEnabled()) {
TRACE_LOGGER.error( TRACE_LOGGER.error(
String.format("Receive pump for partition (%s) encountered unrecoverable error and exited with exception %s.", String.format("Receive pump for eventHub (%s), consumerGroup (%s), partition (%s) " +
ReceivePump.this.receiver.getPartitionId(), exception.toString())); "encountered unrecoverable error and exited with exception %s.",
this.eventHubName, this.consumerGroupName, this.receiver.getPartitionId(), exception.toString()));
} }
throw exception; throw exception;
@ -63,11 +69,11 @@ public class ReceivePump implements Runnable {
public void receiveAndProcess() { public void receiveAndProcess() {
if (this.shouldContinue()) { if (this.shouldContinue()) {
this.receiver.receive(this.onReceiveHandler.getMaxEventCount()) this.receiver.receive(this.onReceiveHandler.getMaxEventCount())
.handleAsync(this.processAndReschedule, this.executor); .handleAsync(this.processAndReschedule, this.executor);
} else { } else {
if (TRACE_LOGGER.isInfoEnabled()) { if (TRACE_LOGGER.isInfoEnabled()) {
TRACE_LOGGER.info(String.format("Stopping receive pump for partition (%s) as %s", TRACE_LOGGER.info(String.format("Stopping receive pump for eventHub (%s), consumerGroup (%s), partition (%s) as %s",
ReceivePump.this.receiver.getPartitionId(), this.eventHubName, this.consumerGroupName, this.receiver.getPartitionId(),
this.stopPumpRaised.get() ? "per the request." : "pump ran into errors.")); this.stopPumpRaised.get() ? "per the request." : "pump ran into errors."));
} }
@ -84,13 +90,6 @@ public class ReceivePump implements Runnable {
return !this.stopPump.isDone(); return !this.stopPump.isDone();
} }
// partition receiver contract against which this pump works
public interface IPartitionReceiver {
String getPartitionId();
CompletableFuture<Iterable<EventData>> receive(final int maxBatchSize);
}
private boolean shouldContinue() { private boolean shouldContinue() {
return this.isPumpHealthy && !this.stopPumpRaised.get(); return this.isPumpHealthy && !this.stopPumpRaised.get();
} }
@ -101,8 +100,8 @@ public class ReceivePump implements Runnable {
if (TRACE_LOGGER.isWarnEnabled()) { if (TRACE_LOGGER.isWarnEnabled()) {
TRACE_LOGGER.warn(String.format( TRACE_LOGGER.warn(String.format(
"Receive pump for partition (%s) exiting after receive exception %s", "Receive pump for eventHub (%s), consumerGroup (%s), partition (%s) exiting after receive exception %s",
this.receiver.getPartitionId(), clientException.toString())); this.eventHubName, this.consumerGroupName, this.receiver.getPartitionId(), clientException.toString()));
} }
this.onReceiveHandler.onError(clientException); this.onReceiveHandler.onError(clientException);
@ -113,16 +112,17 @@ public class ReceivePump implements Runnable {
this.isPumpHealthy = false; this.isPumpHealthy = false;
if (TRACE_LOGGER.isErrorEnabled()) { if (TRACE_LOGGER.isErrorEnabled()) {
TRACE_LOGGER.error( TRACE_LOGGER.error(
String.format("Receive pump for partition (%s) exiting after user-code exception %s", String.format("Receive pump for eventHub (%s), consumerGroup (%s), partition (%s) " +
this.receiver.getPartitionId(), userCodeException.toString())); "exiting after user-code exception %s",
this.eventHubName, this.consumerGroupName, this.receiver.getPartitionId(), userCodeException.toString()));
} }
this.onReceiveHandler.onError(userCodeException); this.onReceiveHandler.onError(userCodeException);
if (userCodeException instanceof InterruptedException) { if (userCodeException instanceof InterruptedException) {
if (TRACE_LOGGER.isInfoEnabled()) { if (TRACE_LOGGER.isInfoEnabled()) {
TRACE_LOGGER.info(String.format("Interrupting receive pump for partition (%s)", TRACE_LOGGER.info(String.format("Interrupting receive pump for eventHub (%s), consumerGroup (%s), partition (%s)",
this.receiver.getPartitionId())); this.eventHubName, this.consumerGroupName, this.receiver.getPartitionId()));
} }
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
@ -137,14 +137,21 @@ public class ReceivePump implements Runnable {
if (TRACE_LOGGER.isWarnEnabled()) { if (TRACE_LOGGER.isWarnEnabled()) {
TRACE_LOGGER.warn(String.format( TRACE_LOGGER.warn(String.format(
"Receive pump for partition (%s) exiting with error: %s", "Receive pump for eventHub (%s), consumerGroup (%s), partition (%s) exiting with error: %s",
ReceivePump.this.receiver.getPartitionId(), rejectedException.toString())); this.eventHubName, this.consumerGroupName, ReceivePump.this.receiver.getPartitionId(), rejectedException.toString()));
} }
this.onReceiveHandler.onError(rejectedException); this.onReceiveHandler.onError(rejectedException);
} }
} }
// partition receiver contract against which this pump works
public interface IPartitionReceiver {
String getPartitionId();
CompletableFuture<Iterable<EventData>> receive(final int maxBatchSize);
}
private final class ProcessAndReschedule implements BiFunction<Iterable<EventData>, Throwable, Void> { private final class ProcessAndReschedule implements BiFunction<Iterable<EventData>, Throwable, Void> {
@Override @Override
@ -156,7 +163,7 @@ public class ReceivePump implements Runnable {
// don't invoke user call back - if stop is already raised / pump is unhealthy // don't invoke user call back - if stop is already raised / pump is unhealthy
if (ReceivePump.this.shouldContinue() && if (ReceivePump.this.shouldContinue() &&
(receivedEvents != null (receivedEvents != null
|| (receivedEvents == null && ReceivePump.this.invokeOnTimeout))) { || (receivedEvents == null && ReceivePump.this.invokeOnTimeout))) {
ReceivePump.this.onReceiveHandler.onReceive(receivedEvents); ReceivePump.this.onReceiveHandler.onReceive(receivedEvents);
} }
} catch (final Throwable userCodeError) { } catch (final Throwable userCodeError) {

Просмотреть файл

@ -2,5 +2,5 @@ package com.microsoft.azure.eventhubs.impl;
interface SchedulerProvider { interface SchedulerProvider {
ReactorDispatcher getReactorScheduler(); ReactorDispatcher getReactorDispatcher();
} }

Просмотреть файл

@ -27,14 +27,25 @@ public class SendLinkHandler extends BaseLinkHandler {
this.isFirstFlow = true; this.isFirstFlow = true;
} }
@Override
public void onLinkLocalOpen(Event event) {
Link link = event.getLink();
if (link instanceof Sender) {
Sender sender = (Sender) link;
if (TRACE_LOGGER.isInfoEnabled()) {
TRACE_LOGGER.info(String.format("onLinkLocalOpen linkName[%s], localTarget[%s]", sender.getName(), sender.getTarget()));
}
}
}
@Override @Override
public void onLinkRemoteOpen(Event event) { public void onLinkRemoteOpen(Event event) {
Link link = event.getLink(); Link link = event.getLink();
if (link != null && link instanceof Sender) { if (link instanceof Sender) {
Sender sender = (Sender) link; Sender sender = (Sender) link;
if (link.getRemoteTarget() != null) { if (link.getRemoteTarget() != null) {
if (TRACE_LOGGER.isInfoEnabled()) { if (TRACE_LOGGER.isInfoEnabled()) {
TRACE_LOGGER.info(String.format(Locale.US, "linkName[%s], remoteTarget[%s]", sender.getName(), link.getRemoteTarget())); TRACE_LOGGER.info(String.format(Locale.US, "onLinkRemoteOpen linkName[%s], remoteTarget[%s]", sender.getName(), link.getRemoteTarget()));
} }
synchronized (this.firstFlow) { synchronized (this.firstFlow) {
@ -44,7 +55,7 @@ public class SendLinkHandler extends BaseLinkHandler {
} else { } else {
if (TRACE_LOGGER.isInfoEnabled()) { if (TRACE_LOGGER.isInfoEnabled()) {
TRACE_LOGGER.info( TRACE_LOGGER.info(
String.format(Locale.US, "linkName[%s], remoteTarget[null], remoteSource[null], action[waitingForError]", sender.getName())); String.format(Locale.US, "onLinkRemoteOpen linkName[%s], remoteTarget[null], remoteSource[null], action[waitingForError]", sender.getName()));
} }
} }
} }
@ -59,7 +70,7 @@ public class SendLinkHandler extends BaseLinkHandler {
if (TRACE_LOGGER.isTraceEnabled()) { if (TRACE_LOGGER.isTraceEnabled()) {
TRACE_LOGGER.trace( TRACE_LOGGER.trace(
"linkName[" + sender.getName() + "onDelivery linkName[" + sender.getName() +
"], unsettled[" + sender.getUnsettled() + "], credit[" + sender.getRemoteCredit() + "], deliveryState[" + delivery.getRemoteState() + "], unsettled[" + sender.getUnsettled() + "], credit[" + sender.getRemoteCredit() + "], deliveryState[" + delivery.getRemoteState() +
"], delivery.isBuffered[" + delivery.isBuffered() + "], delivery.id[" + new String(delivery.getTag()) + "]"); "], delivery.isBuffered[" + delivery.isBuffered() + "], delivery.id[" + new String(delivery.getTag()) + "]");
} }
@ -86,7 +97,7 @@ public class SendLinkHandler extends BaseLinkHandler {
this.msgSender.onFlow(sender.getRemoteCredit()); this.msgSender.onFlow(sender.getRemoteCredit());
if (TRACE_LOGGER.isDebugEnabled()) { if (TRACE_LOGGER.isDebugEnabled()) {
TRACE_LOGGER.debug("linkName[" + sender.getName() + "], unsettled[" + sender.getUnsettled() + "], credit[" + sender.getCredit() + "]"); TRACE_LOGGER.debug("onLinkFlow linkName[" + sender.getName() + "], unsettled[" + sender.getUnsettled() + "], credit[" + sender.getCredit() + "]");
} }
} }
} }

Просмотреть файл

@ -5,7 +5,6 @@
package com.microsoft.azure.eventhubs.impl; package com.microsoft.azure.eventhubs.impl;
import com.microsoft.azure.eventhubs.EventHubException; import com.microsoft.azure.eventhubs.EventHubException;
import com.microsoft.azure.eventhubs.TimeoutException;
import org.apache.qpid.proton.amqp.transport.ErrorCondition; import org.apache.qpid.proton.amqp.transport.ErrorCondition;
import org.apache.qpid.proton.engine.*; import org.apache.qpid.proton.engine.*;
import org.apache.qpid.proton.reactor.Reactor; import org.apache.qpid.proton.reactor.Reactor;
@ -13,6 +12,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.time.Duration;
import java.util.Iterator; import java.util.Iterator;
import java.util.Locale; import java.util.Locale;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
@ -24,18 +24,28 @@ public class SessionHandler extends BaseHandler {
private final String entityName; private final String entityName;
private final Consumer<Session> onRemoteSessionOpen; private final Consumer<Session> onRemoteSessionOpen;
private final BiConsumer<ErrorCondition, Exception> onRemoteSessionOpenError; private final BiConsumer<ErrorCondition, Exception> onRemoteSessionOpenError;
private final Duration openTimeout;
private boolean sessionCreated = false; private boolean sessionCreated = false;
private boolean sessionOpenErrorDispatched = false; private boolean sessionOpenErrorDispatched = false;
public SessionHandler(final String entityName, final Consumer<Session> onRemoteSessionOpen, final BiConsumer<ErrorCondition, Exception> onRemoteSessionOpenError) { public SessionHandler(final String entityName,
final Consumer<Session> onRemoteSessionOpen,
final BiConsumer<ErrorCondition, Exception> onRemoteSessionOpenError,
final Duration openTimeout) {
this.entityName = entityName; this.entityName = entityName;
this.onRemoteSessionOpenError = onRemoteSessionOpenError; this.onRemoteSessionOpenError = onRemoteSessionOpenError;
this.onRemoteSessionOpen = onRemoteSessionOpen; this.onRemoteSessionOpen = onRemoteSessionOpen;
this.openTimeout = openTimeout;
} }
@Override @Override
public void onSessionLocalOpen(Event e) { public void onSessionLocalOpen(Event e) {
if (TRACE_LOGGER.isInfoEnabled()) {
TRACE_LOGGER.info(String.format(Locale.US, "onSessionLocalOpen entityName[%s], condition[%s]", this.entityName,
e.getSession().getCondition() == null ? "none" : e.getSession().getCondition().toString()));
}
if (this.onRemoteSessionOpenError != null) { if (this.onRemoteSessionOpenError != null) {
ReactorHandler reactorHandler = null; ReactorHandler reactorHandler = null;
@ -53,12 +63,11 @@ public class SessionHandler extends BaseHandler {
final Session session = e.getSession(); final Session session = e.getSession();
try { try {
reactorDispatcher.invoke((int) this.openTimeout.toMillis(), new SessionTimeoutHandler(session));
reactorDispatcher.invoke(ClientConstants.SESSION_OPEN_TIMEOUT_IN_MS, new SessionTimeoutHandler(session));
} catch (IOException ignore) { } catch (IOException ignore) {
if (TRACE_LOGGER.isWarnEnabled()) { if (TRACE_LOGGER.isWarnEnabled()) {
TRACE_LOGGER.warn(String.format(Locale.US, "entityName[%s], reactorDispatcherError[%s]", this.entityName, ignore.getMessage())); TRACE_LOGGER.warn(String.format(Locale.US, "onSessionLocalOpen entityName[%s], reactorDispatcherError[%s]",
this.entityName, ignore.getMessage()));
} }
session.close(); session.close();
@ -66,8 +75,8 @@ public class SessionHandler extends BaseHandler {
null, null,
new EventHubException( new EventHubException(
false, false,
String.format("underlying IO of reactorDispatcher faulted with error: %s", ignore.getMessage()), String.format("onSessionLocalOpen entityName[%s], underlying IO of reactorDispatcher faulted with error: %s",
ignore)); this.entityName, ignore.getMessage()), ignore));
} }
} }
} }
@ -89,7 +98,6 @@ public class SessionHandler extends BaseHandler {
this.onRemoteSessionOpen.accept(session); this.onRemoteSessionOpen.accept(session);
} }
@Override @Override
public void onSessionLocalClose(Event e) { public void onSessionLocalClose(Event e) {
if (TRACE_LOGGER.isInfoEnabled()) { if (TRACE_LOGGER.isInfoEnabled()) {
@ -132,32 +140,17 @@ public class SessionHandler extends BaseHandler {
@Override @Override
public void onEvent() { public void onEvent() {
// It is supposed to close a local session to handle timeout exception.
// However, closing the session can result in NPE because of proton-j bug (https://issues.apache.org/jira/browse/PROTON-1939).
// And the bug will cause the reactor thread to stop processing pending tasks scheduled on the reactor and
// as a result task won't be completed at all.
// TODO: handle timeout error once the proton-j bug is fixed.
// notify - if connection or transport error'ed out before even session open completed
if (!sessionCreated && !sessionOpenErrorDispatched) { if (!sessionCreated && !sessionOpenErrorDispatched) {
if (TRACE_LOGGER.isWarnEnabled()) {
final Connection connection = session.getConnection(); TRACE_LOGGER.warn(String.format(Locale.US, "SessionTimeoutHandler.onEvent - session open timed out."));
if (connection != null) {
if (connection.getRemoteCondition() != null && connection.getRemoteCondition().getCondition() != null) {
session.close();
onRemoteSessionOpenError.accept(connection.getRemoteCondition(), null);
return;
}
final Transport transport = connection.getTransport();
if (transport != null && transport.getCondition() != null && transport.getCondition().getCondition() != null) {
session.close();
onRemoteSessionOpenError.accept(transport.getCondition(), null);
return;
}
} }
session.close();
onRemoteSessionOpenError.accept(null, new TimeoutException("session creation timedout."));
} }
} }
} }

Просмотреть файл

@ -24,7 +24,7 @@ final class Timer {
final ScheduledTask scheduledTask = new ScheduledTask(runnable); final ScheduledTask scheduledTask = new ScheduledTask(runnable);
final CompletableFuture<?> taskHandle = scheduledTask.getScheduledFuture(); final CompletableFuture<?> taskHandle = scheduledTask.getScheduledFuture();
try { try {
this.schedulerProvider.getReactorScheduler().invoke((int) runAfter.toMillis(), scheduledTask); this.schedulerProvider.getReactorDispatcher().invoke((int) runAfter.toMillis(), scheduledTask);
} catch (IOException | RejectedExecutionException e) { } catch (IOException | RejectedExecutionException e) {
taskHandle.completeExceptionally(e); taskHandle.completeExceptionally(e);
} }

Просмотреть файл

@ -14,7 +14,9 @@ import com.microsoft.azure.eventhubs.lib.TestContext;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import java.util.concurrent.*; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
public class EventHubClientTest extends ApiTestBase { public class EventHubClientTest extends ApiTestBase {
@ -24,7 +26,7 @@ public class EventHubClientTest extends ApiTestBase {
final String consumerGroupName = TestContext.getConsumerGroupName(); final String consumerGroupName = TestContext.getConsumerGroupName();
final String partitionId = "0"; final String partitionId = "0";
final int noOfClients = 4; final int noOfClients = 4;
final ExecutorService executorService = Executors.newSingleThreadExecutor(); final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
CompletableFuture<EventHubClient>[] createFutures = new CompletableFuture[noOfClients]; CompletableFuture<EventHubClient>[] createFutures = new CompletableFuture[noOfClients];

Просмотреть файл

@ -20,7 +20,7 @@ public class EventDataBatchTest extends ApiTestBase {
@Test(expected = PayloadSizeExceededException.class) @Test(expected = PayloadSizeExceededException.class)
public void payloadExceededException() throws EventHubException, IOException { public void payloadExceededException() throws EventHubException, IOException {
final ConnectionStringBuilder connStrBuilder = TestContext.getConnectionString(); final ConnectionStringBuilder connStrBuilder = TestContext.getConnectionString();
ehClient = EventHubClient.createSync(connStrBuilder.toString(), Executors.newSingleThreadExecutor()); ehClient = EventHubClient.createSync(connStrBuilder.toString(), Executors.newScheduledThreadPool(1));
final EventDataBatch batch = ehClient.createBatch(); final EventDataBatch batch = ehClient.createBatch();

Просмотреть файл

@ -22,16 +22,14 @@ public class MsgFactoryOpenCloseTest extends ApiTestBase {
static ConnectionStringBuilder connStr; static ConnectionStringBuilder connStr;
@BeforeClass @BeforeClass
public static void initialize() throws Exception { public static void initialize() {
connStr = TestContext.getConnectionString(); connStr = TestContext.getConnectionString();
} }
@Test() @Test()
public void VerifyTaskQueueEmptyOnMsgFactoryGracefulClose() throws Exception { public void VerifyTaskQueueEmptyOnMsgFactoryGracefulClose() throws Exception {
final LinkedBlockingQueue<Runnable> blockingQueue = new LinkedBlockingQueue<>(); final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
final ThreadPoolExecutor executor = new ThreadPoolExecutor(
1, 1, 1, TimeUnit.MINUTES, blockingQueue);
try { try {
final EventHubClient ehClient = EventHubClient.createSync( final EventHubClient ehClient = EventHubClient.createSync(
TestContext.getConnectionString().toString(), TestContext.getConnectionString().toString(),
@ -49,8 +47,7 @@ public class MsgFactoryOpenCloseTest extends ApiTestBase {
ehClient.closeSync(); ehClient.closeSync();
Assert.assertEquals(blockingQueue.size(), 0); Assert.assertEquals(((ScheduledThreadPoolExecutor) executor).getQueue().size(), 0);
Assert.assertEquals(executor.getTaskCount(), executor.getCompletedTaskCount());
} finally { } finally {
executor.shutdown(); executor.shutdown();
} }
@ -59,9 +56,8 @@ public class MsgFactoryOpenCloseTest extends ApiTestBase {
@Test() @Test()
public void VerifyTaskQueueEmptyOnMsgFactoryWithPumpGracefulClose() throws Exception { public void VerifyTaskQueueEmptyOnMsgFactoryWithPumpGracefulClose() throws Exception {
final LinkedBlockingQueue<Runnable> blockingQueue = new LinkedBlockingQueue<>(); final ScheduledExecutorService executor = new ScheduledThreadPoolExecutor(1);
final ThreadPoolExecutor executor = new ThreadPoolExecutor(
1, 1, 1, TimeUnit.MINUTES, blockingQueue);
try { try {
final EventHubClient ehClient = EventHubClient.createSync( final EventHubClient ehClient = EventHubClient.createSync(
TestContext.getConnectionString().toString(), TestContext.getConnectionString().toString(),
@ -100,8 +96,7 @@ public class MsgFactoryOpenCloseTest extends ApiTestBase {
ehClient.closeSync(); ehClient.closeSync();
Assert.assertEquals(blockingQueue.size(), 0); Assert.assertEquals(((ScheduledThreadPoolExecutor) executor).getQueue().size(), 0);
Assert.assertEquals(executor.getTaskCount(), executor.getCompletedTaskCount());
} finally { } finally {
executor.shutdown(); executor.shutdown();
} }
@ -113,9 +108,7 @@ public class MsgFactoryOpenCloseTest extends ApiTestBase {
final FaultInjectingReactorFactory networkOutageSimulator = new FaultInjectingReactorFactory(); final FaultInjectingReactorFactory networkOutageSimulator = new FaultInjectingReactorFactory();
networkOutageSimulator.setFaultType(FaultInjectingReactorFactory.FaultType.NetworkOutage); networkOutageSimulator.setFaultType(FaultInjectingReactorFactory.FaultType.NetworkOutage);
final LinkedBlockingQueue<Runnable> blockingQueue = new LinkedBlockingQueue<>(); final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
final ThreadPoolExecutor executor = new ThreadPoolExecutor(
1, 1, 1, TimeUnit.MINUTES, blockingQueue);
try { try {
final CompletableFuture<MessagingFactory> openFuture = MessagingFactory.createFromConnectionString( final CompletableFuture<MessagingFactory> openFuture = MessagingFactory.createFromConnectionString(
@ -131,8 +124,7 @@ public class MsgFactoryOpenCloseTest extends ApiTestBase {
Thread.sleep(1000); // for reactor to transition from cleanup to complete-stop Thread.sleep(1000); // for reactor to transition from cleanup to complete-stop
Assert.assertEquals(0, blockingQueue.size()); Assert.assertEquals(((ScheduledThreadPoolExecutor) executor).getQueue().size(), 0);
Assert.assertEquals(executor.getTaskCount(), executor.getCompletedTaskCount());
} finally { } finally {
executor.shutdown(); executor.shutdown();
} }
@ -140,7 +132,7 @@ public class MsgFactoryOpenCloseTest extends ApiTestBase {
@Test(expected = RejectedExecutionException.class) @Test(expected = RejectedExecutionException.class)
public void SupplyClosedExecutorServiceToEventHubClient() throws Exception { public void SupplyClosedExecutorServiceToEventHubClient() throws Exception {
final ExecutorService testClosed = Executors.newWorkStealingPool(); final ScheduledExecutorService testClosed = new ScheduledThreadPoolExecutor(1);
testClosed.shutdown(); testClosed.shutdown();
EventHubClient.createSync( EventHubClient.createSync(
@ -150,7 +142,7 @@ public class MsgFactoryOpenCloseTest extends ApiTestBase {
@Test(expected = RejectedExecutionException.class) @Test(expected = RejectedExecutionException.class)
public void SupplyClosedExecutorServiceToSendOperation() throws Exception { public void SupplyClosedExecutorServiceToSendOperation() throws Exception {
final ExecutorService testClosed = Executors.newWorkStealingPool(); final ScheduledExecutorService testClosed = Executors.newScheduledThreadPool(1);
final EventHubClient temp = EventHubClient.createSync( final EventHubClient temp = EventHubClient.createSync(
TestContext.getConnectionString().toString(), TestContext.getConnectionString().toString(),
@ -165,7 +157,7 @@ public class MsgFactoryOpenCloseTest extends ApiTestBase {
@Test(expected = RejectedExecutionException.class) @Test(expected = RejectedExecutionException.class)
public void SupplyClosedExecutorServiceToReceiveOperation() throws Exception { public void SupplyClosedExecutorServiceToReceiveOperation() throws Exception {
final ExecutorService testClosed = Executors.newWorkStealingPool(); final ScheduledExecutorService testClosed = new ScheduledThreadPoolExecutor(1);
final PartitionReceiver temp = EventHubClient.createSync( final PartitionReceiver temp = EventHubClient.createSync(
TestContext.getConnectionString().toString(), TestContext.getConnectionString().toString(),
@ -180,7 +172,7 @@ public class MsgFactoryOpenCloseTest extends ApiTestBase {
@Test(expected = RejectedExecutionException.class) @Test(expected = RejectedExecutionException.class)
public void SupplyClosedExecutorServiceToCreateLinkOperation() throws Exception { public void SupplyClosedExecutorServiceToCreateLinkOperation() throws Exception {
final ExecutorService testClosed = Executors.newWorkStealingPool(); final ScheduledExecutorService testClosed = Executors.newScheduledThreadPool(1);
final EventHubClient temp = EventHubClient.createSync( final EventHubClient temp = EventHubClient.createSync(
TestContext.getConnectionString().toString(), TestContext.getConnectionString().toString(),
@ -195,7 +187,7 @@ public class MsgFactoryOpenCloseTest extends ApiTestBase {
@Test(expected = RejectedExecutionException.class) @Test(expected = RejectedExecutionException.class)
public void SupplyClosedExecutorServiceToCreateSenderOperation() throws Exception { public void SupplyClosedExecutorServiceToCreateSenderOperation() throws Exception {
final ExecutorService testClosed = Executors.newWorkStealingPool(); final ScheduledExecutorService testClosed = new ScheduledThreadPoolExecutor(1);
final EventHubClient temp = EventHubClient.createSync( final EventHubClient temp = EventHubClient.createSync(
TestContext.getConnectionString().toString(), TestContext.getConnectionString().toString(),
@ -209,7 +201,7 @@ public class MsgFactoryOpenCloseTest extends ApiTestBase {
@Test(expected = RejectedExecutionException.class) @Test(expected = RejectedExecutionException.class)
public void SupplyClosedExecutorServiceToCreateReceiverOperation() throws Exception { public void SupplyClosedExecutorServiceToCreateReceiverOperation() throws Exception {
final ExecutorService testClosed = Executors.newWorkStealingPool(); final ScheduledExecutorService testClosed = Executors.newScheduledThreadPool(1);
final EventHubClient temp = EventHubClient.createSync( final EventHubClient temp = EventHubClient.createSync(
TestContext.getConnectionString().toString(), TestContext.getConnectionString().toString(),
@ -223,7 +215,7 @@ public class MsgFactoryOpenCloseTest extends ApiTestBase {
@Test(expected = RejectedExecutionException.class) @Test(expected = RejectedExecutionException.class)
public void SupplyClosedExecutorServiceThenMgmtOperation() throws Throwable { public void SupplyClosedExecutorServiceThenMgmtOperation() throws Throwable {
final ExecutorService testClosed = Executors.newWorkStealingPool(); final ScheduledThreadPoolExecutor testClosed = new ScheduledThreadPoolExecutor(1);
final EventHubClient temp = EventHubClient.createSync( final EventHubClient temp = EventHubClient.createSync(
TestContext.getConnectionString().toString(), TestContext.getConnectionString().toString(),
@ -241,7 +233,7 @@ public class MsgFactoryOpenCloseTest extends ApiTestBase {
@Test(expected = RejectedExecutionException.class) @Test(expected = RejectedExecutionException.class)
public void SupplyClosedExecutorServiceThenFactoryCloseOperation() throws Exception { public void SupplyClosedExecutorServiceThenFactoryCloseOperation() throws Exception {
final ExecutorService testClosed = Executors.newWorkStealingPool(); final ScheduledExecutorService testClosed = Executors.newScheduledThreadPool(1);
final EventHubClient temp = EventHubClient.createSync( final EventHubClient temp = EventHubClient.createSync(
TestContext.getConnectionString().toString(), TestContext.getConnectionString().toString(),
@ -255,7 +247,7 @@ public class MsgFactoryOpenCloseTest extends ApiTestBase {
@Test(expected = RejectedExecutionException.class) @Test(expected = RejectedExecutionException.class)
public void SupplyClosedExecutorServiceThenSenderCloseOperation() throws Exception { public void SupplyClosedExecutorServiceThenSenderCloseOperation() throws Exception {
final ExecutorService testClosed = Executors.newWorkStealingPool(); final ScheduledThreadPoolExecutor testClosed = new ScheduledThreadPoolExecutor(1);
final PartitionSender temp = EventHubClient.createSync( final PartitionSender temp = EventHubClient.createSync(
TestContext.getConnectionString().toString(), TestContext.getConnectionString().toString(),
@ -269,7 +261,7 @@ public class MsgFactoryOpenCloseTest extends ApiTestBase {
@Test(expected = RejectedExecutionException.class) @Test(expected = RejectedExecutionException.class)
public void SupplyClosedExecutorServiceThenReceiverCloseOperation() throws Exception { public void SupplyClosedExecutorServiceThenReceiverCloseOperation() throws Exception {
final ExecutorService testClosed = Executors.newWorkStealingPool(); final ScheduledExecutorService testClosed = Executors.newScheduledThreadPool(1);
final PartitionReceiver temp = EventHubClient.createSync( final PartitionReceiver temp = EventHubClient.createSync(
TestContext.getConnectionString().toString(), TestContext.getConnectionString().toString(),

Просмотреть файл

@ -12,6 +12,7 @@ import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class ReactorFaultTest extends ApiTestBase { public class ReactorFaultTest extends ApiTestBase {
@ -26,27 +27,38 @@ public class ReactorFaultTest extends ApiTestBase {
@Test() @Test()
public void VerifyReactorRestartsOnProtonBugs() throws Exception { public void VerifyReactorRestartsOnProtonBugs() throws Exception {
final EventHubClient eventHubClient = EventHubClient.createSync(connStr.toString(), TestContext.EXECUTOR_SERVICE); final EventHubClient eventHubClient = EventHubClient.createSync(connStr.toString(), TestContext.EXECUTOR_SERVICE);
try { try {
final PartitionReceiver partitionReceiver = eventHubClient.createEpochReceiverSync( final PartitionReceiver partitionReceiver = eventHubClient.createEpochReceiverSync(
"$default", "0", EventPosition.fromStartOfStream(), System.currentTimeMillis()); "$default", "0", EventPosition.fromStartOfStream(), System.currentTimeMillis());
partitionReceiver.receiveSync(100);
Executors.newScheduledThreadPool(1).schedule(new Runnable() {
@Override
public void run() {
try {
final Field factoryField = EventHubClientImpl.class.getDeclaredField("underlyingFactory");
factoryField.setAccessible(true);
final MessagingFactory underlyingFactory = (MessagingFactory) factoryField.get(eventHubClient);
final Field reactorField = MessagingFactory.class.getDeclaredField("reactor");
reactorField.setAccessible(true);
final Reactor reactor = (Reactor) reactorField.get(underlyingFactory);
org.apache.qpid.proton.engine.Handler handler = reactor.getHandler();
handler.add(new BaseHandler() {
@Override
public void handle(org.apache.qpid.proton.engine.Event e) {
throw new NullPointerException();
}
});
} catch (Exception e) {
Assert.fail(e.getMessage());
}
}
}, 2, TimeUnit.SECONDS);
try { try {
final Field factoryField = EventHubClientImpl.class.getDeclaredField("underlyingFactory"); Thread.sleep(4000);
factoryField.setAccessible(true);
final MessagingFactory underlyingFactory = (MessagingFactory) factoryField.get(eventHubClient);
final Field reactorField = MessagingFactory.class.getDeclaredField("reactor");
reactorField.setAccessible(true);
final Reactor reactor = (Reactor) reactorField.get(underlyingFactory);
org.apache.qpid.proton.engine.Handler handler = reactor.getHandler();
handler.add(new BaseHandler() {
@Override
public void handle(org.apache.qpid.proton.engine.Event e) {
throw new NullPointerException();
}
});
final Iterable<EventData> events = partitionReceiver.receiveSync(100); final Iterable<EventData> events = partitionReceiver.receiveSync(100);
Assert.assertTrue(events != null && events.iterator().hasNext()); Assert.assertTrue(events != null && events.iterator().hasNext());

Просмотреть файл

@ -6,11 +6,13 @@ package com.microsoft.azure.eventhubs.lib;
import com.microsoft.azure.eventhubs.ConnectionStringBuilder; import com.microsoft.azure.eventhubs.ConnectionStringBuilder;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
public final class TestContext { public final class TestContext {
public final static ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor();
public final static ScheduledExecutorService EXECUTOR_SERVICE = Executors.newScheduledThreadPool(1);
final static String EVENT_HUB_CONNECTION_STRING_ENV_NAME = "EVENT_HUB_CONNECTION_STRING"; final static String EVENT_HUB_CONNECTION_STRING_ENV_NAME = "EVENT_HUB_CONNECTION_STRING";

Просмотреть файл

@ -32,6 +32,7 @@ public class ReceivePumpTest {
public void testPumpOnReceiveEventFlow() throws Exception { public void testPumpOnReceiveEventFlow() throws Exception {
final CompletableFuture<Void> pumpRun = new CompletableFuture<>(); final CompletableFuture<Void> pumpRun = new CompletableFuture<>();
final ReceivePump receivePump = new ReceivePump( final ReceivePump receivePump = new ReceivePump(
"eventhub1", "consumerGroup1",
new ReceivePump.IPartitionReceiver() { new ReceivePump.IPartitionReceiver() {
@Override @Override
public CompletableFuture<Iterable<EventData>> receive(int maxBatchSize) { public CompletableFuture<Iterable<EventData>> receive(int maxBatchSize) {
@ -82,6 +83,7 @@ public class ReceivePumpTest {
public void testPumpReceiveTransientErrorsPropagated() throws Exception { public void testPumpReceiveTransientErrorsPropagated() throws Exception {
final CompletableFuture<Void> pumpRun = new CompletableFuture<>(); final CompletableFuture<Void> pumpRun = new CompletableFuture<>();
final ReceivePump receivePump = new ReceivePump( final ReceivePump receivePump = new ReceivePump(
"eventhub1", "consumerGroup1",
new ReceivePump.IPartitionReceiver() { new ReceivePump.IPartitionReceiver() {
@Override @Override
public CompletableFuture<Iterable<EventData>> receive(int maxBatchSize) { public CompletableFuture<Iterable<EventData>> receive(int maxBatchSize) {
@ -128,6 +130,7 @@ public class ReceivePumpTest {
public void testPumpReceiveExceptionsPropagated() throws Exception { public void testPumpReceiveExceptionsPropagated() throws Exception {
final CompletableFuture<Void> pumpRun = new CompletableFuture<>(); final CompletableFuture<Void> pumpRun = new CompletableFuture<>();
final ReceivePump receivePump = new ReceivePump( final ReceivePump receivePump = new ReceivePump(
"eventhub1", "consumerGroup1",
new ReceivePump.IPartitionReceiver() { new ReceivePump.IPartitionReceiver() {
@Override @Override
public CompletableFuture<Iterable<EventData>> receive(int maxBatchSize) { public CompletableFuture<Iterable<EventData>> receive(int maxBatchSize) {
@ -175,6 +178,7 @@ public class ReceivePumpTest {
final String runtimeExceptionMsg = "random exception"; final String runtimeExceptionMsg = "random exception";
final CompletableFuture<Void> pumpRun = new CompletableFuture<>(); final CompletableFuture<Void> pumpRun = new CompletableFuture<>();
final ReceivePump receivePump = new ReceivePump( final ReceivePump receivePump = new ReceivePump(
"eventhub1", "consumerGroup1",
new ReceivePump.IPartitionReceiver() { new ReceivePump.IPartitionReceiver() {
@Override @Override
public CompletableFuture<Iterable<EventData>> receive(int maxBatchSize) { public CompletableFuture<Iterable<EventData>> receive(int maxBatchSize) {

Просмотреть файл

@ -50,7 +50,7 @@ public class RequestResponseTest extends ApiTestBase {
@Test() @Test()
public void testRequestResponse() throws Exception { public void testRequestResponse() throws Exception {
final ReactorDispatcher dispatcher = factory.getReactorScheduler(); final ReactorDispatcher dispatcher = factory.getReactorDispatcher();
final RequestResponseChannel requestResponseChannel = new RequestResponseChannel( final RequestResponseChannel requestResponseChannel = new RequestResponseChannel(
"reqresp", "reqresp",
ClientConstants.MANAGEMENT_ADDRESS, ClientConstants.MANAGEMENT_ADDRESS,