15
15
*/
16
16
package rx .operators ;
17
17
18
- import java .util .concurrent .ArrayBlockingQueue ;
18
+ import java .util .concurrent .Semaphore ;
19
19
import java .util .concurrent .atomic .AtomicLong ;
20
20
21
21
import rx .Scheduler ;
24
24
import rx .schedulers .ImmediateScheduler ;
25
25
import rx .schedulers .TestScheduler ;
26
26
import rx .schedulers .TrampolineScheduler ;
27
+ import rx .subscriptions .Subscriptions ;
28
+ import rx .util .functions .Action0 ;
27
29
import rx .util .functions .Action1 ;
28
30
29
31
/**
30
- * Asynchronously notify Observers on the specified Scheduler.
32
+ * Delivers events on the specified Scheduler.
31
33
* <p>
34
+ * This provides backpressure by blocking the incoming onNext when there is already one in the queue.
35
+ * <p>
36
+ * This means that at any given time the max number of "onNext" in flight is 3:
37
+ * -> 1 being delivered on the Scheduler
38
+ * -> 1 in the queue waiting for the Scheduler
39
+ * -> 1 blocking on the queue waiting to deliver it
40
+ *
41
+ * I have chosen to allow 1 in the queue rather than using an Exchanger style process so that the Scheduler
42
+ * can loop and have something to do each time around to optimize for avoiding rescheduling when it
43
+ * can instead just loop. I'm avoiding having the Scheduler thread ever block as it could be an event-loop
44
+ * thus if the queue is empty it exits and next time something is added it will reschedule.
45
+ *
32
46
* <img width="640" src="https://github.com/Netflix/RxJava/wiki/images/rx-operators/observeOn.png">
33
47
*/
34
48
public class OperatorObserveOn <T > implements Operator <T , T > {
@@ -73,7 +87,7 @@ private class ObserveOnSubscriber extends Subscriber<T> {
73
87
final Subscriber <? super T > observer ;
74
88
private volatile Scheduler .Inner recursiveScheduler ;
75
89
76
- private final ArrayBlockingQueue < Object > queue = new ArrayBlockingQueue < Object >( 1 );
90
+ private final InterruptibleBlockingQueue queue = new InterruptibleBlockingQueue ( );
77
91
final AtomicLong counter = new AtomicLong (0 );
78
92
79
93
public ObserveOnSubscriber (Subscriber <? super T > observer ) {
@@ -93,7 +107,9 @@ public void onNext(final T t) {
93
107
}
94
108
schedule ();
95
109
} catch (InterruptedException e ) {
96
- onError (e );
110
+ if (!isUnsubscribed ()) {
111
+ onError (e );
112
+ }
97
113
}
98
114
}
99
115
@@ -125,6 +141,18 @@ public void onError(final Throwable e) {
125
141
protected void schedule () {
126
142
if (counter .getAndIncrement () == 0 ) {
127
143
if (recursiveScheduler == null ) {
144
+ // first time through, register a Subscription
145
+ // that can interrupt this thread
146
+ add (Subscriptions .create (new Action0 () {
147
+
148
+ @ Override
149
+ public void call () {
150
+ // we have to interrupt the parent thread because
151
+ // it can be blocked on queue.put
152
+ queue .interrupt ();
153
+ }
154
+
155
+ }));
128
156
add (scheduler .schedule (new Action1 <Inner >() {
129
157
130
158
@ Override
@@ -167,4 +195,57 @@ private void pollQueue() {
167
195
168
196
}
169
197
198
+ /**
199
+ * Same behavior as ArrayBlockingQueue<Object>(1) except that we can interrupt/unsubscribe it.
200
+ */
201
+ private class InterruptibleBlockingQueue {
202
+
203
+ private final Semaphore semaphore = new Semaphore (1 );
204
+ private volatile Object item ;
205
+ private volatile boolean interrupted = false ;
206
+
207
+ public Object poll () {
208
+ if (interrupted ) {
209
+ return null ;
210
+ }
211
+ if (item == null ) {
212
+ return null ;
213
+ }
214
+ try {
215
+ return item ;
216
+ } finally {
217
+ item = null ;
218
+ semaphore .release ();
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Add an Object, blocking if an item is already in the queue.
224
+ *
225
+ * @param o
226
+ * @throws InterruptedException
227
+ */
228
+ public void put (Object o ) throws InterruptedException {
229
+ if (interrupted ) {
230
+ throw new InterruptedException ("Interrupted by Unsubscribe" );
231
+ }
232
+ semaphore .acquire ();
233
+ if (interrupted ) {
234
+ throw new InterruptedException ("Interrupted by Unsubscribe" );
235
+ }
236
+ if (o == null ) {
237
+ throw new IllegalArgumentException ("Can not put null" );
238
+ }
239
+ item = o ;
240
+ }
241
+
242
+ /**
243
+ * Used to unsubscribe and interrupt the producer if blocked in put()
244
+ */
245
+ public void interrupt () {
246
+ interrupted = true ;
247
+ semaphore .release ();
248
+ }
249
+ }
250
+
170
251
}
0 commit comments