@@ -148,7 +148,7 @@ static final class PublishSubscriber<T>
148
148
final int bufferSize ;
149
149
150
150
/** Tracks the subscribed InnerSubscribers. */
151
- final AtomicReference <InnerSubscriber []> subscribers ;
151
+ final AtomicReference <InnerSubscriber < T > []> subscribers ;
152
152
/**
153
153
* Atomically changed from false to true by connect to make sure the
154
154
* connection is only performed by one thread.
@@ -165,8 +165,9 @@ static final class PublishSubscriber<T>
165
165
/** Holds notifications from upstream. */
166
166
volatile SimpleQueue <T > queue ;
167
167
168
+ @ SuppressWarnings ("unchecked" )
168
169
PublishSubscriber (AtomicReference <PublishSubscriber <T >> current , int bufferSize ) {
169
- this .subscribers = new AtomicReference <InnerSubscriber []>(EMPTY );
170
+ this .subscribers = new AtomicReference <InnerSubscriber < T > []>(EMPTY );
170
171
this .current = current ;
171
172
this .shouldConnect = new AtomicBoolean ();
172
173
this .bufferSize = bufferSize ;
@@ -175,6 +176,7 @@ static final class PublishSubscriber<T>
175
176
@ Override
176
177
public void dispose () {
177
178
if (subscribers .get () != TERMINATED ) {
179
+ @ SuppressWarnings ("unchecked" )
178
180
InnerSubscriber [] ps = subscribers .getAndSet (TERMINATED );
179
181
if (ps != TERMINATED ) {
180
182
current .compareAndSet (PublishSubscriber .this , null );
@@ -263,15 +265,16 @@ boolean add(InnerSubscriber<T> producer) {
263
265
// the state can change so we do a CAS loop to achieve atomicity
264
266
for (;;) {
265
267
// get the current producer array
266
- InnerSubscriber [] c = subscribers .get ();
268
+ InnerSubscriber < T > [] c = subscribers .get ();
267
269
// if this subscriber-to-source reached a terminal state by receiving
268
270
// an onError or onComplete, just refuse to add the new producer
269
271
if (c == TERMINATED ) {
270
272
return false ;
271
273
}
272
274
// we perform a copy-on-write logic
273
275
int len = c .length ;
274
- InnerSubscriber [] u = new InnerSubscriber [len + 1 ];
276
+ @ SuppressWarnings ("unchecked" )
277
+ InnerSubscriber <T >[] u = new InnerSubscriber [len + 1 ];
275
278
System .arraycopy (c , 0 , u , 0 , len );
276
279
u [len ] = producer ;
277
280
// try setting the subscribers array
@@ -287,11 +290,12 @@ boolean add(InnerSubscriber<T> producer) {
287
290
* Atomically removes the given InnerSubscriber from the subscribers array.
288
291
* @param producer the producer to remove
289
292
*/
293
+ @ SuppressWarnings ("unchecked" )
290
294
void remove (InnerSubscriber <T > producer ) {
291
295
// the state can change so we do a CAS loop to achieve atomicity
292
296
for (;;) {
293
297
// let's read the current subscribers array
294
- InnerSubscriber [] c = subscribers .get ();
298
+ InnerSubscriber < T > [] c = subscribers .get ();
295
299
int len = c .length ;
296
300
// if it is either empty or terminated, there is nothing to remove so we quit
297
301
if (len == 0 ) {
@@ -311,7 +315,7 @@ void remove(InnerSubscriber<T> producer) {
311
315
return ;
312
316
}
313
317
// we do copy-on-write logic here
314
- InnerSubscriber [] u ;
318
+ InnerSubscriber < T > [] u ;
315
319
// we don't create a new empty array if producer was the single inhabitant
316
320
// but rather reuse an empty array
317
321
if (len == 1 ) {
@@ -340,6 +344,7 @@ void remove(InnerSubscriber<T> producer) {
340
344
* @param empty set to true if the queue is empty
341
345
* @return true if there is indeed a terminal condition
342
346
*/
347
+ @ SuppressWarnings ("unchecked" )
343
348
boolean checkTerminated (Object term , boolean empty ) {
344
349
// first of all, check if there is actually a terminal event
345
350
if (term != null ) {
@@ -404,6 +409,17 @@ void dispatch() {
404
409
return ;
405
410
}
406
411
int missed = 1 ;
412
+
413
+ // saving a local copy because this will be accessed after every item
414
+ // delivered to detect changes in the subscribers due to an onNext
415
+ // and thus not dropping items
416
+ AtomicReference <InnerSubscriber <T >[]> subscribers = this .subscribers ;
417
+
418
+ // We take a snapshot of the current child subscribers.
419
+ // Concurrent subscribers may miss this iteration, but it is to be expected
420
+ InnerSubscriber <T >[] ps = subscribers .get ();
421
+
422
+ outer :
407
423
for (;;) {
408
424
/*
409
425
* We need to read terminalEvent before checking the queue for emptiness because
@@ -434,10 +450,6 @@ void dispatch() {
434
450
// this loop is the only one which can turn a non-empty queue into an empty one
435
451
// and as such, no need to ask the queue itself again for that.
436
452
if (!empty ) {
437
- // We take a snapshot of the current child subscribers.
438
- // Concurrent subscribers may miss this iteration, but it is to be expected
439
- @ SuppressWarnings ("unchecked" )
440
- InnerSubscriber <T >[] ps = subscribers .get ();
441
453
442
454
int len = ps .length ;
443
455
// Let's assume everyone requested the maximum value.
@@ -452,14 +464,11 @@ void dispatch() {
452
464
long r = ip .get ();
453
465
// if there is one child subscriber that hasn't requested yet
454
466
// we can't emit anything to anyone
455
- if (r >= 0L ) {
456
- maxRequested = Math .min (maxRequested , r );
457
- } else
458
- // cancellation is indicated by a special value
459
- if (r == CANCELLED ) {
467
+ if (r != CANCELLED ) {
468
+ maxRequested = Math .min (maxRequested , r - ip .emitted );
469
+ } else {
460
470
cancelled ++;
461
471
}
462
- // we ignore those with NOT_REQUESTED as if they aren't even there
463
472
}
464
473
465
474
// it may happen everyone has cancelled between here and subscribers.get()
@@ -518,20 +527,36 @@ void dispatch() {
518
527
}
519
528
// we need to unwrap potential nulls
520
529
T value = NotificationLite .getValue (v );
530
+
531
+ boolean subscribersChanged = false ;
532
+
521
533
// let's emit this value to all child subscribers
522
534
for (InnerSubscriber <T > ip : ps ) {
523
535
// if ip.get() is negative, the child has either cancelled in the
524
536
// meantime or hasn't requested anything yet
525
537
// this eager behavior will skip cancelled children in case
526
538
// multiple values are available in the queue
527
- if (ip .get () > 0L ) {
539
+ long ipr = ip .get ();
540
+ if (ipr != CANCELLED ) {
541
+ if (ipr != Long .MAX_VALUE ) {
542
+ // indicate this child has received 1 element
543
+ ip .emitted ++;
544
+ }
528
545
ip .child .onNext (value );
529
- // indicate this child has received 1 element
530
- ip . produced ( 1 ) ;
546
+ } else {
547
+ subscribersChanged = true ;
531
548
}
532
549
}
533
550
// indicate we emitted one element
534
551
d ++;
552
+
553
+ // see if the array of subscribers changed as a consequence
554
+ // of emission or concurrent activity
555
+ InnerSubscriber <T >[] freshArray = subscribers .get ();
556
+ if (subscribersChanged || freshArray != ps ) {
557
+ ps = freshArray ;
558
+ continue outer ;
559
+ }
535
560
}
536
561
537
562
// if we did emit at least one element, request more to replenish the queue
@@ -552,6 +577,9 @@ void dispatch() {
552
577
if (missed == 0 ) {
553
578
break ;
554
579
}
580
+
581
+ // get a fresh copy of the current subscribers
582
+ ps = subscribers .get ();
555
583
}
556
584
}
557
585
}
@@ -571,6 +599,9 @@ static final class InnerSubscriber<T> extends AtomicLong implements Subscription
571
599
*/
572
600
volatile PublishSubscriber <T > parent ;
573
601
602
+ /** Track the number of emitted items (avoids decrementing the request counter). */
603
+ long emitted ;
604
+
574
605
InnerSubscriber (Subscriber <? super T > child ) {
575
606
this .child = child ;
576
607
}
@@ -586,15 +617,6 @@ public void request(long n) {
586
617
}
587
618
}
588
619
589
- /**
590
- * Indicate that values have been emitted to this child subscriber by the dispatch() method.
591
- * @param n the number of items emitted
592
- * @return the updated request value (may indicate how much can be produced or a terminal state)
593
- */
594
- public long produced (long n ) {
595
- return BackpressureHelper .producedCancel (this , n );
596
- }
597
-
598
620
@ Override
599
621
public void cancel () {
600
622
long r = get ();
0 commit comments