/* * Copyright (c) 2011-2017 Pivotal Software Inc, All Rights Reserved. * * 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 reactor.core.scheduler; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import reactor.core.Disposable; import reactor.util.concurrent.OpenHashSet; /** * A simple {@link Scheduler} which uses a backing {@link ExecutorService} to schedule * Runnables for async operators. This scheduler is time-capable (can schedule with a * delay and/or periodically) if the backing executor is a {@link ScheduledExecutorService}. * * @author Stephane Maldini * @author Simon Baslé */ final class ExecutorServiceScheduler implements Scheduler { static final Runnable EMPTY = () -> { }; static final Future<?> CANCELLED = new FutureTask<>(EMPTY, null); static final Future<?> FINISHED = new FutureTask<>(EMPTY, null); final ExecutorService executor; final boolean interruptOnCancel; ExecutorServiceScheduler(ExecutorService executorService, boolean interruptOnCancel) { if (executorService instanceof ScheduledExecutorService) { this.executor = Schedulers.decorateScheduledExecutorService("ExecutorService", () -> (ScheduledExecutorService) executorService); } else { this.executor = Schedulers.decorateExecutorService("ExecutorService", () -> executorService); } this.interruptOnCancel = interruptOnCancel; } @Override public Worker createWorker() { return new ExecutorServiceWorker(executor, interruptOnCancel); } boolean isTimeCapable() { return executor instanceof ScheduledExecutorService; } @Override public Disposable schedule(Runnable task) { try { return new DisposableFuture(executor.submit(task), interruptOnCancel); } catch (RejectedExecutionException ree) { return REJECTED; } } @Override public Disposable schedule(Runnable task, long delay, TimeUnit unit) { if (!isTimeCapable()) { return REJECTED; } ScheduledExecutorService scheduledExecutor = (ScheduledExecutorService) executor; try { return new DisposableFuture(scheduledExecutor.schedule(task, delay, unit), interruptOnCancel); } catch (RejectedExecutionException ree) { return REJECTED; } } @Override public Disposable schedulePeriodically(Runnable task, long initialDelay, long period, TimeUnit unit) { if (!isTimeCapable()) { return REJECTED; } ScheduledExecutorService scheduledExecutor = (ScheduledExecutorService) executor; try { return new DisposableFuture(scheduledExecutor.scheduleAtFixedRate(task, initialDelay, period, unit), interruptOnCancel); } catch (RejectedExecutionException ree) { return REJECTED; } } @Override public boolean isDisposed() { return executor.isShutdown(); } @Override public void dispose() { Schedulers.executorServiceShutdown(executor, "ExecutorService"); } static final class DisposableFuture implements Disposable { final Future<?> f; final boolean interruptOnCancel; DisposableFuture(Future<?> f, boolean interruptOnCancel) { this.f = f; this.interruptOnCancel = interruptOnCancel; } @Override public void dispose() { f.cancel(interruptOnCancel); } @Override public boolean isDisposed() { return f.isCancelled() || f.isDone(); } } static final class ExecutorServiceWorker implements Worker, DisposableContainer<ExecutorServiceSchedulerRunnable> { final ExecutorService executor; final boolean interruptOnCancel; volatile boolean terminated; OpenHashSet<ExecutorServiceSchedulerRunnable> tasks; ExecutorServiceWorker(ExecutorService executor, boolean interruptOnCancel) { this.executor = executor; this.interruptOnCancel = interruptOnCancel; this.tasks = new OpenHashSet<>(); } boolean isTimeCapable() { return executor instanceof ScheduledExecutorService; } @Override public Disposable schedule(Runnable t) { ExecutorServiceSchedulerRunnable sr = new ExecutorServiceSchedulerRunnable(t, this); try { if (add(sr)) { Future<?> f = executor.submit(sr); sr.setFuture(f); return sr; } } catch (RejectedExecutionException ree) { removeAndDispose(sr); } return REJECTED; } @Override public Disposable schedule(Runnable t, long delay, TimeUnit unit) { if (!isTimeCapable()) { return REJECTED; } ScheduledExecutorService scheduledExecutor = (ScheduledExecutorService) executor; ExecutorServiceSchedulerRunnable sr = new ExecutorServiceSchedulerRunnable(t, this); try { if (add(sr)) { Future<?> f = scheduledExecutor.schedule(sr, delay, unit); sr.setFuture(f); return sr; } } catch (RejectedExecutionException ree) { removeAndDispose(sr); } return REJECTED; } @Override public Disposable schedulePeriodically(Runnable t, long initialDelay, long period, TimeUnit unit) { if (!isTimeCapable()) { return REJECTED; } ScheduledExecutorService scheduledExecutor = (ScheduledExecutorService) executor; ExecutorServiceSchedulerRunnable sr = new ExecutorServiceSchedulerRunnable(t, this); try { if (add(sr)) { Future<?> f = scheduledExecutor.scheduleAtFixedRate(sr, initialDelay, period, unit); sr.setFuture(f); return sr; } } catch (RejectedExecutionException ree) { removeAndDispose(sr); } return REJECTED; } @Override public boolean add(ExecutorServiceSchedulerRunnable sr) { if (!terminated) { synchronized (this) { if (!terminated) { tasks.add(sr); return true; } } } return false; } @Override public boolean remove(ExecutorServiceSchedulerRunnable sr) { if (!terminated) { synchronized (this) { if (!terminated) { tasks.remove(sr); return true; } } } return false; } @Override public void dispose() { if (!terminated) { OpenHashSet<ExecutorServiceSchedulerRunnable> coll; synchronized (this) { if (terminated) { return; } coll = tasks; tasks = null; terminated = true; } if (!coll.isEmpty()) { Object[] a = coll.keys(); for (Object o : a) { if (o != null) { ((ExecutorServiceSchedulerRunnable) o).dispose(); } } } } } @Override public boolean isDisposed() { return terminated; } } /** * A runnable task for {@link ExecutorServiceScheduler} Workers that exposes the * ability to cancel inner task when interrupted. * * @author Simon Baslé */ static final class ExecutorServiceSchedulerRunnable implements Runnable, Disposable { static final ExecutorServiceWorker DISPOSED_PARENT = new ExecutorServiceWorker(null, false); static final ExecutorServiceWorker DONE_PARENT = new ExecutorServiceWorker(null, false); final Runnable task; volatile Future<?> future; static final AtomicReferenceFieldUpdater<ExecutorServiceSchedulerRunnable, Future> FUTURE = AtomicReferenceFieldUpdater.newUpdater( ExecutorServiceSchedulerRunnable.class, Future.class, "future"); volatile ExecutorServiceWorker parent; static final AtomicReferenceFieldUpdater<ExecutorServiceSchedulerRunnable, ExecutorServiceWorker> PARENT = AtomicReferenceFieldUpdater.newUpdater( ExecutorServiceSchedulerRunnable.class, ExecutorServiceWorker.class, "parent"); ExecutorServiceSchedulerRunnable(Runnable task, ExecutorServiceWorker parent) { this.task = task; PARENT.lazySet(this, parent); } @Override public void run() { try { try { task.run(); } catch (Throwable ex) { Schedulers.handleError(ex); } } finally { ExecutorServiceWorker o = parent; if (o != DISPOSED_PARENT && o != null && PARENT.compareAndSet(this, o, DONE_PARENT)) { o.remove(this); } Future f; for (; ; ) { f = future; if (f == CANCELLED || FUTURE.compareAndSet(this, f, FINISHED)) { break; } } } } void setFuture(Future<?> f) { for (; ; ) { Future o = future; if (o == FINISHED) { return; } if (o == CANCELLED) { f.cancel(parent.interruptOnCancel); return; } if (FUTURE.compareAndSet(this, o, f)) { return; } } } @Override public boolean isDisposed() { Future<?> a = future; return FINISHED == a || CANCELLED == a; } @Override public void dispose() { for (; ; ) { Future f = future; if (f == FINISHED || f == CANCELLED) { break; } if (FUTURE.compareAndSet(this, f, CANCELLED)) { if (f != null) { f.cancel(parent.interruptOnCancel); } break; } } for (; ; ) { ExecutorServiceWorker o = parent; if (o == DONE_PARENT || o == DISPOSED_PARENT || o == null) { return; } if (PARENT.compareAndSet(this, o, DISPOSED_PARENT)) { o.remove(this); return; } } } } }