16
16
package rx .internal .schedulers ;
17
17
18
18
import java .lang .reflect .Method ;
19
+ import java .util .Iterator ;
19
20
import java .util .concurrent .*;
21
+ import java .util .concurrent .atomic .AtomicReference ;
20
22
21
23
import rx .*;
24
+ import rx .exceptions .Exceptions ;
22
25
import rx .functions .Action0 ;
26
+ import rx .internal .util .RxThreadFactory ;
23
27
import rx .plugins .*;
24
28
import rx .subscriptions .Subscriptions ;
25
29
@@ -30,24 +34,111 @@ public class NewThreadWorker extends Scheduler.Worker implements Subscription {
30
34
private final ScheduledExecutorService executor ;
31
35
private final RxJavaSchedulersHook schedulersHook ;
32
36
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
+
34
132
/* package */
35
133
public NewThreadWorker (ThreadFactory threadFactory ) {
36
- executor = Executors .newScheduledThreadPool (1 , threadFactory );
134
+ ScheduledExecutorService exec = Executors .newScheduledThreadPool (1 , threadFactory );
37
135
// 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 );
49
139
}
50
140
schedulersHook = RxJavaPlugins .getInstance ().getSchedulersHook ();
141
+ executor = exec ;
51
142
}
52
143
53
144
@ Override
@@ -88,6 +179,7 @@ public ScheduledAction scheduleActual(final Action0 action, long delayTime, Time
88
179
public void unsubscribe () {
89
180
isUnsubscribed = true ;
90
181
executor .shutdownNow ();
182
+ deregisterExecutor (executor );
91
183
}
92
184
93
185
@ Override
0 commit comments