/*
* Copyright 2016 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 io.reactivex.netty.threads;
import io.netty.channel.EventLoop;
import io.netty.channel.EventLoopGroup;
import io.netty.util.concurrent.Future;
import rx.Scheduler;
import rx.Subscription;
import rx.annotations.Beta;
import rx.functions.Action0;
import rx.internal.schedulers.EventLoopsScheduler;
import rx.internal.schedulers.ScheduledAction;
import rx.internal.util.SubscriptionList;
import rx.subscriptions.CompositeSubscription;
import rx.subscriptions.Subscriptions;
import java.util.concurrent.TimeUnit;
/**
* A scheduler that uses a provided {@link EventLoopGroup} instance to schedule tasks. This should typically be used as
* a computation scheduler or any other scheduler that do not schedule blocking tasks. <p>
*/
@Beta
public class RxJavaEventloopScheduler extends Scheduler {
private final EventLoopGroup eventLoopGroup;
public RxJavaEventloopScheduler(EventLoopGroup eventLoopGroup) {
this.eventLoopGroup = eventLoopGroup;
}
@Override
public Worker createWorker() {
final EventLoop eventLoop = eventLoopGroup.next();
return new EventloopWorker(eventLoop);
}
/**
* This code is more or less copied from RxJava's {@link EventLoopsScheduler} worker code.
**/
/*Visible for testing*/static class EventloopWorker extends Worker {
/**
* Why are there two subscription holders?
*
* The serial subscriptions are used for non-delayed schedules which are always executed (and hence removed)
* in order. Since SubscriptionList holds the subs as a linked list, removals are optimal for serial removes.
* OTOH, delayed schedules are executed (and hence removed) out of order and hence a CompositeSubscription,
* that stores the subs in a hash structure is more optimal for removals.
*/
private final SubscriptionList serial;
private final CompositeSubscription timed;
private final SubscriptionList both;
private final EventLoop eventLoop;
public EventloopWorker(EventLoop eventLoop) {
this.eventLoop = eventLoop;
serial = new SubscriptionList();
timed = new CompositeSubscription();
both = new SubscriptionList(serial, timed);
}
@Override
public Subscription schedule(final Action0 action) {
return schedule(action, 0, TimeUnit.DAYS);
}
@Override
public Subscription schedule(final Action0 action, long delayTime, TimeUnit unit) {
if (isUnsubscribed()) {
return Subscriptions.unsubscribed();
}
final ScheduledAction sa;
if (delayTime <= 0) {
sa = new ScheduledAction(action, serial);
serial.add(sa);
} else {
sa = new ScheduledAction(action, timed);
timed.add(sa);
}
final Future<?> result = eventLoop.schedule(sa, delayTime, unit);
Subscription cancelFuture = Subscriptions.create(new Action0() {
@Override
public void call() {
result.cancel(false);
}
});
sa.add(cancelFuture); /*An unsubscribe of the returned sub should cancel the future*/
return sa;
}
@Override
public void unsubscribe() {
both.unsubscribe();
}
@Override
public boolean isUnsubscribed() {
return both.isUnsubscribed();
}
public boolean hasScheduledSubscriptions() {
return serial.hasSubscriptions();
}
public boolean hasDelayScheduledSubscriptions() {
return timed.hasSubscriptions();
}
}
}