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

net.freeutils.util.Spool Maven / Gradle / Ivy

The newest version!
/*
 *  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); }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy