1
- /**
2
- * Copyright 2013 Netflix, Inc.
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- */
1
+ /**
2
+ * Copyright 2013 Netflix, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
16
package rx .subscriptions ;
17
17
18
- import static java .util .Arrays .asList ;
19
- import static java .util .Collections .unmodifiableSet ;
20
-
21
18
import java .util .ArrayList ;
19
+ import java .util .Arrays ;
22
20
import java .util .Collection ;
23
- import java .util .Collections ;
24
- import java .util .HashSet ;
25
- import java .util .Set ;
21
+ import java .util .List ;
26
22
import java .util .concurrent .atomic .AtomicReference ;
27
23
28
24
import rx .Subscription ;
31
27
/**
32
28
* Subscription that represents a group of Subscriptions that are unsubscribed
33
29
* together.
34
- *
35
- * @see <a
36
- * href="http://msdn.microsoft.com/en-us/library/system.reactive.disposables.compositedisposable(v=vs.103).aspx">Rx.Net
37
- * equivalent CompositeDisposable</a>
30
+ *
31
+ * @see <a href="http://msdn.microsoft.com/en-us/library/system.reactive.disposables.compositedisposable(v=vs.103).aspx">Rx.Net equivalent CompositeDisposable</a>
38
32
*/
39
- public class CompositeSubscription implements Subscription {
40
- /** Sentinel to indicate a thread is modifying the subscription set. */
41
- private static final Set <Subscription > MUTATE_SENTINEL = unmodifiableSet (Collections .<Subscription >emptySet ());
42
- /** Sentinel to indicate the entire CompositeSubscription has been unsubscribed.*/
43
- private static final Set <Subscription > UNSUBSCRIBED_SENTINEL = unmodifiableSet (Collections .<Subscription >emptySet ());
44
- /** The reference to the set of subscriptions. */
45
- private final AtomicReference <Set <Subscription >> reference = new AtomicReference <Set <Subscription >>();
46
-
33
+ public final class CompositeSubscription implements Subscription {
34
+
35
+ private final AtomicReference <State > state = new AtomicReference <State >();
36
+
37
+ private static final class State {
38
+ final boolean isUnsubscribed ;
39
+ final List <Subscription > subscriptions ;
40
+
41
+ State (boolean u , List <Subscription > s ) {
42
+ this .isUnsubscribed = u ;
43
+ this .subscriptions = s ;
44
+ }
45
+
46
+ State unsubscribe () {
47
+ return new State (true , subscriptions );
48
+ }
49
+
50
+ State add (Subscription s ) {
51
+ List <Subscription > newSubscriptions = new ArrayList <Subscription >();
52
+ newSubscriptions .addAll (subscriptions );
53
+ newSubscriptions .add (s );
54
+ return new State (isUnsubscribed , newSubscriptions );
55
+ }
56
+
57
+ State remove (Subscription s ) {
58
+ List <Subscription > newSubscriptions = new ArrayList <Subscription >();
59
+ newSubscriptions .addAll (subscriptions );
60
+ newSubscriptions .remove (s ); // only first occurrence
61
+ return new State (isUnsubscribed , newSubscriptions );
62
+ }
63
+
64
+ State clear () {
65
+ return new State (isUnsubscribed , new ArrayList <Subscription >());
66
+ }
67
+ }
68
+
47
69
public CompositeSubscription (final Subscription ... subscriptions ) {
48
- reference .set (new HashSet < Subscription >( asList (subscriptions )));
70
+ state .set (new State ( false , Arrays . asList (subscriptions )));
49
71
}
50
-
72
+
51
73
public boolean isUnsubscribed () {
52
- return reference .get () == UNSUBSCRIBED_SENTINEL ;
74
+ return state .get (). isUnsubscribed ;
53
75
}
54
-
76
+
55
77
public void add (final Subscription s ) {
78
+ State oldState ;
79
+ State newState ;
56
80
do {
57
- final Set < Subscription > existing = reference .get ();
58
- if (existing == UNSUBSCRIBED_SENTINEL ) {
81
+ oldState = state .get ();
82
+ if (oldState . isUnsubscribed ) {
59
83
s .unsubscribe ();
60
- break ;
61
- }
62
-
63
- if (existing == MUTATE_SENTINEL ) {
64
- continue ;
65
- }
66
-
67
- if (reference .compareAndSet (existing , MUTATE_SENTINEL )) {
68
- existing .add (s );
69
- reference .set (existing );
70
- break ;
84
+ return ;
85
+ } else {
86
+ newState = oldState .add (s );
71
87
}
72
- } while (true );
88
+ } while (! state . compareAndSet ( oldState , newState ) );
73
89
}
74
-
90
+
75
91
public void remove (final Subscription s ) {
92
+ State oldState ;
93
+ State newState ;
76
94
do {
77
- final Set <Subscription > subscriptions = reference .get ();
78
- if (subscriptions == UNSUBSCRIBED_SENTINEL ) {
79
- s .unsubscribe ();
80
- break ;
81
- }
82
-
83
- if (subscriptions == MUTATE_SENTINEL ) {
84
- continue ;
85
- }
86
-
87
- if (reference .compareAndSet (subscriptions , MUTATE_SENTINEL )) {
88
- // also unsubscribe from it:
89
- // http://msdn.microsoft.com/en-us/library/system.reactive.disposables.compositedisposable.remove(v=vs.103).aspx
90
- subscriptions .remove (s );
91
- reference .set (subscriptions );
92
- s .unsubscribe ();
93
- break ;
95
+ oldState = state .get ();
96
+ if (oldState .isUnsubscribed ) {
97
+ return ;
98
+ } else {
99
+ newState = oldState .remove (s );
94
100
}
95
- } while (true );
101
+ } while (!state .compareAndSet (oldState , newState ));
102
+ // if we removed successfully we then need to call unsubscribe on it
103
+ s .unsubscribe ();
96
104
}
97
-
105
+
98
106
public void clear () {
107
+ State oldState ;
108
+ State newState ;
99
109
do {
100
- final Set <Subscription > subscriptions = reference .get ();
101
- if (subscriptions == UNSUBSCRIBED_SENTINEL ) {
102
- break ;
103
- }
104
-
105
- if (subscriptions == MUTATE_SENTINEL ) {
106
- continue ;
110
+ oldState = state .get ();
111
+ if (oldState .isUnsubscribed ) {
112
+ return ;
113
+ } else {
114
+ newState = oldState .clear ();
107
115
}
108
-
109
- if (reference .compareAndSet (subscriptions , MUTATE_SENTINEL )) {
110
- final Set <Subscription > copy = new HashSet <Subscription >(
111
- subscriptions );
112
- subscriptions .clear ();
113
- reference .set (subscriptions );
114
-
115
- unsubscribeAll (copy );
116
- break ;
116
+ } while (!state .compareAndSet (oldState , newState ));
117
+ // if we cleared successfully we then need to call unsubscribe on all previous
118
+ unsubscribeFromAll (oldState .subscriptions );
119
+ }
120
+
121
+ @ Override
122
+ public void unsubscribe () {
123
+ State oldState ;
124
+ State newState ;
125
+ do {
126
+ oldState = state .get ();
127
+ if (oldState .isUnsubscribed ) {
128
+ return ;
129
+ } else {
130
+ newState = oldState .unsubscribe ();
117
131
}
118
- } while (true );
132
+ } while (!state .compareAndSet (oldState , newState ));
133
+ unsubscribeFromAll (oldState .subscriptions );
119
134
}
120
- /**
121
- * Unsubscribe from the collection of subscriptions.
122
- * <p>
123
- * Exceptions thrown by any of the {@code unsubscribe()} methods are
124
- * collected into a {@link CompositeException} and thrown once
125
- * all unsubscriptions have been attempted.
126
- * @param subs the collection of subscriptions
127
- */
128
- private void unsubscribeAll (Collection <Subscription > subs ) {
135
+
136
+ private static void unsubscribeFromAll (Collection <Subscription > subscriptions ) {
129
137
final Collection <Throwable > es = new ArrayList <Throwable >();
130
- for (final Subscription s : subs ) {
138
+ for (Subscription s : subscriptions ) {
131
139
try {
132
140
s .unsubscribe ();
133
- } catch (final Throwable e ) {
141
+ } catch (Throwable e ) {
134
142
es .add (e );
135
143
}
136
144
}
@@ -139,22 +147,4 @@ private void unsubscribeAll(Collection<Subscription> subs) {
139
147
"Failed to unsubscribe to 1 or more subscriptions." , es );
140
148
}
141
149
}
142
- @ Override
143
- public void unsubscribe () {
144
- do {
145
- final Set <Subscription > subscriptions = reference .get ();
146
- if (subscriptions == UNSUBSCRIBED_SENTINEL ) {
147
- break ;
148
- }
149
-
150
- if (subscriptions == MUTATE_SENTINEL ) {
151
- continue ;
152
- }
153
-
154
- if (reference .compareAndSet (subscriptions , UNSUBSCRIBED_SENTINEL )) {
155
- unsubscribeAll (subscriptions );
156
- break ;
157
- }
158
- } while (true );
159
- }
160
150
}
0 commit comments