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

com.unboundid.util.parallel.AsynchronousParallelProcessor Maven / Gradle / Ivy

/*
 * Copyright 2008-2019 Ping Identity Corporation
 * All Rights Reserved.
 */
/*
 * Copyright (C) 2008-2019 Ping Identity Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License (GPLv2 only)
 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
 * as published by the Free Software Foundation.
 *
 * This program 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 this program; if not, see .
 */
package com.unboundid.util.parallel;



import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import com.unboundid.util.Debug;
import com.unboundid.util.InternalUseOnly;
import com.unboundid.util.StaticUtils;
import com.unboundid.util.ThreadSafety;
import com.unboundid.util.ThreadSafetyLevel;



/**
 * This class provides an asynchronous mechanism to do parallel processing of
 * items that undergo identical processing.  It can be used as part of a
 * pipeline where input items are submitted without waiting for them to
 * complete.  To prevent a backlog of input or output items, a maximum
 * capacity should be set on the {@code pendingQueue} and {@code outputQueue}
 * if one is used.
 * 

* The {@link ParallelProcessor} passed into the constructor is wholly owned * by this instance. It should not be used elsewhere after constructing this * instance. It will be shut down when this instance's {@code shutdown()} * method is called. *

* When this AsynchronousParallelProcessor is no longer needed, {@code shutdown} * must be called to terminate all worker threads. *

* This class is intended to only be used internally by the SDK. * * @param The type of the input items of {@code processAll}. * @param The type of the output items of {@code processAll}. */ @InternalUseOnly() @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) public final class AsynchronousParallelProcessor { // Queue of input items. private final BlockingQueue pendingQueue; // The ParallelProcessor that is used to process the input items. private final ParallelProcessor parallelProcessor; // Processor for the results. private final ResultProcessor resultProcessor; // Thread that pulls items from pendingQueue, passes them to // parallelProcessor, and processes the results. private final InvokerThread invokerThread; // Set to true when this is shutdown. private final AtomicBoolean shutdown = new AtomicBoolean(false); // Set by worker threads to signal that there was a problem during processing. // Once this is set, all calls to submit() will fail with this exception. private final AtomicReference invocationException = new AtomicReference<>(); /** * Constructs a new AsynchronousParallelProcessor with the specified * parameters. * * @param pendingQueue The queue where pending input items will be stored. * If processing of input items cannot keep up with the * rate that they are submitted, then this queue can grow * without bound unless it was created with a maximum * capacity. This queue must be used exclusively by this * instance. * @param parallelProcessor The ParallelProcessor that is used to process * the submitted input items. It must be used * exclusively by this instance. When this instance * is shutdown, the ParallelProcessor will be * shutdown too. * @param resultProcessor The ResultProcessor that is invoked sequentially * for each result. */ public AsynchronousParallelProcessor( final BlockingQueue pendingQueue, final ParallelProcessor parallelProcessor, final ResultProcessor resultProcessor) { this.pendingQueue = pendingQueue; this.parallelProcessor = parallelProcessor; this.resultProcessor = resultProcessor; invokerThread = new InvokerThread(); invokerThread.start(); } /** * Constructs a new AsynchronousParallelProcessor with the specified * parameters. * * @param pendingQueue The queue where pending input items will be stored. * If processing of input items cannot keep up with the * rate that they are submitted, then this queue can grow * without bound unless it was created with a maximum * capacity. This queue must be used exclusively by this * instance. * @param parallelProcessor The ParallelProcessor that is used to process * the submitted input items. It must be used * exclusively by this instance. When this instance * is shutdown, the ParallelProcessor will be * shutdown too. * @param outputQueue The output queue where results will be submitted * sequentially in the order that they were submitted. * If the queue has maximum capacity, then processing * will cease while waiting for capacity in the queue. */ public AsynchronousParallelProcessor( final BlockingQueue pendingQueue, final ParallelProcessor parallelProcessor, final BlockingQueue> outputQueue) { this(pendingQueue, parallelProcessor, new OutputEnqueuer<>(outputQueue)); } /** * Submits the specified item for processing, waiting if necessary for * room in the pendingQueue. * * @param input The input item to process. It must not be {@code null}. * * @throws InterruptedException If this thread is interrupted during * processing. */ public synchronized void submit(final I input) throws InterruptedException { if (shutdown.get()) { throw new IllegalStateException("cannot call submit() after shutdown()"); } final Throwable resultProcessingError = invocationException.get(); if (resultProcessingError != null) { shutdown(); StaticUtils.throwErrorOrRuntimeException(resultProcessingError); } pendingQueue.put(input); } /** * Shuts this down. This includes waiting for all worker threads to * finish. Processing of all input items will complete before this call * returns. It is not an error to call {@code shutdown()} more than once, * but it is an error to call {@code submit()} after {@code shutdown()} has * been called. * * @throws InterruptedException If this thread is interrupted during * processing. */ public synchronized void shutdown() throws InterruptedException { if (shutdown.getAndSet(true)) { // Already shut down. return; } // The invoker thread will not exit until it has completed all // of the pending items. invokerThread.join(); parallelProcessor.shutdown(); } /** * ResultProcessor implementation that enqueues results. * * @param The type of the input items of {@code processAll}. * @param The type of the output items of {@code processAll}. */ private static final class OutputEnqueuer implements ResultProcessor { private final BlockingQueue> outputQueue; /** * Constructor. * * @param outputQueue The queue where results will be enqueued. */ private OutputEnqueuer(final BlockingQueue> outputQueue) { this.outputQueue = outputQueue; } /** * {@inheritDoc} */ @Override() public void processResult(final Result ioResult) throws Exception { outputQueue.put(ioResult); } } /** * This thread pulls items from pendingQueue, processes them in parallel, * and passes the results to resultProcessor. And then does it all over again * until it's shutdown and there is no more to process. */ private final class InvokerThread extends Thread { /** * Constructor. */ private InvokerThread() { super("Asynchronous Parallel Processor"); setDaemon(true); } /** * Pulls items from pendingQueue, processes them in parallel, * and passes the results to resultProcessor. And then does it all over * again until it's shutdown and there is no more to process. */ @Override() public void run() { while (!(shutdown.get() && pendingQueue.isEmpty())) { try { final I item = pendingQueue.poll(100, TimeUnit.MILLISECONDS); if (item != null) { final List items = new ArrayList<>(1 + pendingQueue.size()); items.add(item); pendingQueue.drainTo(items); final List> results = parallelProcessor.processAll(items); for (final Result result : results) { resultProcessor.processResult(result); } } } catch (final Throwable e) { Debug.debugException(e); // It's in the contract of this class that nothing in this try // block should throw an exception under normal operating conditions. // So if we ever catch something here, then we treat that it as // a terminating condition. This thread will continue to run to // drain any remaining items from the queue, so that we can shutdown // reasonably cleanly. However, we will not accept any new input // item submissions. That is, future calls to submit() will force // a shutdown and then throw an Exception, which includes e as the // original cause. We use compareAndSet so that we only keep track // of the first caught exception. invocationException.compareAndSet(null, e); } } } } }