1616package rx .internal .schedulers ;
1717
1818import java .lang .reflect .Method ;
19+ import java .util .Iterator ;
1920import java .util .concurrent .*;
21+ import java .util .concurrent .atomic .AtomicReference ;
2022
2123import rx .*;
24+ import rx .exceptions .Exceptions ;
2225import rx .functions .Action0 ;
26+ import rx .internal .util .RxThreadFactory ;
2327import rx .plugins .*;
2428import rx .subscriptions .Subscriptions ;
2529
@@ -30,24 +34,111 @@ public class NewThreadWorker extends Scheduler.Worker implements Subscription {
3034 private final ScheduledExecutorService executor ;
3135 private final RxJavaSchedulersHook schedulersHook ;
3236 volatile boolean isUnsubscribed ;
33-
37+ /** The purge frequency in milliseconds. */
38+ private static final String FREQUENCY_KEY = "io.reactivex.rxjava.scheduler.jdk6.purge-frequency-millis" ;
39+ /** Force the use of purge (true/false). */
40+ private static final String PURGE_FORCE_KEY = "io.reactivex.rxjava.scheduler.jdk6.purge-force" ;
41+ private static final String PURGE_THREAD_PREFIX = "RxSchedulerPurge-" ;
42+ /** Forces the use of purge even if setRemoveOnCancelPolicy is available. */
43+ private static final boolean PURGE_FORCE ;
44+ /** The purge frequency in milliseconds. */
45+ public static final int PURGE_FREQUENCY ;
46+ private static final ConcurrentHashMap <ScheduledThreadPoolExecutor , ScheduledThreadPoolExecutor > EXECUTORS ;
47+ private static final AtomicReference <ScheduledExecutorService > PURGE ;
48+ static {
49+ EXECUTORS = new ConcurrentHashMap <ScheduledThreadPoolExecutor , ScheduledThreadPoolExecutor >();
50+ PURGE = new AtomicReference <ScheduledExecutorService >();
51+ PURGE_FORCE = Boolean .getBoolean (PURGE_FORCE_KEY );
52+ PURGE_FREQUENCY = Integer .getInteger (FREQUENCY_KEY , 1000 );
53+ }
54+ /**
55+ * Registers the given executor service and starts the purge thread if not already started.
56+ * <p>{@code public} visibility reason: called from other package(s) within RxJava
57+ * @param service a scheduled thread pool executor instance
58+ */
59+ public static void registerExecutor (ScheduledThreadPoolExecutor service ) {
60+ do {
61+ ScheduledExecutorService exec = PURGE .get ();
62+ if (exec != null ) {
63+ break ;
64+ }
65+ exec = Executors .newScheduledThreadPool (1 , new RxThreadFactory (PURGE_THREAD_PREFIX ));
66+ if (PURGE .compareAndSet (null , exec )) {
67+ exec .scheduleAtFixedRate (new Runnable () {
68+ @ Override
69+ public void run () {
70+ purgeExecutors ();
71+ }
72+ }, PURGE_FREQUENCY , PURGE_FREQUENCY , TimeUnit .MILLISECONDS );
73+
74+ break ;
75+ }
76+ } while (true );
77+
78+ EXECUTORS .putIfAbsent (service , service );
79+ }
80+ /**
81+ * Deregisters the executor service.
82+ * <p>{@code public} visibility reason: called from other package(s) within RxJava
83+ * @param service a scheduled thread pool executor instance
84+ */
85+ public static void deregisterExecutor (ScheduledExecutorService service ) {
86+ EXECUTORS .remove (service );
87+ }
88+ /** Purges each registered executor and eagerly evicts shutdown executors. */
89+ static void purgeExecutors () {
90+ try {
91+ Iterator <ScheduledThreadPoolExecutor > it = EXECUTORS .keySet ().iterator ();
92+ while (it .hasNext ()) {
93+ ScheduledThreadPoolExecutor exec = it .next ();
94+ if (!exec .isShutdown ()) {
95+ exec .purge ();
96+ } else {
97+ it .remove ();
98+ }
99+ }
100+ } catch (Throwable t ) {
101+ Exceptions .throwIfFatal (t );
102+ RxJavaPlugins .getInstance ().getErrorHandler ().handleError (t );
103+ }
104+ }
105+
106+ /**
107+ * Tries to enable the Java 7+ setRemoveOnCancelPolicy.
108+ * <p>{@code public} visibility reason: called from other package(s) within RxJava.
109+ * If the method returns false, the {@link #registerExecutor(ScheduledThreadPoolExecutor)} may
110+ * be called to enable the backup option of purging the executors.
111+ * @param exec the executor to call setRemoveOnCaneclPolicy if available.
112+ * @return true if the policy was successfully enabled
113+ */
114+ public static boolean tryEnableCancelPolicy (ScheduledExecutorService exec ) {
115+ if (!PURGE_FORCE ) {
116+ for (Method m : exec .getClass ().getMethods ()) {
117+ if (m .getName ().equals ("setRemoveOnCancelPolicy" )
118+ && m .getParameterTypes ().length == 1
119+ && m .getParameterTypes ()[0 ] == Boolean .TYPE ) {
120+ try {
121+ m .invoke (exec , true );
122+ return true ;
123+ } catch (Exception ex ) {
124+ RxJavaPlugins .getInstance ().getErrorHandler ().handleError (ex );
125+ }
126+ }
127+ }
128+ }
129+ return false ;
130+ }
131+
34132 /* package */
35133 public NewThreadWorker (ThreadFactory threadFactory ) {
36- executor = Executors .newScheduledThreadPool (1 , threadFactory );
134+ ScheduledExecutorService exec = Executors .newScheduledThreadPool (1 , threadFactory );
37135 // Java 7+: cancelled future tasks can be removed from the executor thus avoiding memory leak
38- for (Method m : executor .getClass ().getMethods ()) {
39- if (m .getName ().equals ("setRemoveOnCancelPolicy" )
40- && m .getParameterTypes ().length == 1
41- && m .getParameterTypes ()[0 ] == Boolean .TYPE ) {
42- try {
43- m .invoke (executor , true );
44- } catch (Exception ex ) {
45- RxJavaPlugins .getInstance ().getErrorHandler ().handleError (ex );
46- }
47- break ;
48- }
136+ boolean cancelSupported = tryEnableCancelPolicy (exec );
137+ if (!cancelSupported && exec instanceof ScheduledThreadPoolExecutor ) {
138+ registerExecutor ((ScheduledThreadPoolExecutor )exec );
49139 }
50140 schedulersHook = RxJavaPlugins .getInstance ().getSchedulersHook ();
141+ executor = exec ;
51142 }
52143
53144 @ Override
@@ -88,6 +179,7 @@ public ScheduledAction scheduleActual(final Action0 action, long delayTime, Time
88179 public void unsubscribe () {
89180 isUnsubscribed = true ;
90181 executor .shutdownNow ();
182+ deregisterExecutor (executor );
91183 }
92184
93185 @ Override
0 commit comments