15
15
*/
16
16
package rx .internal .operators ;
17
17
18
- import java .util .Queue ;
19
18
import java .util .concurrent .ConcurrentLinkedQueue ;
20
19
import java .util .concurrent .atomic .AtomicBoolean ;
21
20
import java .util .concurrent .atomic .AtomicLong ;
25
24
import rx .Subscriber ;
26
25
import rx .exceptions .MissingBackpressureException ;
27
26
import rx .functions .Action0 ;
27
+ import rx .internal .util .BackpressureDrainManager ;
28
28
29
29
public class OperatorOnBackpressureBuffer <T > implements Operator <T , T > {
30
30
31
- private final NotificationLite <T > on = NotificationLite .instance ();
32
-
33
31
private final Long capacity ;
34
32
private final Action0 onOverflow ;
35
33
@@ -52,122 +50,114 @@ public OperatorOnBackpressureBuffer(long capacity, Action0 onOverflow) {
52
50
53
51
@ Override
54
52
public Subscriber <? super T > call (final Subscriber <? super T > child ) {
55
- // TODO get a different queue implementation
56
- final ConcurrentLinkedQueue <Object > queue = new ConcurrentLinkedQueue <Object >();
57
- final AtomicLong capacity = (this .capacity == null ) ? null : new AtomicLong (this .capacity );
58
- final AtomicLong wip = new AtomicLong ();
59
- final AtomicLong requested = new AtomicLong ();
60
-
61
- child .setProducer (new Producer () {
62
-
63
- @ Override
64
- public void request (long n ) {
65
- if (requested .getAndAdd (n ) == 0 ) {
66
- pollQueue (wip , requested , capacity , queue , child );
67
- }
68
- }
69
53
70
- });
71
54
// don't pass through subscriber as we are async and doing queue draining
72
55
// a parent being unsubscribed should not affect the children
73
- Subscriber <T > parent = new Subscriber <T >() {
56
+ BufferSubscriber <T > parent = new BufferSubscriber <T >(child , capacity , onOverflow );
74
57
75
- private AtomicBoolean saturated = new AtomicBoolean (false );
58
+ // if child unsubscribes it should unsubscribe the parent, but not the other way around
59
+ child .add (parent );
60
+ child .setProducer (parent .manager ());
76
61
77
- @ Override
78
- public void onStart () {
79
- request (Long .MAX_VALUE );
80
- }
62
+ return parent ;
63
+ }
64
+ private static final class BufferSubscriber <T > extends Subscriber <T > implements BackpressureDrainManager .BackpressureQueueCallback {
65
+ // TODO get a different queue implementation
66
+ private final ConcurrentLinkedQueue <Object > queue = new ConcurrentLinkedQueue <Object >();
67
+ private final Long baseCapacity ;
68
+ private final AtomicLong capacity ;
69
+ private final Subscriber <? super T > child ;
70
+ private final AtomicBoolean saturated = new AtomicBoolean (false );
71
+ private final BackpressureDrainManager manager ;
72
+ private final NotificationLite <T > on = NotificationLite .instance ();
73
+ private final Action0 onOverflow ;
74
+
75
+ public BufferSubscriber (final Subscriber <? super T > child , Long capacity , Action0 onOverflow ) {
76
+ this .child = child ;
77
+ this .baseCapacity = capacity ;
78
+ this .capacity = capacity != null ? new AtomicLong (capacity ) : null ;
79
+ this .onOverflow = onOverflow ;
80
+ this .manager = new BackpressureDrainManager (this );
81
+ }
82
+ @ Override
83
+ public void onStart () {
84
+ request (Long .MAX_VALUE );
85
+ }
81
86
82
- @ Override
83
- public void onCompleted () {
84
- if (!saturated .get ()) {
85
- queue .offer (on .completed ());
86
- pollQueue (wip , requested , capacity , queue , child );
87
- }
87
+ @ Override
88
+ public void onCompleted () {
89
+ if (!saturated .get ()) {
90
+ manager .terminateAndDrain ();
88
91
}
92
+ }
89
93
90
- @ Override
91
- public void onError (Throwable e ) {
92
- if (!saturated .get ()) {
93
- queue .offer (on .error (e ));
94
- pollQueue (wip , requested , capacity , queue , child );
95
- }
94
+ @ Override
95
+ public void onError (Throwable e ) {
96
+ if (!saturated .get ()) {
97
+ manager .terminateAndDrain (e );
96
98
}
99
+ }
97
100
98
- @ Override
99
- public void onNext (T t ) {
100
- if (!assertCapacity ()) {
101
- return ;
102
- }
103
- queue .offer (on .next (t ));
104
- pollQueue (wip , requested , capacity , queue , child );
101
+ @ Override
102
+ public void onNext (T t ) {
103
+ if (!assertCapacity ()) {
104
+ return ;
105
105
}
106
+ queue .offer (on .next (t ));
107
+ manager .drain ();
108
+ }
106
109
107
- private boolean assertCapacity () {
108
- if (capacity == null ) {
109
- return true ;
110
- }
111
-
112
- long currCapacity ;
113
- do {
114
- currCapacity = capacity .get ();
115
- if (currCapacity <= 0 ) {
116
- if (saturated .compareAndSet (false , true )) {
117
- unsubscribe ();
118
- child .onError (new MissingBackpressureException (
119
- "Overflowed buffer of "
120
- + OperatorOnBackpressureBuffer .this .capacity ));
121
- if (onOverflow != null ) {
122
- onOverflow .call ();
123
- }
124
- }
125
- return false ;
126
- }
127
- // ensure no other thread stole our slot, or retry
128
- } while (!capacity .compareAndSet (currCapacity , currCapacity - 1 ));
129
- return true ;
110
+ @ Override
111
+ public boolean accept (Object value ) {
112
+ return on .accept (child , value );
113
+ }
114
+ @ Override
115
+ public void complete (Throwable exception ) {
116
+ if (exception != null ) {
117
+ child .onError (exception );
118
+ } else {
119
+ child .onCompleted ();
130
120
}
131
- };
132
-
133
- // if child unsubscribes it should unsubscribe the parent, but not the other way around
134
- child .add (parent );
121
+ }
122
+ @ Override
123
+ public Object peek () {
124
+ return queue .peek ();
125
+ }
126
+ @ Override
127
+ public Object poll () {
128
+ Object value = queue .poll ();
129
+ if (capacity != null && value != null ) {
130
+ capacity .incrementAndGet ();
131
+ }
132
+ return value ;
133
+ }
135
134
136
- return parent ;
137
- }
135
+ private boolean assertCapacity () {
136
+ if (capacity == null ) {
137
+ return true ;
138
+ }
138
139
139
- private void pollQueue (AtomicLong wip , AtomicLong requested , AtomicLong capacity , Queue <Object > queue , Subscriber <? super T > child ) {
140
- // TODO can we do this without putting everything in the queue first so we can fast-path the case when we don't need to queue?
141
- if (requested .get () > 0 ) {
142
- // only one draining at a time
143
- try {
144
- /*
145
- * This needs to protect against concurrent execution because `request` and `on*` events can come concurrently.
146
- */
147
- if (wip .getAndIncrement () == 0 ) {
148
- while (true ) {
149
- if (requested .getAndDecrement () != 0 ) {
150
- Object o = queue .poll ();
151
- if (o == null ) {
152
- // nothing in queue
153
- requested .incrementAndGet ();
154
- return ;
155
- }
156
- if (capacity != null ) { // it's bounded
157
- capacity .incrementAndGet ();
158
- }
159
- on .accept (child , o );
160
- } else {
161
- // we hit the end ... so increment back to 0 again
162
- requested .incrementAndGet ();
163
- return ;
140
+ long currCapacity ;
141
+ do {
142
+ currCapacity = capacity .get ();
143
+ if (currCapacity <= 0 ) {
144
+ if (saturated .compareAndSet (false , true )) {
145
+ unsubscribe ();
146
+ child .onError (new MissingBackpressureException (
147
+ "Overflowed buffer of "
148
+ + baseCapacity ));
149
+ if (onOverflow != null ) {
150
+ onOverflow .call ();
164
151
}
165
152
}
153
+ return false ;
166
154
}
167
-
168
- } finally {
169
- wip .decrementAndGet ();
170
- }
155
+ // ensure no other thread stole our slot, or retry
156
+ } while (!capacity .compareAndSet (currCapacity , currCapacity - 1 ));
157
+ return true ;
158
+ }
159
+ protected Producer manager () {
160
+ return manager ;
171
161
}
172
162
}
173
163
}
0 commit comments