-
Notifications
You must be signed in to change notification settings - Fork 2.1k
SyncClient: Bug fix - Multiple Receive on same client #10734
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
7672638
160171f
54a03f3
58a53d3
ff89717
52bb661
57fae63
e13c33e
0301202
e8a5345
baacc2e
38cd751
99418e7
072ea0c
3eb41e2
66e580f
0796a28
6e73d01
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,8 @@ | |
| import com.azure.messaging.servicebus.models.DeadLetterOptions; | ||
| import com.azure.messaging.servicebus.models.ReceiveAsyncOptions; | ||
| import com.azure.messaging.servicebus.models.ReceiveMode; | ||
| import reactor.core.Disposable; | ||
| import reactor.core.publisher.EmitterProcessor; | ||
| import reactor.core.publisher.Flux; | ||
| import reactor.core.publisher.FluxSink; | ||
|
|
||
|
|
@@ -17,6 +19,7 @@ | |
| import java.util.Map; | ||
| import java.util.Objects; | ||
| import java.util.concurrent.atomic.AtomicInteger; | ||
| import java.util.concurrent.atomic.AtomicReference; | ||
|
|
||
| /** | ||
| * A <b>synchronous</b> receiver responsible for receiving {@link ServiceBusReceivedMessage} from a specific queue or | ||
|
|
@@ -38,6 +41,9 @@ public final class ServiceBusReceiverClient implements AutoCloseable { | |
| .setIsAutoCompleteEnabled(false) | ||
| .setMaxAutoLockRenewalDuration(Duration.ZERO); | ||
|
|
||
| private final AtomicReference<EmitterProcessor<ServiceBusReceivedMessageContext>> messageProcessor = | ||
| new AtomicReference<>(); | ||
| private final AtomicReference<Disposable> messageProcessorSubscription = new AtomicReference<>(); | ||
| /** | ||
| * Creates a synchronous receiver given its asynchronous counterpart. | ||
| * | ||
|
|
@@ -503,7 +509,6 @@ public IterableStream<ServiceBusReceivedMessageContext> receive(int maxMessages, | |
|
|
||
| final Flux<ServiceBusReceivedMessageContext> messages = Flux.create(emitter -> queueWork(maxMessages, | ||
| maxWaitTime, emitter)); | ||
|
|
||
| return new IterableStream<>(messages); | ||
| } | ||
|
|
||
|
|
@@ -622,20 +627,61 @@ public void setSessionState(String sessionId, byte[] sessionState) { | |
| @Override | ||
| public void close() { | ||
| asyncClient.close(); | ||
|
|
||
| if (messageProcessor.get() != null && !messageProcessor.get().isDisposed()) { | ||
| messageProcessor.get().dispose(); | ||
| } | ||
|
|
||
| Disposable activeSubscription = messageProcessorSubscription.get(); | ||
| if (activeSubscription != null && !activeSubscription.isDisposed()) { | ||
| activeSubscription.dispose(); | ||
| } | ||
|
||
| } | ||
|
|
||
| /** | ||
| * Given an {@code emitter}, queues that work in {@link SynchronousMessageSubscriber}. If the synchronous job has | ||
| * not been created, will initialise it. | ||
| * Given an {@code emitter}, creates a {@link EmitterProcessor} to receive messages from Service Bus. If the | ||
| * message processor has not been created, will initialise it. | ||
| */ | ||
| private void queueWork(int maximumMessageCount, Duration maxWaitTime, | ||
| FluxSink<ServiceBusReceivedMessageContext> emitter) { | ||
hemanttanwar marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| final long id = idGenerator.getAndIncrement(); | ||
| final SynchronousReceiveWork work = new SynchronousReceiveWork(id, maximumMessageCount, maxWaitTime, | ||
| emitter); | ||
| final SynchronousMessageSubscriber syncSubscriber = new SynchronousMessageSubscriber(work); | ||
|
|
||
| logger.info("[{}]: Started synchronous message subscriber.", id); | ||
| asyncClient.receive(DEFAULT_RECEIVE_OPTIONS).subscribeWith(syncSubscriber); | ||
| if (messageProcessor.get() != null && messageProcessor.get().isDisposed()) { | ||
|
||
| logger.error("[{}]: Can not receive messaged because client is closed.", asyncClient.getEntityPath()); | ||
| return; | ||
| } | ||
|
|
||
| if (messageProcessor.get() == null) { | ||
| logger.info("[{}]: Creating EmitterProcessor message processor for entity.", asyncClient.getEntityPath()); | ||
|
|
||
| EmitterProcessor<ServiceBusReceivedMessageContext> newProcessor = asyncClient.receive(DEFAULT_RECEIVE_OPTIONS) | ||
| .subscribeWith(EmitterProcessor.create(false)); | ||
|
|
||
| // if some other thread have come in between, we will dispose new processor | ||
| if (!messageProcessor.compareAndSet(null, newProcessor)) { | ||
| newProcessor.dispose(); | ||
| } | ||
|
|
||
| logger.info("[{}]: Started EmitterProcessor message processor for entity.", | ||
| asyncClient.getEntityPath()); | ||
| } | ||
|
|
||
| Disposable newSubscription = messageProcessor.get() | ||
| .take(maximumMessageCount) | ||
| .timeout(maxWaitTime) | ||
|
||
| .map(message -> { | ||
| emitter.next(message); | ||
| return message; | ||
| }) | ||
| .subscribe(message -> {}, | ||
| error -> { | ||
| logger.error("Error occurred while receiving messages.", error); | ||
| emitter.error(error); | ||
| }, | ||
| () -> emitter.complete()); | ||
|
|
||
| Disposable oldSubscription = messageProcessorSubscription.getAndSet(newSubscription); | ||
| if (oldSubscription != null && !oldSubscription.isDisposed()) { | ||
|
||
| oldSubscription.dispose(); | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Between
isDisposed()check anddispose()the processor might have changed state. This is not an atomic operation, if that's what you were trying to achieve using the AtomicReference.