net.freeutils.util.Spool Maven / Gradle / Ivy
Show all versions of jelementary Show documentation
/*
* Copyright © 2003-2024 Amichai Rothman
*
* This file is part of JElementary - the Java Elementary Utilities package.
*
* JElementary is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* JElementary is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with JElementary. If not, see .
*
* For additional info see https://www.freeutils.net/source/jelementary/
*/
package net.freeutils.util;
import java.util.Collection;
import java.util.concurrent.*;
import net.freeutils.util.Containers.Mapper;
/**
* The {@code Spool} class is a general-purpose multithreaded processing
* spool. It provides convenience and functionality beyond that which is
* given by the underlying {@link ExecutorService}, as well as an abstract
* queue backend which allows implementations to use a simple runtime queue,
* a persistent queue, a remote queue, etc.
*
* @param the spooled item type
*/
public abstract class Spool implements Runnable {
/**
* The {@code Item} inner class holds an item for use with an executor.
* It implements the {@code Runnable} interface by calling the spool's
* {@link Spool#process process} method.
*/
protected class Item implements Runnable {
final T t;
public Item(T t) {
this.t = t;
}
@Override
public void run() {
try {
process(t);
processComplete(t, null);
} catch (Throwable th) {
processComplete(t, th);
}
}
}
/**
* A {@link Mapper} which returns an Item's underlying item.
*/
protected final Mapper TASK_MAPPER = new Mapper() {
@Override
@SuppressWarnings("unchecked")
public T map(Runnable item) {
return ((Item)item).t;
}
};
/**
* The {@code ExecutorService} used for processing spooled items.
*/
protected final ExecutorService exec;
/**
* Constructs a Spool with a default cached thread pool ExecutorService.
*/
public Spool() {
this(Executors.newCachedThreadPool());
}
/**
* Constructs a Spool with the given ExecutorService.
* The ExecutorService must support at least two threads,
* one for internal spool queue management, and one for
* processing the spooled items.
*
* @param exec an executor service
*/
public Spool(ExecutorService exec) {
this.exec = exec;
}
/**
* Initializes the spool and starts its processing.
*
* Subclasses may override this method to initialize the spool
* backend, such as filling the spool queue from persistent
* storage, but must still call {@code super.init()} when ready
* for processing.
*/
public void init() {
exec.execute(this);
}
/**
* Shuts down the spool and stops its processing.
*
* An attempt is made to finish processing current items, up to the given
* timeout. If not all items have completed in time, an abrupt shutdown is
* made, and the items which were not yet processed are returned.
*
* Subclasses may override this method to shut down the spool backend,
* such as persisting remaining items and releasing resources, but must
* first call {@code super.shutdown(timeout)} to stop current processing.
*
* @param timeout the maximum time (in milliseconds) to
* wait for current items to finish processing
* @return the items which were not yet processed
*/
public Collection shutdown(long timeout) {
exec.shutdown();
try {
exec.awaitTermination(timeout, TimeUnit.MILLISECONDS);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt(); // reset interrupt flag
}
Collection remaining = exec.shutdownNow();
return Containers.map(TASK_MAPPER, remaining);
}
/**
* Polls the spool (until interrupted) and sends items
* to processing using the executor.
*/
@Override
public void run() {
while (true) {
final T item;
try {
if (Thread.interrupted()) // additional check before calling next()
throw new InterruptedException();
item = next(); // blocks until available
try {
exec.execute(new Item(item));
} catch (Throwable t) {
processComplete(item, t);
}
} catch (InterruptedException ie) {
break; // kill thread
} catch (Throwable t) {
processComplete(null, t);
}
}
}
/**
* Adds an item to the spool.
*
* @param item the item to add
* @throws Exception if the item cannot be added
*/
public void add(T item) throws Exception {
add(item, 0);
}
/**
* Adds an item to the spool. The item's processing will not begin
* before the given start time.
*
* @param item the item to add
* @param beginTime the minimum absolute time (milliseconds since
* the epoch) at which processing of this item may begin
* @throws Exception if the item cannot be added
*/
public abstract void add(T item, long beginTime) throws Exception;
/**
* Returns the next item to be processed, blocking if necessary
* until one becomes available.
*
* @return the next item to process
* @throws InterruptedException if the thread is interrupted while
* waiting for the next item to become available
* @throws Exception if an error occurs while retrieving the next item
*/
protected abstract T next() throws InterruptedException, Exception;
/**
* Processes an item.
*
* If processing can take a long time, it is recommended that
* the implementation respect the Thread interruption mechanism
* so that it can be shutdown in a timely manner.
*
* @param item the item to process
* @throws InterruptedException if the thread is interrupted before
* processing has been completed
* @throws Exception if an error occurs during processing
*/
protected abstract void process(T item) throws InterruptedException, Exception;
/**
* Signals the completion of an item's processing, whether successful
* or not. If an error occurred while retrieving an item or processing it,
* the exception is provided.
*
* @param item the item whose processing has completed, or null if an error
* occurred before an item could be retrieved
* @param t the error which occurred, or null if processing
* completed successfully
*/
protected abstract void processComplete(T item, Throwable t);
}