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 ;
2924import rx .plugins .RxJavaPlugins ;
30- import rx .subscriptions .CompositeSubscription ;
31- import rx .subscriptions .MultipleAssignmentSubscription ;
32- import rx .subscriptions .Subscriptions ;
25+ import rx .subscriptions .*;
3326
3427/**
3528 * 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
5851 // TODO: use a better performing structure for task tracking
5952 final CompositeSubscription tasks ;
6053 // TODO: use MpscLinkedQueue once available
61- final ConcurrentLinkedQueue <ExecutorAction > queue ;
54+ final ConcurrentLinkedQueue <ScheduledAction > queue ;
6255 final AtomicInteger wip ;
6356
6457 public ExecutorSchedulerWorker (Executor executor ) {
6558 this .executor = executor ;
66- this .queue = new ConcurrentLinkedQueue <ExecutorAction >();
59+ this .queue = new ConcurrentLinkedQueue <ScheduledAction >();
6760 this .wip = new AtomicInteger ();
6861 this .tasks = new CompositeSubscription ();
6962 }
@@ -73,11 +66,15 @@ public Subscription schedule(Action0 action) {
7366 if (isUnsubscribed ()) {
7467 return Subscriptions .unsubscribed ();
7568 }
76- ExecutorAction ea = new ExecutorAction (action , tasks );
69+ ScheduledAction ea = new ScheduledAction (action , tasks );
7770 tasks .add (ea );
7871 queue .offer (ea );
7972 if (wip .getAndIncrement () == 0 ) {
8073 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
8178 executor .execute (this );
8279 } catch (RejectedExecutionException t ) {
8380 // cleanup if rejected
@@ -96,7 +93,10 @@ public Subscription schedule(Action0 action) {
9693 @ Override
9794 public void run () {
9895 do {
99- queue .poll ().run ();
96+ ScheduledAction sa = queue .poll ();
97+ if (!sa .isUnsubscribed ()) {
98+ sa .run ();
99+ }
100100 } while (wip .decrementAndGet () > 0 );
101101 }
102102
@@ -115,28 +115,54 @@ public Subscription schedule(final Action0 action, long delayTime, TimeUnit unit
115115 service = GenericScheduledExecutorService .getInstance ();
116116 }
117117
118+ final MultipleAssignmentSubscription first = new MultipleAssignmentSubscription ();
118119 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+ });
120128
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 ;
130134 }
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 );
133155 } catch (RejectedExecutionException t ) {
134156 // report the rejection to plugins
135157 RxJavaPlugins .getInstance ().getErrorHandler ().handleError (t );
136158 throw t ;
137159 }
138160
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 ;
140166 }
141167
142168 @ Override
@@ -150,46 +176,4 @@ public void unsubscribe() {
150176 }
151177
152178 }
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- }
195179}
0 commit comments