rx.schedulers.ExecutorScheduler Maven / Gradle / Ivy
Show all versions of rxjava-core Show documentation
/**
* 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.schedulers;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import rx.Scheduler;
import rx.Subscription;
import rx.functions.Action0;
import rx.plugins.RxJavaPlugins;
import rx.subscriptions.CompositeSubscription;
import rx.subscriptions.MultipleAssignmentSubscription;
import rx.subscriptions.Subscriptions;
/**
* Scheduler that wraps an Executor instance and establishes the Scheduler contract upon it.
*
* Note that thread-hopping is unavoidable with this kind of Scheduler as we don't know about the underlying
* threading behavior of the executor.
*/
/* public */final class ExecutorScheduler extends Scheduler {
final Executor executor;
public ExecutorScheduler(Executor executor) {
this.executor = executor;
}
/**
* @warn javadoc missing
* @return
*/
@Override
public Worker createWorker() {
return new ExecutorSchedulerWorker(executor);
}
/** Worker that schedules tasks on the executor indirectly through a trampoline mechanism. */
static final class ExecutorSchedulerWorker extends Scheduler.Worker implements Runnable {
final Executor executor;
// TODO: use a better performing structure for task tracking
final CompositeSubscription tasks;
// TODO: use MpscLinkedQueue once available
final ConcurrentLinkedQueue queue;
final AtomicInteger wip;
public ExecutorSchedulerWorker(Executor executor) {
this.executor = executor;
this.queue = new ConcurrentLinkedQueue();
this.wip = new AtomicInteger();
this.tasks = new CompositeSubscription();
}
@Override
public Subscription schedule(Action0 action) {
if (isUnsubscribed()) {
return Subscriptions.empty();
}
ExecutorAction ea = new ExecutorAction(action, tasks);
tasks.add(ea);
queue.offer(ea);
if (wip.getAndIncrement() == 0) {
try {
executor.execute(this);
} catch (RejectedExecutionException t) {
// cleanup if rejected
tasks.remove(ea);
wip.decrementAndGet();
// report the error to the plugin
RxJavaPlugins.getInstance().getErrorHandler().handleError(t);
// throw it to the caller
throw t;
}
}
return ea;
}
@Override
public void run() {
do {
queue.poll().run();
} while (wip.decrementAndGet() > 0);
}
@Override
public Subscription schedule(final Action0 action, long delayTime, TimeUnit unit) {
if (delayTime <= 0) {
return schedule(action);
}
if (isUnsubscribed()) {
return Subscriptions.empty();
}
ScheduledExecutorService service;
if (executor instanceof ScheduledExecutorService) {
service = (ScheduledExecutorService)executor;
} else {
service = GenericScheduledExecutorService.getInstance();
}
final MultipleAssignmentSubscription mas = new MultipleAssignmentSubscription();
// tasks.add(mas); // Needs a removal without unsubscription
try {
Future> f = service.schedule(new Runnable() {
@Override
public void run() {
if (mas.isUnsubscribed()) {
return;
}
mas.set(schedule(action));
// tasks.delete(mas); // Needs a removal without unsubscription
}
}, delayTime, unit);
mas.set(Subscriptions.from(f));
} catch (RejectedExecutionException t) {
// report the rejection to plugins
RxJavaPlugins.getInstance().getErrorHandler().handleError(t);
throw t;
}
return mas;
}
@Override
public boolean isUnsubscribed() {
return tasks.isUnsubscribed();
}
@Override
public void unsubscribe() {
tasks.unsubscribe();
}
}
/** Runs the actual action and maintains an unsubscription state. */
static final class ExecutorAction implements Runnable, Subscription {
final Action0 actual;
final CompositeSubscription parent;
volatile int unsubscribed;
static final AtomicIntegerFieldUpdater UNSUBSCRIBED_UPDATER
= AtomicIntegerFieldUpdater.newUpdater(ExecutorAction.class, "unsubscribed");
public ExecutorAction(Action0 actual, CompositeSubscription parent) {
this.actual = actual;
this.parent = parent;
}
@Override
public void run() {
if (isUnsubscribed()) {
return;
}
try {
actual.call();
} catch (Throwable t) {
RxJavaPlugins.getInstance().getErrorHandler().handleError(t);
} finally {
unsubscribe();
}
}
@Override
public boolean isUnsubscribed() {
return unsubscribed != 0;
}
@Override
public void unsubscribe() {
if (UNSUBSCRIBED_UPDATER.compareAndSet(this, 0, 1)) {
parent.remove(this);
}
}
}
}