16
16
package rx .operators ;
17
17
18
18
import java .util .concurrent .ConcurrentLinkedQueue ;
19
+ import java .util .concurrent .atomic .AtomicBoolean ;
19
20
import java .util .concurrent .atomic .AtomicInteger ;
20
21
21
22
import rx .Notification ;
22
23
import rx .Observer ;
23
24
import rx .Scheduler ;
24
- import rx .util .functions .Action0 ;
25
+ import rx .Subscription ;
26
+ import rx .subscriptions .CompositeSubscription ;
27
+ import rx .subscriptions .MultipleAssignmentSubscription ;
28
+ import rx .util .functions .Func2 ;
25
29
26
30
/* package */ class ScheduledObserver <T > implements Observer <T > {
27
31
private final Observer <? super T > underlying ;
28
32
private final Scheduler scheduler ;
33
+ private final CompositeSubscription parentSubscription ;
34
+ private final EventLoop eventLoop = new EventLoop ();
29
35
30
36
private final ConcurrentLinkedQueue <Notification <? extends T >> queue = new ConcurrentLinkedQueue <Notification <? extends T >>();
31
- private final AtomicInteger counter = new AtomicInteger ( 0 );
37
+ private final AtomicBoolean started = new AtomicBoolean ( );
32
38
33
- public ScheduledObserver (Observer <? super T > underlying , Scheduler scheduler ) {
39
+ public ScheduledObserver (CompositeSubscription s , Observer <? super T > underlying , Scheduler scheduler ) {
40
+ this .parentSubscription = s ;
34
41
this .underlying = underlying ;
35
42
this .scheduler = scheduler ;
36
43
}
@@ -50,46 +57,83 @@ public void onNext(final T args) {
50
57
enqueue (new Notification <T >(args ));
51
58
}
52
59
60
+ final AtomicInteger counter = new AtomicInteger ();
61
+
53
62
private void enqueue (Notification <? extends T > notification ) {
54
- // this must happen before 'counter' is used to provide synchronization between threads
63
+ // this must happen before synchronization between threads
55
64
queue .offer (notification );
56
65
57
- // we now use counter to atomically determine if we need to start processing or not
58
- // it will be 0 if it's the first notification or the scheduler has finished processing work
59
- // and we need to start doing it again
60
- if (counter .getAndIncrement () == 0 ) {
61
- processQueue ();
66
+ /**
67
+ * If the counter is currently at 0 (before incrementing with this addition)
68
+ * we will schedule the work.
69
+ */
70
+ if (counter .getAndIncrement () <= 0 ) {
71
+ if (!started .get () && started .compareAndSet (false , true )) {
72
+ // first time we use the parent scheduler to start the event loop
73
+ MultipleAssignmentSubscription recursiveSubscription = new MultipleAssignmentSubscription ();
74
+ parentSubscription .add (scheduler .schedule (recursiveSubscription , eventLoop ));
75
+ parentSubscription .add (recursiveSubscription );
76
+ } else {
77
+ // subsequent times we reschedule existing one
78
+ eventLoop .reschedule ();
79
+ }
62
80
}
63
81
}
64
82
65
- private void processQueue () {
66
- scheduler .schedule (new Action0 () {
67
- @ Override
68
- public void call () {
69
- Notification <? extends T > not = queue .poll ();
70
-
71
- switch (not .getKind ()) {
72
- case OnNext :
73
- underlying .onNext (not .getValue ());
74
- break ;
75
- case OnError :
76
- underlying .onError (not .getThrowable ());
77
- break ;
78
- case OnCompleted :
79
- underlying .onCompleted ();
80
- break ;
81
- default :
82
- throw new IllegalStateException ("Unknown kind of notification " + not );
83
+ private class EventLoop implements Func2 <Scheduler , MultipleAssignmentSubscription , Subscription > {
83
84
84
- }
85
+ volatile Scheduler _recursiveScheduler ;
86
+ volatile MultipleAssignmentSubscription _recursiveSubscription ;
85
87
86
- // decrement count and if we still have work to do
87
- // recursively schedule ourselves to process again
88
- if (counter .decrementAndGet () > 0 ) {
89
- scheduler .schedule (this );
90
- }
88
+ public void reschedule () {
89
+ _recursiveSubscription .setSubscription (_recursiveScheduler .schedule (_recursiveSubscription , this ));
90
+ }
91
91
92
+ @ Override
93
+ public Subscription call (Scheduler s , MultipleAssignmentSubscription recursiveSubscription ) {
94
+ /*
95
+ * --------------------------------------------------------------------------------------
96
+ * Set these the first time through so we can externally trigger recursive execution again
97
+ */
98
+ if (_recursiveScheduler == null ) {
99
+ _recursiveScheduler = s ;
100
+ }
101
+ if (_recursiveSubscription == null ) {
102
+ _recursiveSubscription = recursiveSubscription ;
92
103
}
93
- });
104
+ /*
105
+ * Back to regular flow
106
+ * --------------------------------------------------------------------------------------
107
+ */
108
+
109
+ do {
110
+ Notification <? extends T > notification = queue .poll ();
111
+ // if we got a notification, send it
112
+ if (notification != null ) {
113
+
114
+ // if unsubscribed stop working
115
+ if (parentSubscription .isUnsubscribed ()) {
116
+ return parentSubscription ;
117
+ }
118
+ // process notification
119
+
120
+ switch (notification .getKind ()) {
121
+ case OnNext :
122
+ underlying .onNext (notification .getValue ());
123
+ break ;
124
+ case OnError :
125
+ underlying .onError (notification .getThrowable ());
126
+ break ;
127
+ case OnCompleted :
128
+ underlying .onCompleted ();
129
+ break ;
130
+ default :
131
+ throw new IllegalStateException ("Unknown kind of notification " + notification );
132
+ }
133
+ }
134
+ } while (counter .decrementAndGet () > 0 );
135
+
136
+ return parentSubscription ;
137
+ }
94
138
}
95
139
}
0 commit comments