15
15
*/
16
16
package rx .schedulers ;
17
17
18
- import java .util .concurrent .ConcurrentLinkedQueue ;
19
- import java .util .concurrent .Executor ;
20
- import java .util .concurrent .Future ;
21
- import java .util .concurrent .RejectedExecutionException ;
22
- import java .util .concurrent .ScheduledExecutorService ;
23
- import java .util .concurrent .TimeUnit ;
18
+ import java .util .concurrent .*;
24
19
import java .util .concurrent .atomic .AtomicInteger ;
25
- import java .util .concurrent .atomic .AtomicIntegerFieldUpdater ;
26
- import rx .Scheduler ;
27
- import rx .Subscription ;
20
+
21
+ import rx .*;
28
22
import rx .functions .Action0 ;
23
+ import rx .internal .schedulers .ScheduledAction ;
24
+ import rx .internal .util .SubscriptionList ;
29
25
import rx .plugins .RxJavaPlugins ;
30
- import rx .subscriptions .CompositeSubscription ;
31
- import rx .subscriptions .MultipleAssignmentSubscription ;
32
- import rx .subscriptions .Subscriptions ;
26
+ import rx .subscriptions .*;
33
27
34
28
/**
35
29
* Scheduler that wraps an Executor instance and establishes the Scheduler contract upon it.
@@ -58,12 +52,12 @@ static final class ExecutorSchedulerWorker extends Scheduler.Worker implements R
58
52
// TODO: use a better performing structure for task tracking
59
53
final CompositeSubscription tasks ;
60
54
// TODO: use MpscLinkedQueue once available
61
- final ConcurrentLinkedQueue <ExecutorAction > queue ;
55
+ final ConcurrentLinkedQueue <ScheduledAction > queue ;
62
56
final AtomicInteger wip ;
63
57
64
58
public ExecutorSchedulerWorker (Executor executor ) {
65
59
this .executor = executor ;
66
- this .queue = new ConcurrentLinkedQueue <ExecutorAction >();
60
+ this .queue = new ConcurrentLinkedQueue <ScheduledAction >();
67
61
this .wip = new AtomicInteger ();
68
62
this .tasks = new CompositeSubscription ();
69
63
}
@@ -73,11 +67,15 @@ public Subscription schedule(Action0 action) {
73
67
if (isUnsubscribed ()) {
74
68
return Subscriptions .unsubscribed ();
75
69
}
76
- ExecutorAction ea = new ExecutorAction (action , tasks );
70
+ ScheduledAction ea = new ScheduledAction (action , tasks );
77
71
tasks .add (ea );
78
72
queue .offer (ea );
79
73
if (wip .getAndIncrement () == 0 ) {
80
74
try {
75
+ // note that since we schedule the emission of potentially multiple tasks
76
+ // there is no clear way to cancel this schedule from individual tasks
77
+ // so even if executor is an ExecutorService, we can't associate the future
78
+ // returned by submit() with any particular ScheduledAction
81
79
executor .execute (this );
82
80
} catch (RejectedExecutionException t ) {
83
81
// cleanup if rejected
@@ -96,7 +94,10 @@ public Subscription schedule(Action0 action) {
96
94
@ Override
97
95
public void run () {
98
96
do {
99
- queue .poll ().run ();
97
+ ScheduledAction sa = queue .poll ();
98
+ if (!sa .isUnsubscribed ()) {
99
+ sa .run ();
100
+ }
100
101
} while (wip .decrementAndGet () > 0 );
101
102
}
102
103
@@ -115,28 +116,56 @@ public Subscription schedule(final Action0 action, long delayTime, TimeUnit unit
115
116
service = GenericScheduledExecutorService .getInstance ();
116
117
}
117
118
119
+ final MultipleAssignmentSubscription first = new MultipleAssignmentSubscription ();
118
120
final MultipleAssignmentSubscription mas = new MultipleAssignmentSubscription ();
119
- // tasks.add(mas); // Needs a removal without unsubscription
121
+ mas .set (first );
122
+ tasks .add (mas );
123
+ final Subscription removeMas = Subscriptions .create (new Action0 () {
124
+ @ Override
125
+ public void call () {
126
+ tasks .remove (mas );
127
+ }
128
+ });
120
129
121
- try {
122
- Future <?> f = service .schedule (new Runnable () {
123
- @ Override
124
- public void run () {
125
- if (mas .isUnsubscribed ()) {
126
- return ;
127
- }
128
- mas .set (schedule (action ));
129
- // tasks.delete(mas); // Needs a removal without unsubscription
130
+ ScheduledAction ea = new ScheduledAction (new Action0 () {
131
+ @ Override
132
+ public void call () {
133
+ if (mas .isUnsubscribed ()) {
134
+ return ;
130
135
}
131
- }, delayTime , unit );
132
- mas .set (Subscriptions .from (f ));
136
+ // schedule the real action untimed
137
+ Subscription s2 = schedule (action );
138
+ mas .set (s2 );
139
+ // unless the worker is unsubscribed, we should get a new ScheduledAction
140
+ if (s2 .getClass () == ScheduledAction .class ) {
141
+ // when this ScheduledAction completes, we need to remove the
142
+ // MAS referencing the whole setup to avoid leaks
143
+ ((ScheduledAction )s2 ).add (removeMas );
144
+ }
145
+ }
146
+ });
147
+ // This will make sure if ea.call() gets executed before this line
148
+ // we don't override the current task in mas.
149
+ first .set (ea );
150
+ // we don't need to add ea to tasks because it will be tracked through mas/first
151
+
152
+
153
+ try {
154
+ Future <?> f = service .schedule (ea , delayTime , unit );
155
+ ea .add (f );
133
156
} catch (RejectedExecutionException t ) {
134
157
// report the rejection to plugins
135
158
RxJavaPlugins .getInstance ().getErrorHandler ().handleError (t );
136
159
throw t ;
137
160
}
138
161
139
- return mas ;
162
+ /*
163
+ * This allows cancelling either the delayed schedule or the actual schedule referenced
164
+ * by mas and makes sure mas is removed from the tasks composite to avoid leaks.
165
+ */
166
+ SubscriptionList result = new SubscriptionList (mas , removeMas );
167
+
168
+ return result ;
140
169
}
141
170
142
171
@ Override
@@ -150,46 +179,4 @@ public void unsubscribe() {
150
179
}
151
180
152
181
}
153
-
154
- /** Runs the actual action and maintains an unsubscription state. */
155
- static final class ExecutorAction implements Runnable , Subscription {
156
- final Action0 actual ;
157
- final CompositeSubscription parent ;
158
- volatile int unsubscribed ;
159
- static final AtomicIntegerFieldUpdater <ExecutorAction > UNSUBSCRIBED_UPDATER
160
- = AtomicIntegerFieldUpdater .newUpdater (ExecutorAction .class , "unsubscribed" );
161
-
162
- public ExecutorAction (Action0 actual , CompositeSubscription parent ) {
163
- this .actual = actual ;
164
- this .parent = parent ;
165
- }
166
-
167
- @ Override
168
- public void run () {
169
- if (isUnsubscribed ()) {
170
- return ;
171
- }
172
- try {
173
- actual .call ();
174
- } catch (Throwable t ) {
175
- RxJavaPlugins .getInstance ().getErrorHandler ().handleError (t );
176
- Thread thread = Thread .currentThread ();
177
- thread .getUncaughtExceptionHandler ().uncaughtException (thread , t );
178
- } finally {
179
- unsubscribe ();
180
- }
181
- }
182
- @ Override
183
- public boolean isUnsubscribed () {
184
- return unsubscribed != 0 ;
185
- }
186
-
187
- @ Override
188
- public void unsubscribe () {
189
- if (UNSUBSCRIBED_UPDATER .compareAndSet (this , 0 , 1 )) {
190
- parent .remove (this );
191
- }
192
- }
193
-
194
- }
195
182
}
0 commit comments