18
18
import java .util .Iterator ;
19
19
import java .util .Collections ;
20
20
import java .util .concurrent .Executor ;
21
- import java .util .concurrent .atomic .AtomicBoolean ;
22
21
import java .util .concurrent .ConcurrentLinkedQueue ;
22
+ import java .util .concurrent .atomic .AtomicInteger ;
23
23
24
24
/**
25
25
* AsyncIterablePublisher is an implementation of Reactive Streams `Publisher`
@@ -59,7 +59,6 @@ public void subscribe(final Subscriber<? super T> s) {
59
59
// These represent the protocol of the `AsyncIterablePublishers` SubscriptionImpls
60
60
static interface Signal {};
61
61
enum Cancel implements Signal { Instance ; };
62
- enum Subscribe implements Signal { Instance ; };
63
62
enum Send implements Signal { Instance ; };
64
63
static final class Request implements Signal {
65
64
final long n ;
@@ -87,7 +86,7 @@ final class SubscriptionImpl implements Subscription, Runnable {
87
86
88
87
// We are using this `AtomicBoolean` to make sure that this `Subscription` doesn't run concurrently with itself,
89
88
// which would violate rule 1.3 among others (no concurrent notifications).
90
- private final AtomicBoolean on = new AtomicBoolean ( false );
89
+ private final AtomicInteger wip = new AtomicInteger ( 0 );
91
90
92
91
// This method will register inbound demand from our `Subscriber` and validate it against rule 3.9 and rule 3.17
93
92
private void doRequest (final long n ) {
@@ -205,46 +204,47 @@ private void signal(final Signal signal) {
205
204
206
205
// This is the main "event loop" if you so will
207
206
@ Override public final void run () {
208
- if (on .get ()) { // establishes a happens-before relationship with the end of the previous run
209
- try {
210
- final Signal s = inboundSignals .poll (); // We take a signal off the queue
211
- if (!cancelled ) { // to make sure that we follow rule 1.8, 3.6 and 3.7
207
+ int remainingWork = 1 ;
208
+ for (;;) {
212
209
213
- // Below we simply unpack the `Signal`s and invoke the corresponding methods
214
- if (s instanceof Request )
215
- doRequest (((Request )s ).n );
216
- else if (s == Send .Instance )
217
- doSend ();
218
- else if (s == Cancel .Instance )
219
- doCancel ();
220
- else if (s == Subscribe .Instance )
221
- doSubscribe ();
210
+ Signal s ;
211
+ while ((s = inboundSignals .poll ()) != null ) {
212
+ if (cancelled ) { // to make sure that we follow rule 1.8, 3.6 and 3.7
213
+ return ;
222
214
}
223
- } finally {
224
- on .set (false ); // establishes a happens-before relationship with the beginning of the next run
225
- if (!inboundSignals .isEmpty ()) // If we still have signals to process
226
- tryScheduleToExecute (); // Then we try to schedule ourselves to execute again
215
+
216
+ // Below we simply unpack the `Signal`s and invoke the corresponding methods
217
+ if (s instanceof Request )
218
+ doRequest (((Request ) s ).n );
219
+ else if (s == Send .Instance )
220
+ doSend ();
221
+ else if (s == Cancel .Instance )
222
+ doCancel ();
223
+ }
224
+
225
+ remainingWork = wip .addAndGet (-remainingWork ); // establishes a happens-before relationship with the beginning of the next run
226
+ if (remainingWork == 0 ) {
227
+ return ;
227
228
}
228
229
}
229
230
}
230
231
231
232
// This method makes sure that this `Subscription` is only running on one Thread at a time,
232
233
// this is important to make sure that we follow rule 1.3
233
234
private final void tryScheduleToExecute () {
234
- if (on .compareAndSet (false , true )) {
235
- try {
236
- executor .execute (this );
237
- } catch (Throwable t ) { // If we can't run on the `Executor`, we need to fail gracefully
238
- if (!cancelled ) {
239
- doCancel (); // First of all, this failure is not recoverable, so we need to follow rule 1.4 and 1.6
240
- try {
241
- terminateDueTo (new IllegalStateException ("Publisher terminated due to unavailable Executor." , t ));
242
- } finally {
243
- inboundSignals .clear (); // We're not going to need these anymore
244
- // This subscription is cancelled by now, but letting it become schedulable again means
245
- // that we can drain the inboundSignals queue if anything arrives after clearing
246
- on .set (false );
247
- }
235
+ if (wip .getAndIncrement () != 0 ) { // ensure happens-before with already running work
236
+ return ;
237
+ }
238
+
239
+ try {
240
+ executor .execute (this );
241
+ } catch (Throwable t ) { // If we can't run on the `Executor`, we need to fail gracefully
242
+ if (!cancelled ) {
243
+ doCancel (); // First of all, this failure is not recoverable, so we need to follow rule 1.4 and 1.6
244
+ try {
245
+ terminateDueTo (new IllegalStateException ("Publisher terminated due to unavailable Executor." , t ));
246
+ } finally {
247
+ inboundSignals .clear (); // We're not going to need these anymore
248
248
}
249
249
}
250
250
}
@@ -263,7 +263,7 @@ private final void tryScheduleToExecute() {
263
263
// method is only intended to be invoked once, and immediately after the constructor has
264
264
// finished.
265
265
void init () {
266
- signal ( Subscribe . Instance );
266
+ doSubscribe ( );
267
267
}
268
268
};
269
269
}
0 commit comments