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