@@ -381,11 +381,6 @@ internal sealed class ObserveOnObserverNew<T> : IdentitySink<T>
381
381
{
382
382
private readonly IScheduler _scheduler ;
383
383
384
- /// <summary>
385
- /// If not null, the <see cref="_scheduler"/> supports
386
- /// long running tasks.
387
- /// </summary>
388
- private readonly ISchedulerLongRunning _longRunning ;
389
384
private readonly ConcurrentQueue < T > _queue ;
390
385
391
386
/// <summary>
@@ -418,7 +413,6 @@ internal sealed class ObserveOnObserverNew<T> : IdentitySink<T>
418
413
public ObserveOnObserverNew ( IScheduler scheduler , IObserver < T > downstream ) : base ( downstream )
419
414
{
420
415
_scheduler = scheduler ;
421
- _longRunning = scheduler . AsLongRunning ( ) ;
422
416
_queue = new ConcurrentQueue < T > ( ) ;
423
417
}
424
418
@@ -480,15 +474,7 @@ private void Schedule()
480
474
481
475
if ( Disposable . TrySetMultiple ( ref _task , newTask ) )
482
476
{
483
- var longRunning = _longRunning ;
484
- if ( longRunning != null )
485
- {
486
- newTask . Disposable = longRunning . ScheduleLongRunning ( this , DrainLongRunningAction ) ;
487
- }
488
- else
489
- {
490
- newTask . Disposable = _scheduler . Schedule ( this , DrainShortRunningFunc ) ;
491
- }
477
+ newTask . Disposable = _scheduler . Schedule ( this , DrainShortRunningFunc ) ;
492
478
}
493
479
494
480
// If there was a cancellation, clear the queue
@@ -502,14 +488,6 @@ private void Schedule()
502
488
}
503
489
}
504
490
505
- /// <summary>
506
- /// The static action to be scheduled on a long running scheduler.
507
- /// Avoids creating a delegate that captures <code>this</code>
508
- /// whenever the signals have to be drained.
509
- /// </summary>
510
- private static readonly Action < ObserveOnObserverNew < T > , ICancelable > DrainLongRunningAction =
511
- ( self , cancel ) => self . DrainLongRunning ( ) ;
512
-
513
491
/// <summary>
514
492
/// The static action to be scheduled on a simple scheduler.
515
493
/// Avoids creating a delegate that captures <code>this</code>
@@ -526,7 +504,7 @@ private void Schedule()
526
504
/// <returns>The IDisposable of the recursively scheduled task or an empty disposable.</returns>
527
505
private IDisposable DrainShortRunning ( IScheduler recursiveScheduler )
528
506
{
529
- DrainStep ( _queue , false ) ;
507
+ DrainStep ( _queue ) ;
530
508
531
509
if ( Interlocked . Decrement ( ref _wip ) != 0 )
532
510
{
@@ -550,23 +528,20 @@ private IDisposable DrainShortRunning(IScheduler recursiveScheduler)
550
528
/// In addition, the DrainStep is invoked from the DrainLongRunning's loop
551
529
/// so reading _queue inside this method would still incur the same barrier
552
530
/// overhead otherwise.</param>
553
- /// <param name="delayError">Should the errors be delayed until all
554
- /// queued items have been emitted to the downstream?</param>
555
- /// <returns>True if the drain loop should stop.</returns>
556
- private bool DrainStep ( ConcurrentQueue < T > q , bool delayError )
531
+ private void DrainStep ( ConcurrentQueue < T > q )
557
532
{
558
533
// Check if the operator has been disposed
559
534
if ( Volatile . Read ( ref _disposed ) )
560
535
{
561
536
// cleanup residue items in the queue
562
537
Clear ( q ) ;
563
- return true ;
538
+ return ;
564
539
}
565
540
566
541
// Has the upstream call OnCompleted?
567
542
var d = Volatile . Read ( ref _done ) ;
568
543
569
- if ( d && ! delayError )
544
+ if ( d )
570
545
{
571
546
// done = true happens before setting error
572
547
// this is safe to be a plain read
@@ -576,7 +551,7 @@ private bool DrainStep(ConcurrentQueue<T> q, bool delayError)
576
551
{
577
552
Volatile . Write ( ref _disposed , true ) ;
578
553
ForwardOnError ( ex ) ;
579
- return true ;
554
+ return ;
580
555
}
581
556
}
582
557
@@ -588,65 +563,203 @@ private bool DrainStep(ConcurrentQueue<T> q, bool delayError)
588
563
if ( d && empty )
589
564
{
590
565
Volatile . Write ( ref _disposed , true ) ;
591
- // done = true happens before setting error
592
- // this is safe to be a plain read
593
- var ex = _error ;
594
- // if not null, there was an OnError call
595
- if ( ex != null )
596
- {
597
- ForwardOnError ( ex ) ;
598
- }
599
- else
600
- {
601
- // otherwise, complete normally
602
- ForwardOnCompleted ( ) ;
603
- }
604
- return true ;
566
+ // otherwise, complete normally
567
+ ForwardOnCompleted ( ) ;
568
+ return ;
605
569
}
606
570
607
571
// the queue is empty and the upstream hasn't completed yet
608
572
if ( empty )
609
573
{
610
- return true ;
574
+ return ;
611
575
}
612
576
// emit the item
613
577
ForwardOnNext ( v ) ;
578
+ }
579
+ }
580
+
581
+ /// <summary>
582
+ /// Signals events on a ISchedulerLongRunning by blocking the emission thread while waiting
583
+ /// for them from the upstream.
584
+ /// </summary>
585
+ /// <typeparam name="TSource">The element type of the sequence.</typeparam>
586
+ internal sealed class ObserveOnObserverLongRunning < TSource > : IdentitySink < TSource >
587
+ {
588
+ /// <summary>
589
+ /// This will run a suspending drain task, hogging the backing thread
590
+ /// until the sequence terminates or gets disposed.
591
+ /// </summary>
592
+ private readonly ISchedulerLongRunning _scheduler ;
593
+
594
+ /// <summary>
595
+ /// The queue for holding the OnNext items, terminal signals have their own fields.
596
+ /// </summary>
597
+ private readonly ConcurrentQueue < TSource > _queue ;
598
+
599
+ /// <summary>
600
+ /// Protects the suspension and resumption of the long running drain task.
601
+ /// </summary>
602
+ private readonly object _suspendGuard ;
603
+
604
+ /// <summary>
605
+ /// The work-in-progress counter. If it jumps from 0 to 1, the drain task is resumed,
606
+ /// if it reaches 0 again, the drain task is suspended.
607
+ /// </summary>
608
+ private long _wip ;
609
+
610
+ /// <summary>
611
+ /// Set to true if the upstream terminated.
612
+ /// </summary>
613
+ private bool _done ;
614
+
615
+ /// <summary>
616
+ /// Set to a non-null Exception if the upstream terminated with OnError.
617
+ /// </summary>
618
+ private Exception _error ;
619
+
620
+ /// <summary>
621
+ /// Indicates the sequence has been disposed and the drain task should quit.
622
+ /// </summary>
623
+ private bool _disposed ;
624
+
625
+ /// <summary>
626
+ /// Makes sure the drain task is scheduled only once, when the first signal
627
+ /// from upstream arrives.
628
+ /// </summary>
629
+ private int _runDrainOnce ;
630
+
631
+ /// <summary>
632
+ /// The disposable tracking the drain task.
633
+ /// </summary>
634
+ private IDisposable _drainTask ;
614
635
615
- // keep looping
616
- return false ;
636
+ public ObserveOnObserverLongRunning ( ISchedulerLongRunning scheduler , IObserver < TSource > observer ) : base ( observer )
637
+ {
638
+ _scheduler = scheduler ;
639
+ _queue = new ConcurrentQueue < TSource > ( ) ;
640
+ _suspendGuard = new object ( ) ;
641
+ }
642
+
643
+ public override void OnCompleted ( )
644
+ {
645
+ Volatile . Write ( ref _done , true ) ;
646
+ Schedule ( ) ;
647
+ }
648
+
649
+ public override void OnError ( Exception error )
650
+ {
651
+ _error = error ;
652
+ Volatile . Write ( ref _done , true ) ;
653
+ Schedule ( ) ;
654
+ }
655
+
656
+ public override void OnNext ( TSource value )
657
+ {
658
+ _queue . Enqueue ( value ) ;
659
+ Schedule ( ) ;
660
+ }
661
+
662
+ private void Schedule ( )
663
+ {
664
+ // Schedule the suspending drain once
665
+ if ( Volatile . Read ( ref _runDrainOnce ) == 0
666
+ && Interlocked . CompareExchange ( ref _runDrainOnce , 1 , 0 ) == 0 )
667
+ {
668
+ Disposable . SetSingle ( ref _drainTask , _scheduler . ScheduleLongRunning ( this , DrainLongRunning ) ) ;
669
+ }
670
+
671
+ // Indicate more work is to be done by the drain loop
672
+ if ( Interlocked . Increment ( ref _wip ) == 1L )
673
+ {
674
+ // resume the drain loop waiting on the guard
675
+ lock ( _suspendGuard )
676
+ {
677
+ Monitor . Pulse ( _suspendGuard ) ;
678
+ }
679
+ }
617
680
}
618
681
619
682
/// <summary>
620
- /// Emits as many signals as possible to the downstream observer
621
- /// as this is executing a long-running scheduler so
622
- /// it can occupy that thread as long as it needs to.
683
+ /// Static reference to the Drain method, saves allocation.
623
684
/// </summary>
624
- private void DrainLongRunning ( )
685
+ private static readonly Action < ObserveOnObserverLongRunning < TSource > , ICancelable > DrainLongRunning = ( self , cancelable ) => self . Drain ( ) ;
686
+
687
+ protected override void Dispose ( bool disposing )
625
688
{
626
- var missed = 1 ;
689
+ // Indicate the drain task should quit
690
+ Volatile . Write ( ref _disposed , true ) ;
691
+ // Resume the drain task in case it sleeps
692
+ lock ( _suspendGuard )
693
+ {
694
+ Monitor . Pulse ( _suspendGuard ) ;
695
+ }
696
+ // Cancel the drain task handle.
697
+ Disposable . TryDispose ( ref _drainTask ) ;
698
+ base . Dispose ( disposing ) ;
699
+ }
627
700
628
- // read out fields upfront as the DrainStep uses atomics
629
- // that would force the re-read of these constant values
630
- // from memory, regardless of readonly, afaik
701
+ private void Drain ( )
702
+ {
631
703
var q = _queue ;
632
-
633
704
for ( ; ; )
634
705
{
635
- for ( ; ; )
706
+ // If the sequence was disposed, clear the queue and quit
707
+ if ( Volatile . Read ( ref _disposed ) )
708
+ {
709
+ while ( q . TryDequeue ( out var _ ) ) ;
710
+ break ;
711
+ }
712
+
713
+ // Has the upstream terminated?
714
+ var isDone = Volatile . Read ( ref _done ) ;
715
+ // Do we have an item in the queue
716
+ var hasValue = q . TryDequeue ( out var item ) ;
717
+
718
+ // If the upstream has terminated and no further items are in the queue
719
+ if ( isDone && ! hasValue )
636
720
{
637
- // delayError: true - because of
638
- // ObserveOn_LongRunning_HoldUpDuringDispatchAndFail
639
- // expects something that almost looks like full delayError
640
- if ( DrainStep ( q , true ) )
721
+ // Find out if the upstream terminated with an error and signal accordingly.
722
+ var e = _error ;
723
+ if ( e != null )
724
+ {
725
+ ForwardOnError ( e ) ;
726
+ }
727
+ else
641
728
{
642
- break ;
729
+ ForwardOnCompleted ( ) ;
643
730
}
731
+ break ;
644
732
}
645
733
646
- missed = Interlocked . Add ( ref _wip , - missed ) ;
647
- if ( missed == 0 )
734
+ // There was an item, signal it.
735
+ if ( hasValue )
648
736
{
649
- break ;
737
+ ForwardOnNext ( item ) ;
738
+ // Consume the item and try the next item if the work-in-progress
739
+ // indicator is still not zero
740
+ if ( Interlocked . Decrement ( ref _wip ) != 0L )
741
+ {
742
+ continue ;
743
+ }
744
+ }
745
+
746
+ // If we run out of work and the sequence is not disposed
747
+ if ( Volatile . Read ( ref _wip ) == 0L && ! Volatile . Read ( ref _disposed ) )
748
+ {
749
+ var g = _suspendGuard ;
750
+ // try sleeping, if we can't even enter the lock, the producer
751
+ // side is currently trying to resume us
752
+ if ( Monitor . TryEnter ( g ) )
753
+ {
754
+ // Make sure again there is still no work and the sequence is not disposed
755
+ if ( Volatile . Read ( ref _wip ) == 0L && ! Volatile . Read ( ref _disposed ) )
756
+ {
757
+ // wait for a Pulse(g)
758
+ Monitor . Wait ( g ) ;
759
+ }
760
+ // Unlock
761
+ Monitor . Exit ( g ) ;
762
+ }
650
763
}
651
764
}
652
765
}
0 commit comments