1
1
/*
2
- * Copyright (c) 2016-2023 VMware Inc. or its affiliates, All Rights Reserved.
2
+ * Copyright (c) 2016-2025 VMware Inc. or its affiliates, All Rights Reserved.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
57
57
* @author Stephane Maldini
58
58
*/
59
59
final class SinkManyEmitterProcessor <T > extends Flux <T > implements InternalManySink <T >,
60
- Sinks .ManyWithUpstream <T >, CoreSubscriber <T >, Scannable , Disposable , ContextHolder {
60
+ Sinks .ManyWithUpstream <T >, CoreSubscriber <T >, Scannable , Disposable , ContextHolder {
61
61
62
62
@ SuppressWarnings ("rawtypes" )
63
63
static final FluxPublish .PubSubInner [] EMPTY = new FluxPublish .PublishInner [0 ];
@@ -201,6 +201,9 @@ public void onComplete() {
201
201
202
202
@ Override
203
203
public EmitResult tryEmitComplete () {
204
+ if (isCancelled ()) {
205
+ return EmitResult .FAIL_CANCELLED ;
206
+ }
204
207
if (done ) {
205
208
return EmitResult .FAIL_TERMINATED ;
206
209
}
@@ -217,6 +220,9 @@ public void onError(Throwable throwable) {
217
220
@ Override
218
221
public EmitResult tryEmitError (Throwable t ) {
219
222
Objects .requireNonNull (t , "tryEmitError must be invoked with a non-null Throwable" );
223
+ if (isCancelled ()) {
224
+ return EmitResult .FAIL_CANCELLED ;
225
+ }
220
226
if (done ) {
221
227
return EmitResult .FAIL_TERMINATED ;
222
228
}
@@ -241,6 +247,9 @@ public void onNext(T t) {
241
247
242
248
@ Override
243
249
public EmitResult tryEmitNext (T t ) {
250
+ if (isCancelled ()) {
251
+ return EmitResult .FAIL_CANCELLED ;
252
+ }
244
253
if (done ) {
245
254
return Sinks .EmitResult .FAIL_TERMINATED ;
246
255
}
@@ -271,6 +280,23 @@ public EmitResult tryEmitNext(T t) {
271
280
return subscribers == EMPTY ? EmitResult .FAIL_ZERO_SUBSCRIBER : EmitResult .FAIL_OVERFLOW ;
272
281
}
273
282
drain ();
283
+
284
+ // This final check is critical for handling a race between this emit operation
285
+ // and a concurrent cancellation from another thread.
286
+ //
287
+ // The race condition scenario:
288
+ // 1. This thread passes the initial isCancelled() check at the top of the method.
289
+ // 2. This thread successfully offers an item to the queue.
290
+ // 3. Concurrently, another thread disposes the last subscriber, which cancels the sink
291
+ // and triggers a drain that cleans up the just-offered item.
292
+ //
293
+ // Without this check, we would return EmitResult.OK, but the item has already been
294
+ // discarded. This check ensures we accurately report FAIL_CANCELLED, reflecting
295
+ // the final state of the operation.
296
+ if (isCancelled ()) {
297
+ return EmitResult .FAIL_CANCELLED ;
298
+ }
299
+
274
300
return EmitResult .OK ;
275
301
}
276
302
@@ -382,7 +408,7 @@ public Object scanUnsafe(Attr key) {
382
408
return null ;
383
409
}
384
410
385
- final void drain () {
411
+ void drain () {
386
412
if (WIP .getAndIncrement (this ) != 0 ) {
387
413
return ;
388
414
}
@@ -397,11 +423,9 @@ final void drain() {
397
423
398
424
boolean empty = q == null || q .isEmpty ();
399
425
400
- if (checkTerminated (d , empty )) {
401
- return ;
402
- }
426
+ checkTerminated (d , empty );
403
427
404
- FluxPublish .PubSubInner <T >[] a = subscribers ;
428
+ FluxPublish .PubSubInner <T >[] a = subscribers ;
405
429
406
430
if (a != EMPTY && !empty ) {
407
431
long maxRequested = Long .MAX_VALUE ;
@@ -431,10 +455,8 @@ final void drain() {
431
455
d = true ;
432
456
v = null ;
433
457
}
434
- if (checkTerminated (d , v == null )) {
435
- return ;
436
- }
437
- if (sourceMode != Fuseable .SYNC ) {
458
+ checkTerminated (d , v == null );
459
+ if (sourceMode != Fuseable .SYNC ) {
438
460
s .request (1 );
439
461
}
440
462
continue ;
@@ -458,11 +480,9 @@ final void drain() {
458
480
459
481
empty = v == null ;
460
482
461
- if (checkTerminated (d , empty )) {
462
- return ;
463
- }
483
+ checkTerminated (d , empty );
464
484
465
- if (empty ) {
485
+ if (empty ) {
466
486
//async mode only needs to break but SYNC mode needs to perform terminal cleanup here...
467
487
if (sourceMode == Fuseable .SYNC ) {
468
488
//the q is empty
@@ -494,10 +514,8 @@ final void drain() {
494
514
}
495
515
else if ( sourceMode == Fuseable .SYNC ) {
496
516
done = true ;
497
- if (checkTerminated (true , empty )) { //empty can be true if no subscriber
498
- break ;
499
- }
500
- }
517
+ checkTerminated (true , empty );//empty can be true if no subscriber
518
+ }
501
519
502
520
missed = WIP .addAndGet (this , -missed );
503
521
if (missed == 0 ) {
@@ -544,7 +562,7 @@ else if (empty) {
544
562
return false ;
545
563
}
546
564
547
- final boolean add (EmitterInner <T > inner ) {
565
+ boolean add (EmitterInner <T > inner ) {
548
566
for (; ; ) {
549
567
FluxPublish .PubSubInner <T >[] a = subscribers ;
550
568
if (a == TERMINATED ) {
@@ -560,7 +578,7 @@ final boolean add(EmitterInner<T> inner) {
560
578
}
561
579
}
562
580
563
- final void remove (FluxPublish .PubSubInner <T > inner ) {
581
+ void remove (FluxPublish .PubSubInner <T > inner ) {
564
582
for (; ; ) {
565
583
FluxPublish .PubSubInner <T >[] a = subscribers ;
566
584
if (a == TERMINATED || a == EMPTY ) {
@@ -591,14 +609,11 @@ final void remove(FluxPublish.PubSubInner<T> inner) {
591
609
if (SUBSCRIBERS .compareAndSet (this , a , b )) {
592
610
//contrary to FluxPublish, there is a possibility of auto-cancel, which
593
611
//happens when the removed inner makes the subscribers array EMPTY
594
- if (autoCancel && b == EMPTY && Operators .terminate (S , this )) {
595
- if (WIP .getAndIncrement (this ) != 0 ) {
596
- return ;
597
- }
598
- terminate ();
599
- Queue <T > q = queue ;
600
- if (q != null ) {
601
- q .clear ();
612
+ if (autoCancel && b == EMPTY && !isCancelled ()) {
613
+ if (Operators .terminate (S , this )) {
614
+ // The state is now CANCELLED.
615
+ // Trigger a drain so the serialized drain-loop can perform the cleanup
616
+ drain ();
602
617
}
603
618
}
604
619
return ;
@@ -653,5 +668,4 @@ public void dispose() {
653
668
}
654
669
}
655
670
656
-
657
671
}
0 commit comments