1515 */
1616package rx .schedulers ;
1717
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 .*;
2419import java .util .concurrent .atomic .AtomicInteger ;
25- import java .util .concurrent .atomic .AtomicIntegerFieldUpdater ;
26- import rx .Scheduler ;
27- import rx .Subscription ;
20+
21+ import rx .*;
2822import rx .functions .Action0 ;
23+ import rx .internal .schedulers .ScheduledAction ;
24+ import rx .internal .util .SubscriptionList ;
2925import rx .plugins .RxJavaPlugins ;
30- import rx .subscriptions .CompositeSubscription ;
31- import rx .subscriptions .MultipleAssignmentSubscription ;
32- import rx .subscriptions .Subscriptions ;
26+ import rx .subscriptions .*;
3327
3428/**
3529 * 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
5852 // TODO: use a better performing structure for task tracking
5953 final CompositeSubscription tasks ;
6054 // TODO: use MpscLinkedQueue once available
61- final ConcurrentLinkedQueue <ExecutorAction > queue ;
55+ final ConcurrentLinkedQueue <ScheduledAction > queue ;
6256 final AtomicInteger wip ;
6357
6458 public ExecutorSchedulerWorker (Executor executor ) {
6559 this .executor = executor ;
66- this .queue = new ConcurrentLinkedQueue <ExecutorAction >();
60+ this .queue = new ConcurrentLinkedQueue <ScheduledAction >();
6761 this .wip = new AtomicInteger ();
6862 this .tasks = new CompositeSubscription ();
6963 }
@@ -73,11 +67,15 @@ public Subscription schedule(Action0 action) {
7367 if (isUnsubscribed ()) {
7468 return Subscriptions .unsubscribed ();
7569 }
76- ExecutorAction ea = new ExecutorAction (action , tasks );
70+ ScheduledAction ea = new ScheduledAction (action , tasks );
7771 tasks .add (ea );
7872 queue .offer (ea );
7973 if (wip .getAndIncrement () == 0 ) {
8074 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
8179 executor .execute (this );
8280 } catch (RejectedExecutionException t ) {
8381 // cleanup if rejected
@@ -96,7 +94,10 @@ public Subscription schedule(Action0 action) {
9694 @ Override
9795 public void run () {
9896 do {
99- queue .poll ().run ();
97+ ScheduledAction sa = queue .poll ();
98+ if (!sa .isUnsubscribed ()) {
99+ sa .run ();
100+ }
100101 } while (wip .decrementAndGet () > 0 );
101102 }
102103
@@ -115,28 +116,56 @@ public Subscription schedule(final Action0 action, long delayTime, TimeUnit unit
115116 service = GenericScheduledExecutorService .getInstance ();
116117 }
117118
119+ final MultipleAssignmentSubscription first = new MultipleAssignmentSubscription ();
118120 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+ });
120129
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 ;
130135 }
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 );
133156 } catch (RejectedExecutionException t ) {
134157 // report the rejection to plugins
135158 RxJavaPlugins .getInstance ().getErrorHandler ().handleError (t );
136159 throw t ;
137160 }
138161
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 ;
140169 }
141170
142171 @ Override
@@ -150,46 +179,4 @@ public void unsubscribe() {
150179 }
151180
152181 }
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- }
195182}
0 commit comments