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

com.bigdata.util.concurrent.Haltable Maven / Gradle / Ivy

/*

Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016.  All rights reserved.

Contact:
     SYSTAP, LLC DBA Blazegraph
     2501 Calvert ST NW #106
     Washington, DC 20008
     [email protected]

This program 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; version 2 of the License.

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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/
/*
 * Created on Apr 15, 2009
 */

package com.bigdata.util.concurrent;

import java.nio.channels.ClosedByInterruptException;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import com.bigdata.bop.engine.QueryTimeoutException;
import com.bigdata.relation.accesspath.BufferClosedException;
import com.bigdata.relation.accesspath.IAsynchronousIterator;
import com.bigdata.relation.accesspath.IBlockingBuffer;
import com.bigdata.util.InnerCause;

/**
 * Abstract base class for tasks whose processing may be halted asynchronously.
 * While this bears some resemblance to {@link Future}, this class is used in
 * contexts where the process to be halted does not map nicely into a
 * {@link Callable} or {@link Runnable} and hence can not be modeled using a
 * {@link FutureTask}.
 * 

* This class embeds certain knowledge about which exceptions may be observed * during normal termination of asynchronous processes using I/O, thread pools, * and {@link IBlockingBuffer}s. See * {@link #isNormalTerminationCause(Throwable)} for a list of the * {@link Throwable} causes which are treated as normal termination. * * @author Bryan Thompson * @version $Id: AbstractHaltableProcess.java 2265 2009-10-26 12:51:06Z * thompsonbry $ */ public class Haltable implements IHaltable { private final transient static Logger log = Logger .getLogger(Haltable.class); // /** // * Exception used to indicate a {@link #cancel(boolean) cancelled} // * computation. // */ // private static final Throwable CANCELLED = new InterruptedException("CANCELLED"); /** * Lock guarding the {@link #halted} condition and the various non-volatile, * non-atomic fields. */ final private Lock lock = new ReentrantLock(); /** * {@link Condition} signaled when the process is halted */ final private Condition halted = lock.newCondition(); /** * The result of the computation. This is guarded by the {@link #lock} . */ private V result = null; /** * The first cause as set by {@link #halt(Throwable)}. */ private volatile Throwable firstCause = null; /** * Flag is set true if the process was halted by a * {@link Throwable} not included in the set of normal termination causes. */ private volatile boolean error = false; /** * Flag is set true if the process was halted by a * {@link Throwable} indicating a deadline expiration. * * @see * Query timeout only checked at operator start/stop. */ private volatile boolean deadline = false; /** * Set to true iff the process should halt. */ private volatile boolean halt = false; /** * Set to true iff the process was {@link #cancel(boolean) * cancelled}. */ private volatile boolean cancelled = false; /** * Designated constructor. */ public Haltable() { } /** * Halt (normal termination). */ @Override final public void halt(final V v) { lock.lock(); try { if (halt) { // Already halted. return; } try { // signal *all* listeners. halted.signalAll(); result = v; // save result. } finally { halt = true; // volatile write. } } finally { lock.unlock(); } } /** * Halt (exception thrown). The caller is responsible for throwing * the cause out of their own context. * * @param cause * The cause (required). * * @return The argument. */ @Override final public T halt(final T cause) { final boolean didHalt; lock.lock(); try { if (didHalt = !halt) { /* * This is the first cause. */ // note the first cause (and handle an illegal null if found). firstCause = (cause != null ? cause : new IllegalArgumentException()); // note if abnormal termination (firstCause only) error = !isNormalTerminationCause(firstCause); deadline = isDeadlineTerminationCause(firstCause); try { // signal *all* listeners. halted.signalAll(); } finally { halt = true; // volatile write. } } } finally { lock.unlock(); } try { /* * Log exception (can potentially log all causes, not just the first * cause). */ logCause(didHalt, cause); } catch (Throwable ex) { // error in logging system - ignore. } return cause; } /** * Return unless processing has been halted. The method should be invoked * from within the execution of the process itself so that it may notice * asynchronous termination. It will throw out the wrapped first cause if * the process is halted. *

* Note: External processes waiting on the {@link Future} interface should * use {@link #isDone()} which does not have the semantics of asserting that * the process should still be running. * * @throws RuntimeException * wrapping the {@link #firstCause} iff processing has been * halted. */ final public void halted() { if (halt) { if (firstCause == null) { /* * Note: this is an error since there is an expectation by the * process when it invokes halted() that the process is still * running (since it invoked halted() it must be running). Since * it is running, */ throw new RuntimeException("Halted"); } throw new RuntimeException(firstCause); } } @Override final public boolean cancel(final boolean mayInterruptIfRunning) { lock.lock(); try { final Throwable t = new InterruptedException(); halt(t); if (firstCause == t) { // iff this was the firstCause. cancelled = true; return true; } return false; } finally { lock.unlock(); } } @Override final public V get() throws InterruptedException, ExecutionException { lock.lock(); try { while (!halt) { halted.await(); } if(cancelled) { final CancellationException t = new CancellationException(); t.initCause(firstCause); throw t; } if (error) throw new ExecutionException(firstCause); return result; } finally { lock.unlock(); } } @Override final public V get(final long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { final long begin = System.nanoTime(); final long nanos = unit.toNanos(timeout); long remaining = nanos; if (lock.tryLock(remaining, TimeUnit.NANOSECONDS)) { try { // subtract out the elapsed time remaining = nanos - (System.nanoTime() - begin); while (!halt) { if (remaining > 0) remaining = halted.awaitNanos(remaining); else throw new TimeoutException(); } if (error) throw new ExecutionException(firstCause); // normal completion. return result; } finally { lock.unlock(); } } // timeout awaiting the lock. throw new TimeoutException(); } /** * Return true if the process is done. */ @Override final public boolean isDone() { return halt; } /** * Return true if the process was halted by a {@link Throwable} * not included in the set of known normal termination causes. */ final public boolean isError() { // Note: lock required for atomic visibility for [halt AND error]. lock.lock(); try { return halt && error; } finally { lock.unlock(); } } @Override public boolean isCancelled() { // Note: lock required for atomic visibility for [halt AND cancelled]. lock.lock(); try { return halt && cancelled; } finally { lock.unlock(); } } @Override final public Throwable getCause() { lock.lock(); try { if (!halt) return null; if (!error) return null; return firstCause; } finally { lock.unlock(); } } @Override final public Throwable getAsThrownCause() { return firstCause; } /** * Return true if the {@link Throwable} is a known normal * termination cause for the process. The method inspects the stack trace, * examining both the outer and {@link InnerCause}s. The following causes * are interpreted as normal termination: *

*
{@link InterruptedException}
*
The process was terminated by an interrupt. Interrupts are typically * used to terminate asynchronous processes when their production limit has * been satisfied or the consumer otherwise chooses to * {@link IAsynchronousIterator#close()} the iterator through which they are * consuming results from the process.
*
{@link CancellationException}
*
A process has been canceled using its {@link Future}.
*
{@link ClosedByInterruptException}
*
A process was interrupted during an IO operation.
*
{@link BufferClosedException}
*
The {@link IBlockingBuffer} on which the process was writing was * asynchronously closed.
*
{@link RejectedExecutionException}
*
A process was not executed because the pool against which it was * submitted had been shutdown (this of course implies that the work queue * was unbounded as a bounded pool will throw this exception if the work * queue is full).
*
* * @param cause * The {@link Throwable}. * * @return true if the {@link Throwable} indicates normal * termination. * * @see #getCause() */ protected boolean isNormalTerminationCause(final Throwable cause) { if (isTerminationByInterrupt(cause)) return true; if (InnerCause.isInnerCause(cause, RejectedExecutionException.class)) return true; return false; } /** * Note: There is a special exemption for {@link QueryTimeoutException}. * This can not be interpreted as "normal" termination since we want the * exception to be thrown out and then turned into the corresponding openrdf * exception. However, we do not want to log a full stack trace for this * since it is, in fact, an exception termination mode for a query. * * @see * Query timeout only checked at operator start/stop. */ protected boolean isDeadlineTerminationCause(final Throwable cause) { if (InnerCause.isInnerCause(cause, QueryTimeoutException.class)) { return true; } return false; } static public boolean isTerminationByInterrupt(final Throwable cause) { if (InnerCause.isInnerCause(cause, InterruptedException.class)) return true; if (InnerCause.isInnerCause(cause, CancellationException.class)) return true; if (InnerCause.isInnerCause(cause, ClosedByInterruptException.class)) return true; if (InnerCause.isInnerCause(cause, BufferClosedException.class)) return true; /* * Note: We can not treat this as normal termination or the query will * fail to report out the openrdf QueryInterruptedException. */ // if (InnerCause.isInnerCause(cause, QueryTimeoutException.class)) // return true; return false; } /** * This logs all unexpected causes @ WARN (anything not reported as normal * termination by {@link #isNormalTerminationCause(Throwable)}), not just * the first cause. All exceptions are logged @ TRACE. If the firstCause is * an error (as opposed to something which originated as an interrupt) it is * logged @ ERROR. */ private void logCause(final boolean isFirstCause, final Throwable cause) { if (isFirstCause) { if (deadline) { log.warn(this + " : isFirstCause=" + isFirstCause + " : " + cause, cause); } else if (error) { log.error(this + " : isFirstCause=" + isFirstCause + " : " + cause, cause); } } else if (log.isEnabledFor(Level.WARN)) { if (!deadline && error) { log.warn(this + " : isFirstCause=" + isFirstCause + " : " + cause, cause); } } else if(log.isTraceEnabled()) { log.trace(this + " : isFirstCause=" + isFirstCause + " : " + cause, cause); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy