19
19
import java .util .concurrent .atomic .AtomicBoolean ;
20
20
import java .util .concurrent .atomic .AtomicLong ;
21
21
22
+ import rx .BackpressureOverflow ;
22
23
import rx .Observable .Operator ;
23
24
import rx .Producer ;
24
25
import rx .Subscriber ;
27
28
import rx .functions .Action0 ;
28
29
import rx .internal .util .BackpressureDrainManager ;
29
30
31
+ import static rx .BackpressureOverflow .*;
32
+
30
33
public class OperatorOnBackpressureBuffer <T > implements Operator <T , T > {
31
34
32
35
private final Long capacity ;
33
36
private final Action0 onOverflow ;
37
+ private final BackpressureOverflow .Strategy overflowStrategy ;
34
38
35
39
private static class Holder {
36
40
static final OperatorOnBackpressureBuffer <?> INSTANCE = new OperatorOnBackpressureBuffer <Object >();
37
41
}
38
-
42
+
39
43
@ SuppressWarnings ("unchecked" )
40
44
public static <T > OperatorOnBackpressureBuffer <T > instance () {
41
45
return (OperatorOnBackpressureBuffer <T >) Holder .INSTANCE ;
@@ -44,33 +48,65 @@ public static <T> OperatorOnBackpressureBuffer<T> instance() {
44
48
OperatorOnBackpressureBuffer () {
45
49
this .capacity = null ;
46
50
this .onOverflow = null ;
51
+ this .overflowStrategy = ON_OVERFLOW_DEFAULT ;
47
52
}
48
53
54
+ /**
55
+ * Construct a new instance that will handle overflows with {@code ON_OVERFLOW_DEFAULT}, providing the
56
+ * following behavior config:
57
+ *
58
+ * @param capacity the max number of items to be admitted in the buffer, must be greater than 0.
59
+ */
49
60
public OperatorOnBackpressureBuffer (long capacity ) {
50
- this (capacity , null );
61
+ this (capacity , null , ON_OVERFLOW_DEFAULT );
51
62
}
52
63
64
+ /**
65
+ * Construct a new instance that will handle overflows with {@code ON_OVERFLOW_DEFAULT}, providing the
66
+ * following behavior config:
67
+ *
68
+ * @param capacity the max number of items to be admitted in the buffer, must be greater than 0.
69
+ * @param onOverflow the {@code Action0} to execute when the buffer overflows, may be null.
70
+ */
53
71
public OperatorOnBackpressureBuffer (long capacity , Action0 onOverflow ) {
72
+ this (capacity , onOverflow , ON_OVERFLOW_DEFAULT );
73
+ }
74
+
75
+ /**
76
+ * Construct a new instance feeding the following behavior config:
77
+ *
78
+ * @param capacity the max number of items to be admitted in the buffer, must be greater than 0.
79
+ * @param onOverflow the {@code Action0} to execute when the buffer overflows, may be null.
80
+ * @param overflowStrategy the {@code BackpressureOverflow.Strategy} to handle overflows, it must not be null.
81
+ */
82
+ public OperatorOnBackpressureBuffer (long capacity , Action0 onOverflow ,
83
+ BackpressureOverflow .Strategy overflowStrategy ) {
54
84
if (capacity <= 0 ) {
55
85
throw new IllegalArgumentException ("Buffer capacity must be > 0" );
56
86
}
87
+ if (overflowStrategy == null ) {
88
+ throw new NullPointerException ("The BackpressureOverflow strategy must not be null" );
89
+ }
57
90
this .capacity = capacity ;
58
91
this .onOverflow = onOverflow ;
92
+ this .overflowStrategy = overflowStrategy ;
59
93
}
60
94
61
95
@ Override
62
96
public Subscriber <? super T > call (final Subscriber <? super T > child ) {
63
97
64
98
// don't pass through subscriber as we are async and doing queue draining
65
99
// a parent being unsubscribed should not affect the children
66
- BufferSubscriber <T > parent = new BufferSubscriber <T >(child , capacity , onOverflow );
100
+ BufferSubscriber <T > parent = new BufferSubscriber <T >(child , capacity , onOverflow ,
101
+ overflowStrategy );
67
102
68
103
// if child unsubscribes it should unsubscribe the parent, but not the other way around
69
104
child .add (parent );
70
105
child .setProducer (parent .manager ());
71
106
72
107
return parent ;
73
108
}
109
+
74
110
private static final class BufferSubscriber <T > extends Subscriber <T > implements BackpressureDrainManager .BackpressureQueueCallback {
75
111
// TODO get a different queue implementation
76
112
private final ConcurrentLinkedQueue <Object > queue = new ConcurrentLinkedQueue <Object >();
@@ -81,14 +117,18 @@ private static final class BufferSubscriber<T> extends Subscriber<T> implements
81
117
private final BackpressureDrainManager manager ;
82
118
private final NotificationLite <T > on = NotificationLite .instance ();
83
119
private final Action0 onOverflow ;
120
+ private final BackpressureOverflow .Strategy overflowStrategy ;
84
121
85
- public BufferSubscriber (final Subscriber <? super T > child , Long capacity , Action0 onOverflow ) {
122
+ public BufferSubscriber (final Subscriber <? super T > child , Long capacity , Action0 onOverflow ,
123
+ BackpressureOverflow .Strategy overflowStrategy ) {
86
124
this .child = child ;
87
125
this .baseCapacity = capacity ;
88
126
this .capacity = capacity != null ? new AtomicLong (capacity ) : null ;
89
127
this .onOverflow = onOverflow ;
90
128
this .manager = new BackpressureDrainManager (this );
129
+ this .overflowStrategy = overflowStrategy ;
91
130
}
131
+
92
132
@ Override
93
133
public void onStart () {
94
134
request (Long .MAX_VALUE );
@@ -141,7 +181,7 @@ public Object poll() {
141
181
}
142
182
return value ;
143
183
}
144
-
184
+
145
185
private boolean assertCapacity () {
146
186
if (capacity == null ) {
147
187
return true ;
@@ -151,24 +191,30 @@ private boolean assertCapacity() {
151
191
do {
152
192
currCapacity = capacity .get ();
153
193
if (currCapacity <= 0 ) {
154
- if (saturated .compareAndSet (false , true )) {
155
- unsubscribe ();
156
- child .onError (new MissingBackpressureException (
157
- "Overflowed buffer of "
158
- + baseCapacity ));
159
- if (onOverflow != null ) {
160
- try {
161
- onOverflow .call ();
162
- } catch (Throwable e ) {
163
- Exceptions .throwIfFatal (e );
164
- manager .terminateAndDrain (e );
165
- // this line not strictly necessary but nice for clarity
166
- // and in case of future changes to code after this catch block
167
- return false ;
168
- }
194
+ boolean hasCapacity = false ;
195
+ try {
196
+ // ok if we're allowed to drop, and there is indeed an item to discard
197
+ hasCapacity = overflowStrategy .mayAttemptDrop () && poll () != null ;
198
+ } catch (MissingBackpressureException e ) {
199
+ if (saturated .compareAndSet (false , true )) {
200
+ unsubscribe ();
201
+ child .onError (e );
169
202
}
170
203
}
171
- return false ;
204
+ if (onOverflow != null ) {
205
+ try {
206
+ onOverflow .call ();
207
+ } catch (Throwable e ) {
208
+ Exceptions .throwIfFatal (e );
209
+ manager .terminateAndDrain (e );
210
+ // this line not strictly necessary but nice for clarity
211
+ // and in case of future changes to code after this catch block
212
+ return false ;
213
+ }
214
+ }
215
+ if (!hasCapacity ) {
216
+ return false ;
217
+ }
172
218
}
173
219
// ensure no other thread stole our slot, or retry
174
220
} while (!capacity .compareAndSet (currCapacity , currCapacity - 1 ));
0 commit comments