17
17
18
18
import java .util .Queue ;
19
19
import java .util .concurrent .ConcurrentLinkedQueue ;
20
+ import java .util .concurrent .atomic .AtomicBoolean ;
20
21
import java .util .concurrent .atomic .AtomicLong ;
21
22
22
23
import rx .Observable .Operator ;
23
24
import rx .Producer ;
24
25
import rx .Subscriber ;
26
+ import rx .exceptions .MissingBackpressureException ;
27
+ import rx .functions .Action0 ;
25
28
26
29
public class OperatorOnBackpressureBuffer <T > implements Operator <T , T > {
27
30
28
31
private final NotificationLite <T > on = NotificationLite .instance ();
29
32
33
+ private final Long capacity ;
34
+ private final Action0 onOverflow ;
35
+
36
+ public OperatorOnBackpressureBuffer () {
37
+ this .capacity = null ;
38
+ this .onOverflow = null ;
39
+ }
40
+
41
+ public OperatorOnBackpressureBuffer (long capacity ) {
42
+ this (capacity , null );
43
+ }
44
+
45
+ public OperatorOnBackpressureBuffer (long capacity , Action0 onOverflow ) {
46
+ if (capacity <= 0 ) {
47
+ throw new IllegalArgumentException ("Buffer capacity must be > 0" );
48
+ }
49
+ this .capacity = capacity ;
50
+ this .onOverflow = onOverflow ;
51
+ }
52
+
30
53
@ Override
31
54
public Subscriber <? super T > call (final Subscriber <? super T > child ) {
32
55
// TODO get a different queue implementation
33
- // TODO start with size hint
34
56
final ConcurrentLinkedQueue <Object > queue = new ConcurrentLinkedQueue <Object >();
57
+ final AtomicLong capacity = (this .capacity == null ) ? null : new AtomicLong (this .capacity );
35
58
final AtomicLong wip = new AtomicLong ();
36
59
final AtomicLong requested = new AtomicLong ();
37
60
@@ -40,14 +63,17 @@ public Subscriber<? super T> call(final Subscriber<? super T> child) {
40
63
@ Override
41
64
public void request (long n ) {
42
65
if (requested .getAndAdd (n ) == 0 ) {
43
- pollQueue (wip , requested , queue , child );
66
+ pollQueue (wip , requested , capacity , queue , child );
44
67
}
45
68
}
46
69
47
70
});
48
71
// don't pass through subscriber as we are async and doing queue draining
49
72
// a parent being unsubscribed should not affect the children
50
73
Subscriber <T > parent = new Subscriber <T >() {
74
+
75
+ private AtomicBoolean saturated = new AtomicBoolean (false );
76
+
51
77
@ Override
52
78
public void onStart () {
53
79
request (Long .MAX_VALUE );
@@ -56,21 +82,47 @@ public void onStart() {
56
82
@ Override
57
83
public void onCompleted () {
58
84
queue .offer (on .completed ());
59
- pollQueue (wip , requested , queue , child );
85
+ pollQueue (wip , requested , capacity , queue , child );
60
86
}
61
87
62
88
@ Override
63
89
public void onError (Throwable e ) {
64
90
queue .offer (on .error (e ));
65
- pollQueue (wip , requested , queue , child );
91
+ pollQueue (wip , requested , capacity , queue , child );
66
92
}
67
93
68
94
@ Override
69
95
public void onNext (T t ) {
96
+ if (!ensureCapacity ()) {
97
+ return ;
98
+ }
70
99
queue .offer (on .next (t ));
71
- pollQueue (wip , requested , queue , child );
100
+ pollQueue (wip , requested , capacity , queue , child );
72
101
}
73
102
103
+ private boolean ensureCapacity () {
104
+ if (capacity == null ) {
105
+ return true ;
106
+ }
107
+
108
+ long currCapacity ;
109
+ do {
110
+ currCapacity = capacity .get ();
111
+ if (currCapacity <= 0 ) {
112
+ if (saturated .compareAndSet (false , true )) {
113
+ // ensure single completion contract
114
+ child .onError (new MissingBackpressureException ("Overflowed buffer of " + OperatorOnBackpressureBuffer .this .capacity ));
115
+ unsubscribe ();
116
+ if (onOverflow != null ) {
117
+ onOverflow .call ();
118
+ }
119
+ }
120
+ return false ;
121
+ }
122
+ // ensure no other thread stole our slot, or retry
123
+ } while (!capacity .compareAndSet (currCapacity , currCapacity - 1 ));
124
+ return true ;
125
+ }
74
126
};
75
127
76
128
// if child unsubscribes it should unsubscribe the parent, but not the other way around
@@ -79,7 +131,7 @@ public void onNext(T t) {
79
131
return parent ;
80
132
}
81
133
82
- private void pollQueue (AtomicLong wip , AtomicLong requested , Queue <Object > queue , Subscriber <? super T > child ) {
134
+ private void pollQueue (AtomicLong wip , AtomicLong requested , AtomicLong capacity , Queue <Object > queue , Subscriber <? super T > child ) {
83
135
// 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?
84
136
if (requested .get () > 0 ) {
85
137
// only one draining at a time
@@ -96,6 +148,9 @@ private void pollQueue(AtomicLong wip, AtomicLong requested, Queue<Object> queue
96
148
requested .incrementAndGet ();
97
149
return ;
98
150
}
151
+ if (capacity != null ) { // it's bounded
152
+ capacity .incrementAndGet ();
153
+ }
99
154
on .accept (child , o );
100
155
} else {
101
156
// we hit the end ... so increment back to 0 again
0 commit comments