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

org.jtrim2.executor.SingleThreadedExecutor Maven / Gradle / Ivy

There is a newer version: 2.0.7
Show newest version
package org.jtrim2.executor;

import java.util.Objects;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jtrim2.cancel.CancelableWaits;
import org.jtrim2.cancel.Cancellation;
import org.jtrim2.cancel.CancellationSource;
import org.jtrim2.cancel.CancellationToken;
import org.jtrim2.cancel.OperationCanceledException;
import org.jtrim2.collections.RefLinkedList;
import org.jtrim2.collections.RefList;
import org.jtrim2.concurrent.WaitableSignal;
import org.jtrim2.event.ListenerRef;
import org.jtrim2.utils.ExceptionHelper;
import org.jtrim2.utils.ObjectFinalizer;
import org.jtrim2.utils.TimeDuration;

/**
 * Defines a {@code TaskExecutorService} executing submitted tasks on a single
 * background thread. Tasks are guaranteed to be executed in a FIFO order and
 * they are never executed concurrently, so this class might be conveniently
 * used for synchronization purposes.
 * 

* Note: Consider using {@link ThreadPoolBuilder} instead of directly creating * an instance of {@code ThreadPoolTaskExecutor}. * *

Executing new tasks

* Tasks can be submitted by one of the {@code execute} methods. *

* When a new task is submitted, {@code SingleThreadedExecutor} and the queue * of this executor is not full: The task is added to the queue. If the worker * thread of this executor has already been started, the worker will execute the * newly added task after it finishes executing previously submitted tasks. *

* If the queue for tasks is full, the {@code submit} or {@code execute} method * will block and wait until the task can be added to the queue. * *

Cancellation of tasks

* Canceling a task which was not yet started and is still in the queue will * immediately remove it from the queue and no references will be * retained to the task (allowing it to be garbage collected if not referenced * by external code). *

* Canceling a task will cause the {@link CancellationToken} passed to it, * signal cancellation request. In this case the task may decide if it is to be * canceled or not. If the task throws an {@link OperationCanceledException}, * task execution is considered to be canceled. Note that if the task throws * an {@code OperationCanceledException} it is always assumed to be canceled, * even if the {@code CancellationToken} does not signal a cancellation request. * *

Number of referenced tasks

* The maximum number of tasks referenced by a {@code SingleThreadedExecutor} * at any given time is the maximum size of its queue plus one. The * {@code SingleThreadedExecutor} will never reference tasks more than this. * Note however, that not yet returned {@code submit} or {@code execute} methods * always reference their task specified in their argument (obviously this is * unavoidable) and there is no limit on how many times the user can * concurrently call these methods. * *

Terminating {@code SingleThreadedExecutor}

* The {@code SingleThreadedExecutor} must always be shut down when no longer * needed, so that it may shutdown its worker thread. If the user fails to * shut down the {@code SingleThreadedExecutor} (either by calling * {@link #shutdown()} or {@link #shutdownAndCancel()}) and the garbage * collector notifies the {@code SingleThreadedExecutor} that it has become * unreachable (through finalizers), it will be logged as an error using the * logging facility of Java (in a {@code Level.SEVERE} log message). * *

Thread safety

* Methods of this class are safely accessible from multiple threads * concurrently. * *

Synchronization transparency

* Method of this class are not synchronization transparent unless * otherwise noted. * * @see ThreadPoolBuilder */ public final class SingleThreadedExecutor extends DelegatedTaskExecutorService implements MonitorableTaskExecutorService { private static final Logger LOGGER = Logger.getLogger(SingleThreadedExecutor.class.getName()); private static final long DEFAULT_THREAD_TIMEOUT_MS = 5000; private final ObjectFinalizer finalizer; private final Impl impl; /** * Creates a new {@code SingleThreadedExecutor} initialized with the * specified name. *

* The default maximum queue size is {@code Integer.MAX_VALUE} making it * effectively unbounded. *

* The default timeout value after idle threads stop is 5 seconds. *

* The newly created {@code SingleThreadedExecutor} will not have any thread * started. Threads will only be started when submitting tasks * (as required). *

* The default {@code ThreadFactory} will create non-daemon threads and * the name of the started threads will contain the name of this executor. *

* Note: Consider using {@link ThreadPoolBuilder} instead of directly creating * an instance of {@code ThreadPoolTaskExecutor}. * * @param poolName the name of this {@code SingleThreadedExecutor} for * logging and debugging purposes. Setting a descriptive name might help * when debugging or reading logs. This argument cannot be {@code null}. * * @throws IllegalArgumentException thrown if an illegal value was specified * for any of the {@code int} arguments * @throws NullPointerException thrown if the specified name for this * {@code SingleThreadedExecutor} is {@code null} * * @see #setThreadFactory(ThreadFactory) */ public SingleThreadedExecutor(String poolName) { this(poolName, Integer.MAX_VALUE); } /** * Creates a new {@code SingleThreadedExecutor} initialized with the given * properties. *

* The default timeout value after idle threads stop is 5 seconds. *

* The newly created {@code SingleThreadedExecutor} will not have any thread * started. Threads will only be started when submitting tasks * (as required). *

* The default {@code ThreadFactory} will create non-daemon threads and * the name of the started threads will contain the name of this executor. *

* Note: Consider using {@link ThreadPoolBuilder} instead of directly creating * an instance of {@code ThreadPoolTaskExecutor}. * * @param poolName the name of this {@code SingleThreadedExecutor} for * logging and debugging purposes. Setting a descriptive name might help * when debugging or reading logs. This argument cannot be {@code null}. * @param maxQueueSize the maximum size of the internal queue to store tasks * not yet executed due to the worker thread being busy executing tasks. * This argument must greater than or equal to 1. * * @throws IllegalArgumentException thrown if an illegal value was specified * for any of the {@code int} arguments * @throws NullPointerException thrown if the specified name for this * {@code SingleThreadedExecutor} is {@code null} * * @see #setThreadFactory(ThreadFactory) */ public SingleThreadedExecutor(String poolName, int maxQueueSize) { this(poolName, maxQueueSize, DEFAULT_THREAD_TIMEOUT_MS, TimeUnit.MILLISECONDS); } /** * Creates a new {@code SingleThreadedExecutor} initialized with the given * properties. *

* The newly created {@code SingleThreadedExecutor} will not have any thread * started. Threads will only be started when submitting tasks * (as required). *

* The default {@code ThreadFactory} will create non-daemon threads and * the name of the started threads will contain the name of this executor. *

* Note: Consider using {@link ThreadPoolBuilder} instead of directly creating * an instance of {@code ThreadPoolTaskExecutor}. * * @param poolName the name of this {@code SingleThreadedExecutor} for * logging and debugging purposes. Setting a descriptive name might help * when debugging or reading logs. This argument cannot be {@code null}. * @param maxQueueSize the maximum size of the internal queue to store tasks * not yet executed due to the worker thread being busy executing tasks. * This argument must greater than or equal to 1.. * @param idleTimeout the time in the given time unit after idle threads * should stop. That is if a thread goes idle (i.e.: there are no * submitted tasks), it will wait this amount of time before giving up * waiting for submitted tasks. The thread may be restarted if needed * later. It is recommended to use a reasonably low value for this * argument (but not too low), so even if this * {@code SingleThreadedExecutor} is not shut down (due to a bug), * threads will still terminate allowing the JVM to terminate as well (if * there are no more non-daemon threads). This argument must be greater * than or equal to zero. * @param timeUnit the time unit of the {@code idleTimeout} argument. This * argument cannot be {@code null}. * * @throws IllegalArgumentException thrown if an illegal value was specified * for any of the {@code int} arguments * @throws NullPointerException thrown if any of the arguments is * {@code null} * * @see #setThreadFactory(ThreadFactory) */ public SingleThreadedExecutor( String poolName, int maxQueueSize, long idleTimeout, TimeUnit timeUnit) { this(poolName, maxQueueSize, new TimeDuration(idleTimeout, timeUnit), new ExecutorsEx.NamedThreadFactory(false, poolName) ); } SingleThreadedExecutor( String poolName, int maxQueueSize, TimeDuration idleTimeout, ThreadFactory threadFactory) { this(new Impl(poolName, maxQueueSize, idleTimeout, threadFactory)); } private SingleThreadedExecutor(final Impl impl) { super(impl); this.impl = impl; this.finalizer = new ObjectFinalizer(impl::shutdown, impl.getPoolName() + " SingleThreadedExecutor shutdown"); } /** * Specifies that this {@code SingleThreadedExecutor} does not need to be * shut down. Calling this method prevents this executor to be shut * down automatically when there is no more reference to this executor, * which also prevents logging a message if this executor has not been shut * down. This method might be called if you do not plan to shutdown this * executor but instead want to rely on the threads of this executor to * automatically shutdown after a small timeout. */ public void dontNeedShutdown() { finalizer.markFinalized(); } /** * {@inheritDoc } */ @Override public void shutdown() { finalizer.markFinalized(); wrappedExecutor.shutdown(); } /** * {@inheritDoc } */ @Override public void shutdownAndCancel() { finalizer.markFinalized(); wrappedExecutor.shutdownAndCancel(); } /** * Sets the {@code ThreadFactory} which is used to create worker threads * for this executor. Already started worker threads are not affected by * this method call but workers created after this method call will use the * currently set {@code ThreadFactory}. *

* It is recommended to call this method before submitting any task to this * executor. Doing so guarantees that all worker threads of this executor * will be created by the specified {@code ThreadFactory}. * * @param threadFactory the {@code ThreadFactory} which is used to create * worker threads for this executor. This argument cannot be {@code null}. * * @throws NullPointerException thrown if the specified thread factory is * {@code null} */ public void setThreadFactory(ThreadFactory threadFactory) { impl.setThreadFactory(threadFactory); } /** * Sets the maximum number of tasks allowed to be stored in the internal * queue. *

* Setting this property higher than it was set previously will have an * immediate effect and currently blocking {@code submit} and * {@code execute} will recheck if they can add the submitted task to the * queue. Setting this property lower, however, will not remove tasks from * the queue but will prevent more tasks to be added to the queue before * the number of tasks in the queue drops below this limit. * * @param maxQueueSize the maximum number of tasks allowed to be stored in * the internal queue. This argument must be greater than or equal to 1. * * @throws IllegalArgumentException if the specified {@code maxQueueSize} * is less than 1 */ public void setMaxQueueSize(int maxQueueSize) { impl.setMaxQueueSize(maxQueueSize); } /** * Returns the currently set maximum size for the queue of tasks scheduled * to be executed. *

* The return value of this method is for information purpose only. Due to * concurrent sets and already queued tasks, there is no guarantee that * the return value is truly being honored at the moment. See * {@link #setMaxQueueSize(int) setMaxQueueSize} for details on how this * property works. * * @return the currently set maximum size for the queue of tasks scheduled * to be executed. This value is always greater than or equal to one. */ public int getMaxQueueSize() { return impl.maxQueueSize; } /** * Sets the timeout value after idle threads should terminate. That is, * threads will terminate if they waited for at least this much time and * there was no submitted task for them to execute. *

* Setting this property has an immediate effect. * * @param idleTimeout the timeout value in the given time unit after idle * threads should terminate. This argument must be greater than or equal * to zero. * @param timeUnit the time unit of the {@code idleTimeout} argument. * This argument cannot be {@code null}. * * @throws IllegalArgumentException thrown if the specified timeout value is * less than zero * @throws NullPointerException thrown if the specified time unit argument * is {@code null} */ public void setIdleTimeout(long idleTimeout, TimeUnit timeUnit) { impl.setIdleTimeout(idleTimeout, timeUnit); } /** * Returns the currently set timeout value after idle threads should stop. *

* The return value of this method is for information purpose only. Due to * concurrent sets, there is no guarantee that the return value is truly * being honored at the moment. See {@link #setIdleTimeout(long, TimeUnit) setIdleTimeout} * for details on how this property works. * * @param timeUnit the time unit in which the result is request. This * argument cannot be {@code null}. * @return the currently set timeout value after idle threads should stop. * The return value might not be exactly what was set by the previous * invocation to {@code setIdleTimeout} due to rounding errors. This * method always returns a values greater than or equal to zero. * * @throws NullPointerException thrown if the specified time unit is * {@code null} */ public long getIdleTimeout(TimeUnit timeUnit) { return timeUnit.convert(impl.idleTimeoutNanos, TimeUnit.NANOSECONDS); } /** * Returns the name of this {@code SingleThreadedExecutor} as specified at * construction time. * * @return the name of this {@code SingleThreadedExecutor} as specified at * construction time. This method never returns {@code null}. */ public String getPoolName() { return impl.getPoolName(); } /** * {@inheritDoc } */ @Override public long getNumberOfQueuedTasks() { return impl.getNumberOfQueuedTasks(); } /** * {@inheritDoc } *

* Implementation note: This method may only return zero or one. */ @Override public long getNumberOfExecutingTasks() { return impl.getNumberOfExecutingTasks(); } /** * {@inheritDoc } */ @Override public boolean isExecutingInThis() { return impl.isExecutingInThis(); } /** * Returns the string representation of this executor in no particular * format. *

* This method is intended to be used for debugging only. * * @return the string representation of this object in no particular format. * This method never returns {@code null}. */ @Override public String toString() { return impl.toString(); } void setFullQueueHandler(FullQueueHandler fullQueueHandler) { impl.fullQueueHandler = fullQueueHandler; } FullQueueHandler getFullQueueHandler() { return impl.fullQueueHandler; } ThreadFactory getThreadFactory() { return impl.threadFactory; } boolean isFinalized() { return finalizer.isFinalized(); } private static final class Impl extends AbstractTerminateNotifierTaskExecutorService implements MonitorableTaskExecutor { private static final ThreadLocal OWNER_EXECUTOR = new ThreadLocal<>(); private final AtomicReference currentWorker; private final ReentrantLock mainLock; private final RefList taskQueue; private final String poolName; private volatile int maxQueueSize; private volatile long idleTimeoutNanos; private final CancellationSource globalCancel; private final WaitableSignal terminateSignal; private FullQueueHandler fullQueueHandler; private volatile ThreadFactory threadFactory; private volatile ExecutorState state; private volatile boolean active; private final Condition checkQueueSignal; private final Condition checkAddToQueueSignal; public Impl( String poolName, int maxQueueSize, TimeDuration idleTimeout, ThreadFactory threadFactory) { Objects.requireNonNull(poolName, "poolName"); ExceptionHelper.checkArgumentInRange(maxQueueSize, 1, Integer.MAX_VALUE, "maxQueueSize"); this.state = ExecutorState.RUNNING; this.maxQueueSize = maxQueueSize; this.poolName = poolName; this.idleTimeoutNanos = ExceptionHelper.checkArgumentInRange( idleTimeout.toNanos(), 0, Long.MAX_VALUE, "idleTimeout" ); this.mainLock = new ReentrantLock(); this.checkQueueSignal = mainLock.newCondition(); this.checkAddToQueueSignal = mainLock.newCondition(); this.taskQueue = new RefLinkedList<>(); this.globalCancel = Cancellation.createCancellationSource(); this.currentWorker = new AtomicReference<>(null); this.active = false; this.terminateSignal = new WaitableSignal(); this.threadFactory = Objects.requireNonNull(threadFactory, "threadFactory"); } private Thread createWorkerThread(Runnable task) { return threadFactory.newThread(task); } private Thread createOwnedWorkerThread(final Runnable task) { assert task != null; return createWorkerThread(() -> { try { OWNER_EXECUTOR.set(Impl.this); task.run(); } finally { OWNER_EXECUTOR.remove(); } }); } public String getPoolName() { return poolName; } public void setThreadFactory(ThreadFactory threadFactory) { Objects.requireNonNull(threadFactory, "threadFactory"); this.threadFactory = threadFactory; } public void setMaxQueueSize(int maxQueueSize) { ExceptionHelper.checkArgumentInRange(maxQueueSize, 1, Integer.MAX_VALUE, "maxQueueSize"); this.maxQueueSize = maxQueueSize; mainLock.lock(); try { // Actually it might be full but awaking the waiting thread // cause only a performance loss. This performance loss is of // little consequence because we don't expect this method to be // called that much. checkAddToQueueSignal.signalAll(); } finally { mainLock.unlock(); } } public void setIdleTimeout(long idleTimeout, TimeUnit timeUnit) { ExceptionHelper.checkArgumentInRange(idleTimeout, 0, Long.MAX_VALUE, "idleTimeout"); this.idleTimeoutNanos = timeUnit.toNanos(idleTimeout); mainLock.lock(); try { checkQueueSignal.signalAll(); } finally { mainLock.unlock(); } } private RefList.ElementRef tryAddToQueue(CancellationToken cancelToken, QueuedItem queuedTask) { FullQueueHandler currentFullQueueHandler = fullQueueHandler; mainLock.lock(); try { while (true) { if (isShutdown()) { return null; } if (taskQueue.size() < maxQueueSize) { RefList.ElementRef result = taskQueue.addLastGetReference(queuedTask); checkQueueSignal.signalAll(); return result; } if (currentFullQueueHandler != null) { ThreadPoolTaskExecutor.handleFullQueue(mainLock, currentFullQueueHandler, cancelToken); currentFullQueueHandler = null; continue; } CancelableWaits.await(cancelToken, checkAddToQueueSignal); } } finally { mainLock.unlock(); } } private void setRemoveFromQueueOnCancel( final QueuedItem task, final RefList.ElementRef queueRef) { task.onCancel(() -> { boolean removed; mainLock.lock(); try { removed = queueRef.isRemoved(); if (!removed) { queueRef.remove(); checkAddToQueueSignal.signal(); } } finally { mainLock.unlock(); } if (!removed) { try { task.submittedTask.cancel(); } finally { tryTerminateNowAndNotify(); } } }); } @Override protected void submitTask(CancellationToken cancelToken, SubmittedTask submittedTask) { CancellationToken combinedToken = Cancellation.anyToken(globalCancel.getToken(), cancelToken); QueuedItem queuedTask = new QueuedItem(combinedToken, submittedTask); RefList.ElementRef queueRef; try { queueRef = tryAddToQueue(combinedToken, queuedTask); if (queueRef == null) { submittedTask.cancel(); return; } } catch (OperationCanceledException ex) { submittedTask.completeExceptionally(ex); return; } setRemoveFromQueueOnCancel(queuedTask, queueRef); startNewWorkerIfNeeded(); } private void startNewWorkerIfNeeded() { if (currentWorker.get() == null) { // Obviously, we cannot guarantee that won't two worker // start but the very worst case is that the started worker // will recognize this and stop immediately. Worker worker = new Worker(); worker.tryStart(); } } private boolean tryTerminateNow() { assert mainLock.isHeldByCurrentThread(); if (state == ExecutorState.SHUTTING_DOWN && taskQueue.isEmpty()) { if (currentWorker.get() == null) { // 1. Subsequent tasks added to the queue will recognize that // the executor was shut down and will not be executed. // 2. There is no worker thread, which implies that there is // no task about to be executed (as only the worker // thread removes tasks from the queue). // // Therefore: It is guaranteed that from now on, no task // will be executed. state = ExecutorState.TERMINATED; terminateSignal.signal(); return true; } } return false; } private void initiateTerminate(boolean alreadyTerminated) { assert !mainLock.isHeldByCurrentThread(); if (alreadyTerminated) { notifyTerminateListeners(); } else { // This is a rare case, if there is no worker, we could // retry terminating now but the caller already did so. // Instead of retrying we start a worker which is bound // to do the work for us. startNewWorkerIfNeeded(); } } private void tryTerminateNowAndNotify() { if (state != ExecutorState.SHUTTING_DOWN) { return; } boolean terminatedNow; mainLock.lock(); try { terminatedNow = tryTerminateNow(); } finally { mainLock.unlock(); } initiateTerminate(terminatedNow); } @Override public void shutdown() { boolean terminatedNow; mainLock.lock(); try { if (state != ExecutorState.RUNNING) { return; } state = ExecutorState.SHUTTING_DOWN; checkQueueSignal.signalAll(); checkAddToQueueSignal.signalAll(); terminatedNow = tryTerminateNow(); } finally { mainLock.unlock(); } initiateTerminate(terminatedNow); } @Override public void shutdownAndCancel() { shutdown(); globalCancel.getController().cancel(); } @Override public boolean isShutdown() { return state.getStateIndex() >= ExecutorState.SHUTTING_DOWN.getStateIndex(); } @Override public boolean isTerminated() { return state == ExecutorState.TERMINATED; } @Override public boolean tryAwaitTermination(CancellationToken cancelToken, long timeout, TimeUnit unit) { return terminateSignal.tryWaitSignal(cancelToken, timeout, unit); } @Override public long getNumberOfQueuedTasks() { mainLock.lock(); try { return taskQueue.size(); } finally { mainLock.unlock(); } } @Override public long getNumberOfExecutingTasks() { return active ? 1 : 0; } @Override public boolean isExecutingInThis() { Impl owner = OWNER_EXECUTOR.get(); if (owner == null) { OWNER_EXECUTOR.remove(); return false; } return owner == this; } @Override public String toString() { int currentQueueSize; mainLock.lock(); try { currentQueueSize = taskQueue.size(); } finally { mainLock.unlock(); } return "SingleThreadedExecutor{" + "poolName=" + poolName + ", state=" + state + ", maxQueueSize=" + maxQueueSize + ", idleTimeout=" + TimeUnit.NANOSECONDS.toMillis(idleTimeoutNanos) + " ms" + ", queue=" + currentQueueSize + '}'; } private final class Worker implements Runnable { private final AtomicBoolean runCalled; private Thread ownerThread; public Worker() { this.runCalled = new AtomicBoolean(false); this.ownerThread = null; } // This method may only be called once. public void tryStart() { if (currentWorker.compareAndSet(null, this)) { Thread workerThread; try { workerThread = createOwnedWorkerThread(this); Objects.requireNonNull(workerThread, "workerThread"); } catch (Throwable ex) { // Let's hope next time the factory does not fail to // spawn a new thread, so we have chance to recover from // this failure. // Notice that if the thread factory started our worker, // the worker will stop because it will not recognize // "ownerThread". currentWorker.set(null); throw ex; } ownerThread = workerThread; workerThread.start(); } } private boolean isQueueEmpty() { mainLock.lock(); try { return taskQueue.isEmpty(); } finally { mainLock.unlock(); } } private QueuedItem pollFromQueue() { long startTime = System.nanoTime(); long usedIdleTimeoutNanos = idleTimeoutNanos; long toWaitNanos = usedIdleTimeoutNanos; mainLock.lock(); try { do { if (!taskQueue.isEmpty()) { QueuedItem result = taskQueue.remove(0); checkAddToQueueSignal.signal(); return result; } // If we have been shutted down and there is no task // in the queue, then we are done. if (isShutdown()) { return null; } try { toWaitNanos = checkQueueSignal.awaitNanos(toWaitNanos); if (usedIdleTimeoutNanos != idleTimeoutNanos) { long inc; inc = idleTimeoutNanos - usedIdleTimeoutNanos; long prevToWaitNanos = toWaitNanos; toWaitNanos += inc; // Check for overflow in the above addition if (inc > 0) { if (toWaitNanos < 0) { toWaitNanos = Long.MAX_VALUE; } } else { if (prevToWaitNanos < toWaitNanos) { toWaitNanos = 0; } } } } catch (InterruptedException ex) { // In this thread, we don't care about interrupts but // we need to recalculate the allowed waiting time. toWaitNanos = idleTimeoutNanos - (System.nanoTime() - startTime); } } while (toWaitNanos > 0); } finally { mainLock.unlock(); } return null; } private void executeTask(QueuedItem queuedItem) throws Exception { try { active = true; queuedItem.runTask(); } finally { active = false; } } private void processQueue() throws Exception { assert isExecutingInThis(); QueuedItem queuedItem = pollFromQueue(); while (queuedItem != null) { executeTask(queuedItem); queuedItem = pollFromQueue(); } } @Override public void run() { // Prevent abuse when calling from a ThreadFactory. if (Thread.currentThread() != ownerThread) { LOGGER.log(Level.SEVERE, "The worker of {0} has been called from the wrong thread.", poolName); throw new IllegalStateException(); } // This may happen if the thread factory calls this task // multiple times from the started thread. if (!runCalled.compareAndSet(false, true)) { LOGGER.log(Level.SEVERE, "The worker of {0} has been called multiple times.", poolName); throw new IllegalStateException(); } if (currentWorker.get() != this) { // This path is close to impossible to reliably test but is // possible anyway. LOGGER.log(Level.SEVERE, "The thread factory started the worker thread of {0} manually.", poolName); throw new IllegalStateException(); } try { processQueue(); } catch (Throwable ex) { LOGGER.log(Level.SEVERE, "Unexpected error in the worker of " + poolName, ex); } try { exitWorker(); } finally { tryTerminateNowAndNotify(); } } private void exitWorker() { currentWorker.set(null); if (!isQueueEmpty()) { new Worker().tryStart(); } } } } private enum ExecutorState { RUNNING(0), SHUTTING_DOWN(1), TERMINATED(2); private final int stateIndex; private ExecutorState(int stateIndex) { this.stateIndex = stateIndex; } public int getStateIndex() { return stateIndex; } } private static final class QueuedItem { public final CancellationToken cancelToken; public final AbstractTaskExecutor.SubmittedTask submittedTask; public QueuedItem( CancellationToken cancelToken, AbstractTaskExecutor.SubmittedTask submittedTask) { this.cancelToken = cancelToken; this.submittedTask = submittedTask; } private void runTask() { Thread.interrupted(); submittedTask.execute(cancelToken); } public void onCancel(Runnable cancelTask) { ListenerRef listenerRef = cancelToken.addCancellationListener(cancelTask); submittedTask.getFuture().whenComplete((result, error) -> listenerRef.unregister()); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy