All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.diffplug.common.util.concurrent.SerializingExecutor Maven / Gradle / Ivy

There is a newer version: 1.2.0
Show newest version
/*
 * Original Guava code is copyright (C) 2015 The Guava Authors.
 * Modifications from Guava are copyright (C) 2016 DiffPlug.
 *
 * 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.diffplug.common.util.concurrent;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.concurrent.Executor;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.annotation.concurrent.GuardedBy;

import com.diffplug.common.base.Preconditions;

/**
 * Executor ensuring that all Runnables submitted are executed in order,
 * using the provided Executor, and serially such that no two will ever
 * be running at the same time.
 *
 * 

Tasks submitted to {@link #execute(Runnable)} are executed in FIFO order. * *

Tasks can also be prepended to the queue to be executed in LIFO order before any other * submitted tasks. Primarily intended for the currently executing task to be able to schedule a * continuation task. * *

Execution on the queue can be {@linkplain #suspend suspended}, e.g. while waiting for an RPC, * and execution can be {@linkplain #resume resumed} later. * *

The execution of tasks is done by one thread as long as there are tasks left in the queue and * execution has not been suspended. (Even if one task is {@linkplain Thread#interrupt interrupted}, * execution of subsequent tasks continues.) {@code RuntimeException}s thrown by tasks are simply * logged and the executor keeps trucking. If an {@code Error} is thrown, the error will propagate * and execution will stop until it is restarted by external calls. */ final class SerializingExecutor implements Executor { private static final Logger log = Logger.getLogger(SerializingExecutor.class.getName()); /** Underlying executor that all submitted Runnable objects are run on. */ private final Executor executor; @GuardedBy("internalLock") private final Deque queue = new ArrayDeque(); @GuardedBy("internalLock") private boolean isWorkerRunning = false; @GuardedBy("internalLock") private int suspensions = 0; private final Object internalLock = new Object(); public SerializingExecutor(Executor executor) { this.executor = Preconditions.checkNotNull(executor); } /** * Adds a task to the queue and makes sure a worker thread is running, unless the queue has been * suspended. * *

If this method throws, e.g. a {@code RejectedExecutionException} from the delegate executor, * execution of tasks will stop until a call to this method or to {@link #resume()} is * made. */ public void execute(Runnable task) { synchronized (internalLock) { queue.add(task); } startQueueWorker(); } /** * Prepends a task to the front of the queue and makes sure a worker thread is running, unless the * queue has been suspended. */ public void executeFirst(Runnable task) { synchronized (internalLock) { queue.addFirst(task); } startQueueWorker(); } /** * Suspends the running of tasks until {@link #resume()} is called. This can be called multiple * times to increase the suspensions count and execution will not continue until {@link #resume} * has been called the same number of times as {@code suspend} has been. * *

Any task that has already been pulled off the queue for execution will be completed * before execution is suspended. */ public void suspend() { synchronized (internalLock) { suspensions++; } } /** * Continue execution of tasks after a call to {@link #suspend()}. More accurately, decreases the * suspension counter, as has been incremented by calls to {@link #suspend}, and resumes execution * if the suspension counter is zero. * *

If this method throws, e.g. a {@code RejectedExecutionException} from the delegate executor, * execution of tasks will stop until a call to this method or to {@link #execute(Runnable)} or * {@link #executeFirst(Runnable)} is made. * * @throws java.lang.IllegalStateException if this executor is not suspended. */ public void resume() { synchronized (internalLock) { Preconditions.checkState(suspensions > 0); suspensions--; } startQueueWorker(); } private void startQueueWorker() { synchronized (internalLock) { // We sometimes try to start a queue worker without knowing if there is any work to do. if (queue.peek() == null) { return; } if (suspensions > 0) { return; } if (isWorkerRunning) { return; } isWorkerRunning = true; } boolean executionRejected = true; try { executor.execute(new QueueWorker()); executionRejected = false; } finally { if (executionRejected) { // The best we can do is to stop executing the queue, but reset the state so that // execution can be resumed later if the caller so wishes. synchronized (internalLock) { isWorkerRunning = false; } } } } /** * Worker that runs tasks off the queue until it is empty or the queue is suspended. */ private final class QueueWorker implements Runnable { @Override public void run() { try { workOnQueue(); } catch (Error e) { synchronized (internalLock) { isWorkerRunning = false; } throw e; // The execution of a task has ended abnormally. // We could have tasks left in the queue, so should perhaps try to restart a worker, // but then the Error will get delayed if we are using a direct (same thread) executor. } } private void workOnQueue() { while (true) { Runnable task = null; synchronized (internalLock) { // TODO(user): How should we handle interrupts and shutdowns? if (suspensions == 0) { task = queue.poll(); } if (task == null) { isWorkerRunning = false; return; } } try { task.run(); } catch (RuntimeException e) { log.log(Level.SEVERE, "Exception while executing runnable " + task, e); } } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy