19
19
import java .util .concurrent .ConcurrentHashMap ;
20
20
import java .util .concurrent .ConcurrentLinkedQueue ;
21
21
import java .util .concurrent .atomic .AtomicBoolean ;
22
+ import java .util .concurrent .atomic .AtomicInteger ;
22
23
import java .util .concurrent .atomic .AtomicIntegerFieldUpdater ;
23
24
import java .util .concurrent .atomic .AtomicLong ;
24
25
import java .util .concurrent .atomic .AtomicLongFieldUpdater ;
34
35
import rx .functions .Func1 ;
35
36
import rx .observables .GroupedObservable ;
36
37
import rx .subjects .Subject ;
38
+ import rx .subscriptions .Subscriptions ;
37
39
38
40
/**
39
41
* Groups the items emitted by an Observable according to a specified criterion, and emits these
@@ -76,6 +78,10 @@ static final class GroupBySubscriber<K, T, R> extends Subscriber<T> {
76
78
final Func1 <? super T , ? extends R > elementSelector ;
77
79
final Subscriber <? super GroupedObservable <K , R >> child ;
78
80
81
+ @ SuppressWarnings ("rawtypes" )
82
+ static final AtomicIntegerFieldUpdater <GroupBySubscriber > WIP_FOR_UNSUBSCRIBE_UPDATER = AtomicIntegerFieldUpdater .newUpdater (GroupBySubscriber .class , "wipForUnsubscribe" );
83
+ volatile int wipForUnsubscribe = 1 ;
84
+
79
85
public GroupBySubscriber (
80
86
Func1 <? super T , ? extends K > keySelector ,
81
87
Func1 <? super T , ? extends R > elementSelector ,
@@ -84,6 +90,16 @@ public GroupBySubscriber(
84
90
this .keySelector = keySelector ;
85
91
this .elementSelector = elementSelector ;
86
92
this .child = child ;
93
+ child .add (Subscriptions .create (new Action0 () {
94
+
95
+ @ Override
96
+ public void call () {
97
+ if (WIP_FOR_UNSUBSCRIBE_UPDATER .decrementAndGet (self ) == 0 ) {
98
+ self .unsubscribe ();
99
+ }
100
+ }
101
+
102
+ }));
87
103
}
88
104
89
105
private static class GroupState <K , T > {
@@ -138,7 +154,7 @@ public void onCompleted() {
138
154
}
139
155
140
156
// special case (no groups emitted ... or all unsubscribed)
141
- if (groups .size () == 0 ) {
157
+ if (groups .isEmpty () ) {
142
158
// we must track 'completionEmitted' seperately from 'completed' since `completeInner` can result in childObserver.onCompleted() being emitted
143
159
if (COMPLETION_EMITTED_UPDATER .compareAndSet (this , 0 , 1 )) {
144
160
child .onCompleted ();
@@ -150,8 +166,13 @@ public void onCompleted() {
150
166
@ Override
151
167
public void onError (Throwable e ) {
152
168
if (TERMINATED_UPDATER .compareAndSet (this , 0 , 1 )) {
153
- // we immediately tear everything down if we receive an error
154
- child .onError (e );
169
+ try {
170
+ // we immediately tear everything down if we receive an error
171
+ child .onError (e );
172
+ } finally {
173
+ // We have not chained the subscribers, so need to call it explicitly.
174
+ unsubscribe ();
175
+ }
155
176
}
156
177
}
157
178
@@ -187,7 +208,9 @@ public void onNext(T t) {
187
208
}
188
209
group = createNewGroup (key );
189
210
}
190
- emitItem (group , nl .next (t ));
211
+ if (group != null ) {
212
+ emitItem (group , nl .next (t ));
213
+ }
191
214
} catch (Throwable e ) {
192
215
onError (OnErrorThrowable .addValueAsLastCause (e , t ));
193
216
}
@@ -250,7 +273,17 @@ public void onNext(T t) {
250
273
}
251
274
});
252
275
253
- GroupState <K , T > putIfAbsent = groups .putIfAbsent (key , groupState );
276
+ GroupState <K , T > putIfAbsent ;
277
+ for (;;) {
278
+ int wip = wipForUnsubscribe ;
279
+ if (wip <= 0 ) {
280
+ return null ;
281
+ }
282
+ if (WIP_FOR_UNSUBSCRIBE_UPDATER .compareAndSet (this , wip , wip + 1 )) {
283
+ putIfAbsent = groups .putIfAbsent (key , groupState );
284
+ break ;
285
+ }
286
+ }
254
287
if (putIfAbsent != null ) {
255
288
// this shouldn't happen (because we receive onNext sequentially) and would mean we have a bug
256
289
throw new IllegalStateException ("Group already existed while creating a new one" );
@@ -264,7 +297,7 @@ private void cleanupGroup(Object key) {
264
297
GroupState <K , T > removed ;
265
298
removed = groups .remove (key );
266
299
if (removed != null ) {
267
- if (removed .buffer .size () > 0 ) {
300
+ if (! removed .buffer .isEmpty () ) {
268
301
BUFFERED_COUNT .addAndGet (self , -removed .buffer .size ());
269
302
}
270
303
completeInner ();
@@ -342,16 +375,20 @@ private void drainIfPossible(GroupState<K, T> groupState) {
342
375
}
343
376
344
377
private void completeInner () {
378
+ if (WIP_FOR_UNSUBSCRIBE_UPDATER .decrementAndGet (this ) == 0 ) {
379
+ unsubscribe ();
380
+ }
345
381
// if we have no outstanding groups (all completed or unsubscribe) and terminated/unsubscribed on outer
346
- if (groups .size () == 0 && (terminated == 1 || child .isUnsubscribed ())) {
382
+ if (groups .isEmpty () && (terminated == 1 || child .isUnsubscribed ())) {
347
383
// completionEmitted ensures we only emit onCompleted once
348
384
if (COMPLETION_EMITTED_UPDATER .compareAndSet (this , 0 , 1 )) {
349
385
350
386
if (child .isUnsubscribed ()) {
351
387
// if the entire groupBy has been unsubscribed and children are completed we will propagate the unsubscribe up.
352
388
unsubscribe ();
389
+ } else {
390
+ child .onCompleted ();
353
391
}
354
- child .onCompleted ();
355
392
}
356
393
}
357
394
}
0 commit comments