Skip to content

Commit

Permalink
GH-9866: Fix FluxMessageChannel for race condition on destroy
Browse files Browse the repository at this point in the history
Fixes: #9866
Issue link: #9866

Due to race condition between `sink.tryEmitNext()` and `sink.emitComplete()``,
there could be a situation when `onNext` signal slips after `onComplete`.
Appears on fast producers into this `FluxMessageChannel`.
That leads to error like `Spec. Rule 1.3 - onSubscribe, onNext, onError and onComplete signaled to a Subscriber MUST be signaled serially.`

* Fix `FluxMessageChannel` to check for `this.active` one more time just before `sink.tryEmitNext()` call
* Also mitigate a race condition with a `Sinks.EmitFailureHandler.busyLooping(Duration.ofSeconds(1))` in the `destroy()`
instead of `Sinks.EmitFailureHandler.FAIL_FAST`.
The `busyLooping()` would retry requested `onComplete()` signal for that specific error until success or timeout

(cherry picked from commit 7680e02)
  • Loading branch information
artembilan authored and spring-builds committed Feb 26, 2025
1 parent 3a3ea9b commit 33894dc
Showing 1 changed file with 17 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2015-2024 the original author or authors.
* Copyright 2015-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -86,15 +86,21 @@ private boolean tryEmitMessage(Message<?> message) {
.setHeader(IntegrationMessageHeaderAccessor.REACTOR_CONTEXT, contextView)
.build();
}
return switch (this.sink.tryEmitNext(messageToEmit)) {
case OK -> true;
case FAIL_NON_SERIALIZED, FAIL_OVERFLOW -> false;
case FAIL_ZERO_SUBSCRIBER ->
throw new IllegalStateException("The [" + this + "] doesn't have subscribers to accept messages");
case FAIL_TERMINATED, FAIL_CANCELLED ->
throw new IllegalStateException("Cannot emit messages into the cancelled or terminated sink: "
+ this.sink);
};

if (this.active) {
return switch (this.sink.tryEmitNext(messageToEmit)) {
case OK -> true;
case FAIL_NON_SERIALIZED, FAIL_OVERFLOW -> false;
case FAIL_ZERO_SUBSCRIBER ->
throw new IllegalStateException("The [" + this + "] doesn't have subscribers to accept messages");
case FAIL_TERMINATED, FAIL_CANCELLED ->
throw new IllegalStateException("Cannot emit messages into the cancelled or terminated sink: "
+ this.sink);
};
}
else {
return false;
}
}

@Override
Expand Down Expand Up @@ -171,7 +177,7 @@ private void sendReactiveMessage(Message<?> message) {
public void destroy() {
this.active = false;
this.upstreamSubscriptions.dispose();
this.sink.emitComplete(Sinks.EmitFailureHandler.FAIL_FAST);
this.sink.emitComplete(Sinks.EmitFailureHandler.busyLooping(Duration.ofSeconds(1)));
super.destroy();
}

Expand Down

0 comments on commit 33894dc

Please sign in to comment.