/* * Copyright 2016 The Simple File Server Authors * * 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 org.sfs.rx; import io.vertx.core.AsyncResult; import io.vertx.core.Context; import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.json.JsonObject; import rx.Scheduler; import rx.Subscription; import rx.functions.Action0; import rx.plugins.RxJavaHooks; import rx.plugins.RxJavaPlugins; import rx.plugins.RxJavaSchedulersHook; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; public class ContextScheduler extends Scheduler { private static final Handler<AsyncResult<Object>> NOOP = result -> { }; private final Vertx vertx; private final boolean blocking; private final boolean ordered; private final Context context; public ContextScheduler(Context context, boolean blocking) { this(context, blocking, true); } public ContextScheduler(Context context, boolean blocking, boolean ordered) { this.vertx = context.owner(); this.context = context; this.blocking = blocking; this.ordered = ordered; } public ContextScheduler(Vertx vertx, boolean blocking) { this(vertx, blocking, true); } public ContextScheduler(Vertx vertx, boolean blocking, boolean ordered) { this.vertx = vertx; this.context = null; this.blocking = blocking; this.ordered = ordered; } @Override public Worker createWorker() { return new WorkerImpl(); } private static final Object DUMB = new JsonObject(); private class WorkerImpl extends Worker { private final ConcurrentHashMap<TimedAction, Object> actions = new ConcurrentHashMap<>(); private final AtomicBoolean cancelled = new AtomicBoolean(); @Override public Subscription schedule(Action0 action) { return schedule(action, 0, TimeUnit.MILLISECONDS); } @Override public Subscription schedule(Action0 action, long delayTime, TimeUnit unit) { action = RxJavaHooks.onScheduledAction(action); TimedAction timed = new TimedAction(action, unit.toMillis(delayTime), 0); actions.put(timed, DUMB); return timed; } @Override public Subscription schedulePeriodically(Action0 action, long initialDelay, long period, TimeUnit unit) { action = RxJavaHooks.onScheduledAction(action); TimedAction timed = new TimedAction(action, unit.toMillis(initialDelay), unit.toMillis(period)); actions.put(timed, DUMB); return timed; } @Override public void unsubscribe() { if (cancelled.compareAndSet(false, true)) { actions.keySet().forEach(TimedAction::unsubscribe); } } @Override public boolean isUnsubscribed() { return cancelled.get(); } class TimedAction implements Subscription, Runnable { private final Context context; private long id; private final Action0 action; private final long periodMillis; private boolean cancelled; public TimedAction(Action0 action, long delayMillis, long periodMillis) { this.context = ContextScheduler.this.context != null ? ContextScheduler.this.context : vertx.getOrCreateContext(); this.cancelled = false; this.action = action; this.periodMillis = periodMillis; if (delayMillis > 0) { schedule(delayMillis); } else { id = -1; execute(null); } } private void schedule(long delay) { this.id = vertx.setTimer(delay, this::execute); } private void execute(Object o) { if (blocking) { context.executeBlocking(this::run, ordered, NOOP); } else { context.runOnContext(this::run); } } private void run(Object arg) { run(); } @Override public void run() { synchronized (TimedAction.this) { if (cancelled) { return; } } action.call(); synchronized (TimedAction.this) { if (periodMillis > 0) { schedule(periodMillis); } } } @Override public synchronized void unsubscribe() { if (!cancelled) { actions.remove(this); if (id > 0) { vertx.cancelTimer(id); } cancelled = true; } } @Override public synchronized boolean isUnsubscribed() { return cancelled; } } } }