All Downloads are FREE. Search and download functionalities are using the official Maven repository.

rx.internal.schedulers.NewThreadWorker Maven / Gradle / Ivy

There is a newer version: 1.3.8
Show newest version
/**
 * Copyright 2014 Netflix, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package rx.internal.schedulers;

import java.lang.reflect.*;
import java.util.Iterator;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;

import rx.*;
import rx.exceptions.Exceptions;
import rx.functions.Action0;
import rx.internal.util.*;
import rx.plugins.*;
import rx.subscriptions.*;

import static rx.internal.util.PlatformDependent.ANDROID_API_VERSION_IS_NOT_ANDROID;

/**
 * @warn class description missing
 */
public class NewThreadWorker extends Scheduler.Worker implements Subscription {
    private final ScheduledExecutorService executor;
    volatile boolean isUnsubscribed;
    /** The purge frequency in milliseconds. */
    private static final String FREQUENCY_KEY = "rx.scheduler.jdk6.purge-frequency-millis";
    /** Force the use of purge (true/false). */
    private static final String PURGE_FORCE_KEY = "rx.scheduler.jdk6.purge-force";
    private static final String PURGE_THREAD_PREFIX = "RxSchedulerPurge-";
    private static final boolean SHOULD_TRY_ENABLE_CANCEL_POLICY;
    /** The purge frequency in milliseconds. */
    public static final int PURGE_FREQUENCY;
    private static final ConcurrentHashMap EXECUTORS;
    private static final AtomicReference PURGE;
    /**
     * Improves performance of {@link #tryEnableCancelPolicy(ScheduledExecutorService)}.
     * Also, it works even for inheritance: {@link Method} of base class can be invoked on the instance of child class.
     */
    private static volatile Object cachedSetRemoveOnCancelPolicyMethod;

    /**
     * Possible value of {@link #cachedSetRemoveOnCancelPolicyMethod} which means that cancel policy is not supported.
     */
    private static final Object SET_REMOVE_ON_CANCEL_POLICY_METHOD_NOT_SUPPORTED = new Object();
    
    static {
        EXECUTORS = new ConcurrentHashMap();
        PURGE = new AtomicReference();
        PURGE_FREQUENCY = Integer.getInteger(FREQUENCY_KEY, 1000);

        // Forces the use of purge even if setRemoveOnCancelPolicy is available
        final boolean purgeForce = Boolean.getBoolean(PURGE_FORCE_KEY);

        final int androidApiVersion = PlatformDependent.getAndroidApiVersion();

        // According to http://developer.android.com/reference/java/util/concurrent/ScheduledThreadPoolExecutor.html#setRemoveOnCancelPolicy(boolean)
        // setRemoveOnCancelPolicy available since Android API 21
        SHOULD_TRY_ENABLE_CANCEL_POLICY = !purgeForce
                && (androidApiVersion == ANDROID_API_VERSION_IS_NOT_ANDROID || androidApiVersion >= 21);
    }
    /** 
     * Registers the given executor service and starts the purge thread if not already started. 
     * 

{@code public} visibility reason: called from other package(s) within RxJava * @param service a scheduled thread pool executor instance */ public static void registerExecutor(ScheduledThreadPoolExecutor service) { do { ScheduledExecutorService exec = PURGE.get(); if (exec != null) { break; } exec = Executors.newScheduledThreadPool(1, new RxThreadFactory(PURGE_THREAD_PREFIX)); if (PURGE.compareAndSet(null, exec)) { exec.scheduleAtFixedRate(new Runnable() { @Override public void run() { purgeExecutors(); } }, PURGE_FREQUENCY, PURGE_FREQUENCY, TimeUnit.MILLISECONDS); break; } else { exec.shutdownNow(); } } while (true); EXECUTORS.putIfAbsent(service, service); } /** * Deregisters the executor service. *

{@code public} visibility reason: called from other package(s) within RxJava * @param service a scheduled thread pool executor instance */ public static void deregisterExecutor(ScheduledExecutorService service) { EXECUTORS.remove(service); } /** Purges each registered executor and eagerly evicts shutdown executors. */ @SuppressAnimalSniffer // CHM.keySet returns KeySetView in Java 8+; false positive here static void purgeExecutors() { try { Iterator it = EXECUTORS.keySet().iterator(); while (it.hasNext()) { ScheduledThreadPoolExecutor exec = it.next(); if (!exec.isShutdown()) { exec.purge(); } else { it.remove(); } } } catch (Throwable t) { Exceptions.throwIfFatal(t); RxJavaHooks.onError(t); } } /** * Tries to enable the Java 7+ setRemoveOnCancelPolicy. *

{@code public} visibility reason: called from other package(s) within RxJava. * If the method returns false, the {@link #registerExecutor(ScheduledThreadPoolExecutor)} may * be called to enable the backup option of purging the executors. * @param executor the executor to call setRemoveOnCaneclPolicy if available. * @return true if the policy was successfully enabled */ public static boolean tryEnableCancelPolicy(ScheduledExecutorService executor) { if (SHOULD_TRY_ENABLE_CANCEL_POLICY) { // NOPMD final boolean isInstanceOfScheduledThreadPoolExecutor = executor instanceof ScheduledThreadPoolExecutor; Method methodToCall; if (isInstanceOfScheduledThreadPoolExecutor) { final Object localSetRemoveOnCancelPolicyMethod = cachedSetRemoveOnCancelPolicyMethod; if (localSetRemoveOnCancelPolicyMethod == SET_REMOVE_ON_CANCEL_POLICY_METHOD_NOT_SUPPORTED) { return false; } if (localSetRemoveOnCancelPolicyMethod == null) { Method method = findSetRemoveOnCancelPolicyMethod(executor); cachedSetRemoveOnCancelPolicyMethod = method != null ? method : SET_REMOVE_ON_CANCEL_POLICY_METHOD_NOT_SUPPORTED; methodToCall = method; } else { methodToCall = (Method) localSetRemoveOnCancelPolicyMethod; } } else { methodToCall = findSetRemoveOnCancelPolicyMethod(executor); } if (methodToCall != null) { try { methodToCall.invoke(executor, true); return true; } catch (InvocationTargetException e) { RxJavaHooks.onError(e); } catch (IllegalAccessException e) { RxJavaHooks.onError(e); } catch (IllegalArgumentException e) { RxJavaHooks.onError(e); } } } return false; } /** * Tries to find {@code "setRemoveOnCancelPolicy(boolean)"} method in the class of passed executor. * * @param executor whose class will be used to search for required method. * @return {@code "setRemoveOnCancelPolicy(boolean)"} {@link Method} * or {@code null} if required {@link Method} was not found. */ static Method findSetRemoveOnCancelPolicyMethod(ScheduledExecutorService executor) { // The reason for the loop is to avoid NoSuchMethodException being thrown on JDK 6 // which is more costly than looping through ~70 methods. for (final Method method : executor.getClass().getMethods()) { if (method.getName().equals("setRemoveOnCancelPolicy")) { final Class[] parameterTypes = method.getParameterTypes(); if (parameterTypes.length == 1 && parameterTypes[0] == Boolean.TYPE) { return method; } } } return null; } /* package */ public NewThreadWorker(ThreadFactory threadFactory) { ScheduledExecutorService exec = Executors.newScheduledThreadPool(1, threadFactory); // Java 7+: cancelled future tasks can be removed from the executor thus avoiding memory leak boolean cancelSupported = tryEnableCancelPolicy(exec); if (!cancelSupported && exec instanceof ScheduledThreadPoolExecutor) { registerExecutor((ScheduledThreadPoolExecutor)exec); } executor = exec; } @Override public Subscription schedule(final Action0 action) { return schedule(action, 0, null); } @Override public Subscription schedule(final Action0 action, long delayTime, TimeUnit unit) { if (isUnsubscribed) { return Subscriptions.unsubscribed(); } return scheduleActual(action, delayTime, unit); } /** * Schedules the given action by wrapping it into a ScheduledAction on the * underlying ExecutorService, returning the ScheduledAction. * @param action the action to wrap and schedule * @param delayTime the delay in execution * @param unit the time unit of the delay * @return the wrapper ScheduledAction */ public ScheduledAction scheduleActual(final Action0 action, long delayTime, TimeUnit unit) { Action0 decoratedAction = RxJavaHooks.onScheduledAction(action); ScheduledAction run = new ScheduledAction(decoratedAction); Future f; if (delayTime <= 0) { f = executor.submit(run); } else { f = executor.schedule(run, delayTime, unit); } run.add(f); return run; } public ScheduledAction scheduleActual(final Action0 action, long delayTime, TimeUnit unit, CompositeSubscription parent) { Action0 decoratedAction = RxJavaHooks.onScheduledAction(action); ScheduledAction run = new ScheduledAction(decoratedAction, parent); parent.add(run); Future f; if (delayTime <= 0) { f = executor.submit(run); } else { f = executor.schedule(run, delayTime, unit); } run.add(f); return run; } public ScheduledAction scheduleActual(final Action0 action, long delayTime, TimeUnit unit, SubscriptionList parent) { Action0 decoratedAction = RxJavaHooks.onScheduledAction(action); ScheduledAction run = new ScheduledAction(decoratedAction, parent); parent.add(run); Future f; if (delayTime <= 0) { f = executor.submit(run); } else { f = executor.schedule(run, delayTime, unit); } run.add(f); return run; } @Override public void unsubscribe() { isUnsubscribed = true; executor.shutdownNow(); deregisterExecutor(executor); } @Override public boolean isUnsubscribed() { return isUnsubscribed; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy