15
15
*/
16
16
package rx .internal .operators ;
17
17
18
- import java .util .concurrent .atomic .AtomicLong ;
19
- import java .util .concurrent .atomic .AtomicLongFieldUpdater ;
18
+ import java .util .Queue ;
19
+ import java .util .concurrent .atomic .*;
20
+
21
+ import rx .Subscriber ;
20
22
21
23
/**
22
24
* Utility functions for use with backpressure.
@@ -32,6 +34,8 @@ private BackpressureUtils() {
32
34
* addition once the addition is successful (uses CAS semantics). If
33
35
* overflows then sets {@code requested} field to {@code Long.MAX_VALUE}.
34
36
*
37
+ * @param <T> the type of the target object on which the field updater operates
38
+ *
35
39
* @param requested
36
40
* atomic field updater for a request count
37
41
* @param object
@@ -103,6 +107,208 @@ public static long addCap(long a, long b) {
103
107
return u ;
104
108
}
105
109
110
+ /**
111
+ * Masks the most significant bit, i.e., 0x8000_0000_0000_0000L.
112
+ */
113
+ static final long COMPLETED_MASK = Long .MIN_VALUE ;
114
+ /**
115
+ * Masks the request amount bits, i.e., 0x7FFF_FFFF_FFFF_FFFF.
116
+ */
117
+ static final long REQUESTED_MASK = Long .MAX_VALUE ;
118
+
119
+ /**
120
+ * Signals the completion of the main sequence and switches to post-completion replay mode.
121
+ *
122
+ * <p>
123
+ * Don't modify the queue after calling this method!
124
+ *
125
+ * <p>
126
+ * Post-completion backpressure handles the case when a source produces values based on
127
+ * requests when it is active but more values are available even after its completion.
128
+ * In this case, the onCompleted() can't just emit the contents of the queue but has to
129
+ * coordinate with the requested amounts. This requires two distinct modes: active and
130
+ * completed. In active mode, requests flow through and the queue is not accessed but
131
+ * in completed mode, requests no-longer reach the upstream but help in draining the queue.
132
+ * <p>
133
+ * The algorithm utilizes the most significant bit (bit 63) of a long value (AtomicLong) since
134
+ * request amount only goes up to Long.MAX_VALUE (bits 0-62) and negative values aren't
135
+ * allowed.
136
+ *
137
+ * @param <T> the value type to emit
138
+ * @param requested the holder of current requested amount
139
+ * @param queue the queue holding values to be emitted after completion
140
+ * @param actual the subscriber to receive the values
141
+ */
142
+ public static <T > void postCompleteDone (AtomicLong requested , Queue <T > queue , Subscriber <? super T > actual ) {
143
+ for (;;) {
144
+ long r = requested .get ();
145
+
146
+ // switch to completed mode only once
147
+ if ((r & COMPLETED_MASK ) != 0L ) {
148
+ return ;
149
+ }
150
+
151
+ //
152
+ long u = r | COMPLETED_MASK ;
153
+
154
+ if (requested .compareAndSet (r , u )) {
155
+ // if we successfully switched to post-complete mode and there
156
+ // are requests available start draining the queue
157
+ if (r != 0L ) {
158
+ // if the switch happened when there was outstanding requests, start draining
159
+ postCompleteDrain (requested , queue , actual );
160
+ }
161
+ return ;
162
+ }
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Accumulates requests (validated) and handles the completed mode draining of the queue based on the requests.
168
+ *
169
+ * <p>
170
+ * Post-completion backpressure handles the case when a source produces values based on
171
+ * requests when it is active but more values are available even after its completion.
172
+ * In this case, the onCompleted() can't just emit the contents of the queue but has to
173
+ * coordinate with the requested amounts. This requires two distinct modes: active and
174
+ * completed. In active mode, requests flow through and the queue is not accessed but
175
+ * in completed mode, requests no-longer reach the upstream but help in draining the queue.
176
+ *
177
+ * @param <T> the value type to emit
178
+ * @param requested the holder of current requested amount
179
+ * @param n the value requested;
180
+ * @param queue the queue holding values to be emitted after completion
181
+ * @param actual the subscriber to receive the values
182
+ * @return true if in the active mode and the request amount of n can be relayed to upstream, false if
183
+ * in the post-completed mode and the queue is draining.
184
+ */
185
+ public static <T > boolean postCompleteRequest (AtomicLong requested , long n , Queue <T > queue , Subscriber <? super T > actual ) {
186
+ if (n < 0L ) {
187
+ throw new IllegalArgumentException ("n >= 0 required but it was " + n );
188
+ }
189
+ if (n == 0 ) {
190
+ return (requested .get () & COMPLETED_MASK ) == 0 ;
191
+ }
192
+
193
+ for (;;) {
194
+ long r = requested .get ();
195
+
196
+ // mask of the completed flag
197
+ long c = r & COMPLETED_MASK ;
198
+ // mask of the requested amount
199
+ long u = r & REQUESTED_MASK ;
200
+
201
+ // add the current requested amount and the new requested amount
202
+ // cap at Long.MAX_VALUE;
203
+ long v = addCap (u , n );
204
+
205
+ // restore the completed flag
206
+ v |= c ;
207
+
208
+ if (requested .compareAndSet (r , v )) {
209
+ // if there was no outstanding request before and in
210
+ // the post-completed state, start draining
211
+ if (r == COMPLETED_MASK ) {
212
+ postCompleteDrain (requested , queue , actual );
213
+ return false ;
214
+ }
215
+ // returns true for active mode and false if the completed flag was set
216
+ return c == 0L ;
217
+ }
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Drains the queue based on the outstanding requests in post-completed mode (only!).
223
+ *
224
+ * @param <T> the value type to emit
225
+ * @param requested the holder of current requested amount
226
+ * @param queue the queue holding values to be emitted after completion
227
+ * @param actual the subscriber to receive the values
228
+ */
229
+ static <T > void postCompleteDrain (AtomicLong requested , Queue <T > queue , Subscriber <? super T > subscriber ) {
230
+
231
+ long r = requested .get ();
232
+ /*
233
+ * Since we are supposed to be in the post-complete state,
234
+ * requested will have its top bit set.
235
+ * To allow direct comparison, we start with an emission value which has also
236
+ * this flag set, then increment it as usual.
237
+ * Since COMPLETED_MASK is essentially Long.MIN_VALUE,
238
+ * there won't be any overflow or sign flip.
239
+ */
240
+ long e = COMPLETED_MASK ;
241
+
242
+ for (;;) {
243
+
244
+ /*
245
+ * This is an improved queue-drain algorithm with a specialization
246
+ * in which we know the queue won't change anymore (i.e., done is always true
247
+ * when looking at the classical algorithm and there is no error).
248
+ *
249
+ * Note that we don't check for cancellation or emptyness upfront for two reasons:
250
+ * 1) if e != r, the loop will do this and we quit appropriately
251
+ * 2) if e == r, then either there was no outstanding requests or we emitted the requested amount
252
+ * and the execution simply falls to the e == r check below which checks for emptyness anyway.
253
+ */
254
+
255
+ while (e != r ) {
256
+ if (subscriber .isUnsubscribed ()) {
257
+ return ;
258
+ }
259
+
260
+ T v = queue .poll ();
261
+
262
+ if (v == null ) {
263
+ subscriber .onCompleted ();
264
+ return ;
265
+ }
266
+
267
+ subscriber .onNext (v );
268
+
269
+ e ++;
270
+ }
271
+
272
+ /*
273
+ * If the emission count reaches the requested amount the same time the queue becomes empty
274
+ * this will make sure the subscriber is completed immediately instead of on the next request.
275
+ * This is also true if there are no outstanding requests (this the while loop doesn't run)
276
+ * and the queue is empty from the start.
277
+ */
278
+ if (e == r ) {
279
+ if (subscriber .isUnsubscribed ()) {
280
+ return ;
281
+ }
282
+ if (queue .isEmpty ()) {
283
+ subscriber .onCompleted ();
284
+ return ;
285
+ }
286
+ }
287
+
288
+ /*
289
+ * Fast flow: see if more requests have arrived in the meantime.
290
+ * This avoids an atomic add (~40 cycles) and resumes the emission immediately.
291
+ */
292
+ r = requested .get ();
293
+
294
+ if (r == e ) {
295
+ /*
296
+ * Atomically decrement the requested amount by the emission amount.
297
+ * We can't use the full emission value because of the completed flag,
298
+ * however, due to two's complement representation, the flag on requested
299
+ * is preserved.
300
+ */
301
+ r = requested .addAndGet (-(e & REQUESTED_MASK ));
302
+ // The requested amount actually reached zero, quit
303
+ if (r == COMPLETED_MASK ) {
304
+ return ;
305
+ }
306
+ // reset the emission count
307
+ e = COMPLETED_MASK ;
308
+ }
309
+ }
310
+ }
311
+
106
312
/**
107
313
* Atomically subtracts a value from the requested amount unless it's at Long.MAX_VALUE.
108
314
* @param requested the requested amount holder
0 commit comments