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

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

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

import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.jtrim2.cancel.CancellationToken;
import org.jtrim2.concurrent.TaskExecutionException;
import org.jtrim2.event.ListenerRef;

/**
 * Defines a {@code TaskExecutor} where client code can determine where and when
 * submitted tasks are executed. This task is generally useful for testing.
 * 

* You can submit tasks the usual way as defined by the {@code TaskExecutor} * interface and if you actually want to execute them, you have to call the * {@link #executeCurrentlySubmitted() executeCurrentlySubmitted} or the * {@link #tryExecuteOne() tryExecuteOne} methods. This executor has a FIFO * queue and the previously mentioned methods will execute them in the order * they were submitted, unless you call them concurrently (in this case no * ordering guarantee can be made). *

* Warning: If you fail to execute submitted tasks, then completion handlers * will not be notified, so it is the responsibility of the client code to * execute them. * *

Thread safety

* The methods of this class are safe to use by multiple threads concurrently. * *

Synchronization transparency

* The methods of this class are not synchronization transparent. */ public final class ManualTaskExecutor extends AbstractTaskExecutor { private final Lock mainLock; private final List jobs; private final boolean eagerCancel; /** * Creates a new {@code ManualTaskExecutor} with an empty task queue. * * @param eagerCancel if this argument is {@code true} and a task is * canceled, then the task will be eagerly unreferenced (allowing it to be * garbage collected) and will not be attempted to be executed. In case * this argument is {@code false}, submitted tasks will always be executed * even if canceled (though they can check their cancellation token and * cancel themselves). */ public ManualTaskExecutor(boolean eagerCancel) { this.mainLock = new ReentrantLock(); this.jobs = new LinkedList<>(); this.eagerCancel = eagerCancel; } private TaskExecutorJob pollJob() { mainLock.lock(); try { return jobs.isEmpty() ? null : jobs.remove(0); } finally { mainLock.unlock(); } } /** * Removes a single task from the task queue of this executor and executes * it (along with its completion handler) unless the queue is empty. *

* Note: Exceptions thrown by the submitted task will not be rethrown but * can be detected via the associated {@code CompletionStage}. * * @return {@code true} if there was a task to be executed in the queue, * {@code false} if this method did nothing because there was no submitted * task */ public boolean tryExecuteOne() { TaskExecutorJob job = pollJob(); if (job != null) { job.execute(); return true; } else { return false; } } /** * Executes every currently submitted task found in the task queue. The * tasks are executed in FIFO order. This method will collect tasks from the * queue once, if a task is submitted after, it will not be executed. * In particular, new tasks submitted by currently executed tasks are * guaranteed not to be executed by this method. * *

{@code
     * ManualTaskExecutor executor = new ManualTaskExecutor(false);
     * executor.execute(Cancellation.UNCANCELABLE_TOKEN, (outerCancelToken) -> {
     *   System.out.println("OUTER-TASK");
     *   executor.execute(Cancellation.UNCANCELABLE_TOKEN, (innerCancelToken) -> {
     *     System.out.println("INNER-TASK");
     *   });
     * });
     * executor.executeCurrentlySubmitted();
     * }
* * The above code will only print "OUTER-TASK" and the * {@code executeCurrentlySubmitted} method will return 1. * * @return the number of tasks executed by this method. This includes * canceled tasks as well of which only the completion handler is executed. */ public int executeCurrentlySubmitted() { TaskExecutorJob[] currentJobs; mainLock.lock(); try { currentJobs = jobs.toArray(new TaskExecutorJob[jobs.size()]); jobs.clear(); } finally { mainLock.unlock(); } TaskExecutionException toThrow = null; for (TaskExecutorJob job : currentJobs) { try { job.execute(); } catch (Throwable ex) { if (toThrow == null) toThrow = new TaskExecutionException(ex); else toThrow.addSuppressed(ex); } } if (toThrow != null) { throw toThrow; } return currentJobs.length; } /** * {@inheritDoc } *

* Implementation note: * Submits the specified task for execution. The submitted task can be * executed by subsequent calls to {@code executeCurrentlySubmitted} * or {@code tryExecuteOne} methods. *

* This executor implementation ensures that the same * {@code CancellationToken} is passed to the task as passed in the * argument of the {@code execute} method. */ @Override protected void submitTask(CancellationToken cancelToken, SubmittedTask submittedTask) { TaskExecutorJob job = new TaskExecutorJob(cancelToken, submittedTask); if (eagerCancel) { ListenerRef cancelListenerRef = cancelToken.addCancellationListener(submittedTask::cancel); submittedTask.getFuture().whenComplete((result, error) -> { cancelListenerRef.unregister(); }); } mainLock.lock(); try { jobs.add(job); } finally { mainLock.unlock(); } } private static final class TaskExecutorJob { private final CancellationToken cancelToken; private final SubmittedTask submittedTask; public TaskExecutorJob(CancellationToken cancelToken, SubmittedTask submittedTask) { this.cancelToken = Objects.requireNonNull(cancelToken, "cancelToken"); this.submittedTask = Objects.requireNonNull(submittedTask, "submittedTask"); } public void execute() { submittedTask.execute(cancelToken); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy