/**
* Copyright 2013 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 com.netflix.hystrix.strategy.concurrency;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import rx.Scheduler;
import rx.Subscription;
import rx.functions.Action0;
import rx.subscriptions.BooleanSubscription;
import rx.subscriptions.CompositeSubscription;
import rx.subscriptions.Subscriptions;
import com.netflix.hystrix.HystrixThreadPool;
import com.netflix.hystrix.strategy.HystrixPlugins;
/**
* Wrap a {@link Scheduler} so that scheduled actions are wrapped with {@link HystrixContexSchedulerAction} so that
* the {@link HystrixRequestContext} is properly copied across threads (if they are used by the {@link Scheduler}).
*/
public class HystrixContextScheduler extends Scheduler {
private final HystrixConcurrencyStrategy concurrencyStrategy;
private final Scheduler actualScheduler;
private final HystrixThreadPool threadPool;
public HystrixContextScheduler(Scheduler scheduler) {
this.actualScheduler = scheduler;
this.concurrencyStrategy = HystrixPlugins.getInstance().getConcurrencyStrategy();
this.threadPool = null;
}
public HystrixContextScheduler(HystrixConcurrencyStrategy concurrencyStrategy, Scheduler scheduler) {
this.actualScheduler = scheduler;
this.concurrencyStrategy = concurrencyStrategy;
this.threadPool = null;
}
public HystrixContextScheduler(HystrixConcurrencyStrategy concurrencyStrategy, HystrixThreadPool threadPool) {
this.concurrencyStrategy = concurrencyStrategy;
this.threadPool = threadPool;
this.actualScheduler = new ThreadPoolScheduler(threadPool);
}
@Override
public Worker createWorker() {
return new HystrixContextSchedulerWorker(actualScheduler.createWorker());
}
private class HystrixContextSchedulerWorker extends Worker {
private BooleanSubscription s = new BooleanSubscription();
private final Worker worker;
private HystrixContextSchedulerWorker(Worker actualWorker) {
this.worker = actualWorker;
}
@Override
public void unsubscribe() {
s.unsubscribe();
}
@Override
public boolean isUnsubscribed() {
return s.isUnsubscribed();
}
@Override
public Subscription schedule(Action0 action, long delayTime, TimeUnit unit) {
if (threadPool != null) {
if (!threadPool.isQueueSpaceAvailable()) {
throw new RejectedExecutionException("Rejected command because thread-pool queueSize is at rejection threshold.");
}
}
return worker.schedule(new HystrixContexSchedulerAction(concurrencyStrategy, action), delayTime, unit);
}
@Override
public Subscription schedule(Action0 action) {
if (threadPool != null) {
if (!threadPool.isQueueSpaceAvailable()) {
throw new RejectedExecutionException("Rejected command because thread-pool queueSize is at rejection threshold.");
}
}
return worker.schedule(new HystrixContexSchedulerAction(concurrencyStrategy, action));
}
}
private static class ThreadPoolScheduler extends Scheduler {
private final HystrixThreadPool threadPool;
public ThreadPoolScheduler(HystrixThreadPool threadPool) {
this.threadPool = threadPool;
}
@Override
public Worker createWorker() {
return new ThreadPoolWorker(threadPool);
}
}
/**
* Purely for scheduling work on a thread-pool.
* <p>
* This is not natively supported by RxJava as of 0.18.0 because thread-pools
* are contrary to sequential execution.
* <p>
* For the Hystrix case, each Command invocation has a single action so the concurrency
* issue is not a problem.
*/
private static class ThreadPoolWorker extends Worker {
private final HystrixThreadPool threadPool;
private final CompositeSubscription subscription = new CompositeSubscription();
public ThreadPoolWorker(HystrixThreadPool threadPool) {
this.threadPool = threadPool;
}
@Override
public void unsubscribe() {
subscription.unsubscribe();
}
@Override
public boolean isUnsubscribed() {
return subscription.isUnsubscribed();
}
@Override
public Subscription schedule(final Action0 action) {
if (subscription.isUnsubscribed()) {
// don't schedule, we are unsubscribed
return Subscriptions.empty();
}
final AtomicReference<Subscription> sf = new AtomicReference<Subscription>();
Subscription s = Subscriptions.from(threadPool.getExecutor().submit(new Runnable() {
@Override
public void run() {
try {
if (subscription.isUnsubscribed()) {
return;
}
action.call();
} finally {
// remove the subscription now that we're completed
Subscription s = sf.get();
if (s != null) {
subscription.remove(s);
}
}
}
}));
sf.set(s);
subscription.add(s);
return s;
}
@Override
public Subscription schedule(Action0 action, long delayTime, TimeUnit unit) {
System.out.println("delayed scheduling");
throw new IllegalStateException("Hystrix does not support delayed scheduling");
}
}
}