diff options
author | Sven Gothel <[email protected]> | 2015-09-12 17:13:52 +0200 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2015-09-12 17:13:52 +0200 |
commit | 47495cd2a228534578731346c8baf2b190bcd241 (patch) | |
tree | 1900f214d2011d480ede63bf64d0c0de62904a5e | |
parent | b94d0c4e2ac4b29ffe6bb832d37b83c6d32497fe (diff) |
Bug 1213: Expose InterruptSource, SourcedInterruptedException and InterruptedRuntimeException
- InterruptSource interface declares methods to retrieve
the source of a Thread.interrupt() call.
- InterruptSource.Thread implements InterruptSource,
i.e. allows code running within such thread to learn about
the interrupt source (stack trace).
- SourcedInterruptedException is a InterruptedException specialization
which may include the source of the causing Thread.interrupt() call.
- InterruptedRuntimeException
An unchecked RuntimeException propagating an InterruptedException
where handling of the latter is not desired.
The causing InterruptedException may be of type SourcedInterruptedException,
hence a detailed stack trace analysis might be possible.
5 files changed, 414 insertions, 12 deletions
diff --git a/src/java/com/jogamp/common/ExceptionUtils.java b/src/java/com/jogamp/common/ExceptionUtils.java index c848a99..108b466 100644 --- a/src/java/com/jogamp/common/ExceptionUtils.java +++ b/src/java/com/jogamp/common/ExceptionUtils.java @@ -57,6 +57,37 @@ public class ExceptionUtils { } } + /** Interface allowing {@link Throwable} specializations to provide their custom stack trace presentation. */ + public static interface CustomStackTrace { + /** Custom non-iterative stack dump */ + void dumpCauseStack(final PrintStream s, final String causeStr, int causeDepth); + /** Custom {@code printStackTrace} method, similar to {@link Throwable#printStackTrace(PrintStream)}. */ + void printStackTrace(final PrintStream s); + } + + public static int dumpCause(final PrintStream s, final String causeStr, Throwable cause, int causeDepth) { + for( ; null != cause; cause = cause.getCause() ) { + if( cause instanceof CustomStackTrace ) { + ((CustomStackTrace)cause).dumpCauseStack(s, causeStr, causeDepth); + } else { + s.println(causeStr+"["+causeDepth+"] by "+cause.getClass().getSimpleName()+": "+cause.getMessage()+" on thread "+Thread.currentThread().getName()); + dumpStack(s, cause.getStackTrace(), 0, -1); + } + causeDepth++; + } + return causeDepth; + } + + public static void printStackTrace(final PrintStream s, final Throwable t) { + if( t instanceof CustomStackTrace ) { + ((CustomStackTrace)t).printStackTrace(s); + } else { + s.println(t.getClass().getSimpleName()+": "+t.getMessage()+" on thread "+Thread.currentThread().getName()); + dumpStack(s, t.getStackTrace(), 0, -1); + dumpCause(s, "Caused", t.getCause(), 1); + } + } + /** * Dumps a {@link Throwable} in a decorating message including the current thread name, * and its {@link #dumpStack(PrintStream, StackTraceElement[], int, int) stack trace}. @@ -65,14 +96,7 @@ public class ExceptionUtils { * </p> */ public static void dumpThrowable(final String additionalDescr, final Throwable t) { - System.err.println("Caught "+additionalDescr+" "+t.getClass().getSimpleName()+": "+t.getMessage()+" on thread "+Thread.currentThread().getName()); - dumpStack(System.err, t.getStackTrace(), 0, -1); - int causeDepth = 1; - for( Throwable cause = t.getCause(); null != cause; cause = cause.getCause() ) { - System.err.println("Caused["+causeDepth+"] by "+cause.getClass().getSimpleName()+": "+cause.getMessage()+" on thread "+Thread.currentThread().getName()); - dumpStack(System.err, cause.getStackTrace(), 0, -1); - causeDepth++; - } + System.err.print("Caught "+additionalDescr+" "); + printStackTrace(System.err, t); } - } diff --git a/src/java/com/jogamp/common/JogampRuntimeException.java b/src/java/com/jogamp/common/JogampRuntimeException.java index d33d498..524bb93 100644 --- a/src/java/com/jogamp/common/JogampRuntimeException.java +++ b/src/java/com/jogamp/common/JogampRuntimeException.java @@ -28,9 +28,10 @@ package com.jogamp.common; -/** A generic exception for Jogamp errors used throughout the binding - as a substitute for {@link RuntimeException}. */ - +/** + * A generic <i>unchecked exception</i> for Jogamp errors used throughout the binding + * as a substitute for {@link RuntimeException}. + */ @SuppressWarnings("serial") public class JogampRuntimeException extends RuntimeException { /** Constructs a JogampRuntimeException object. */ diff --git a/src/java/com/jogamp/common/util/InterruptSource.java b/src/java/com/jogamp/common/util/InterruptSource.java new file mode 100644 index 0000000..1d43961 --- /dev/null +++ b/src/java/com/jogamp/common/util/InterruptSource.java @@ -0,0 +1,132 @@ +/** + * Copyright 2015 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ + +package com.jogamp.common.util; + +/** + * Interface exposing {@link java.lang.Thread#interrupt()} source, + * intended for {@link java.lang.Thread} specializations. + */ +public interface InterruptSource { + /** + * Returns the source of the last {@link #interrupt()} call. + * @param clear if true, issues {@link #clearInterruptSource()} + */ + Throwable getInterruptSource(final boolean clear); + + /** + * Returns the count of {@link java.lang.Thread#interrupt()} calls. + * @param clear if true, issues {@link #clearInterruptSource()} + */ + int getInterruptCounter(final boolean clear); + + /** + * Clears source and count of {@link java.lang.Thread#interrupt()} calls, if any. + */ + void clearInterruptSource(); + + public static class Util { + /** + * Casts given {@link java.lang.Thread} to {@link InterruptSource}, + * if applicable, otherwise returns {@code null}. + */ + public static InterruptSource get(final java.lang.Thread t) { + if(t instanceof InterruptSource) { + return (InterruptSource)t; + } else { + return null; + } + } + /** + * Casts current {@link java.lang.Thread} to {@link InterruptSource}, + * if applicable, otherwise returns {@code null}. + */ + public static InterruptSource currentThread() { + return get(java.lang.Thread.currentThread()); + } + } + + /** + * {@link java.lang.Thread} specialization implementing {@link InterruptSource} + * to track {@link java.lang.Thread#interrupt()} calls. + */ + public static class Thread extends java.lang.Thread implements InterruptSource { + volatile Throwable interruptSource = null; + volatile int interruptCounter = 0; + final Object sync = new Object(); + + public Thread(final String name) { + super(name); + } + public Thread(final Runnable target) { + super(target); + } + public Thread(final Runnable target, final String name) { + super(target, name); + } + public Thread(final ThreadGroup tg, final Runnable target, final String name) { + super(tg, target, name); + } + + @Override + public final Throwable getInterruptSource(final boolean clear) { + synchronized(sync) { + final Throwable r = interruptSource; + if( clear ) { + clearInterruptSource(); + } + return r; + } + } + @Override + public final int getInterruptCounter(final boolean clear) { + synchronized(sync) { + final int r = interruptCounter; + if( clear ) { + clearInterruptSource(); + } + return r; + } + } + @Override + public final void clearInterruptSource() { + synchronized(sync) { + interruptCounter = 0; + interruptSource = null; + } + } + @Override + public final void interrupt() { + synchronized(sync) { + interruptCounter++; + interruptSource = new Throwable(getName()+".interrupt() #"+interruptCounter); + } + super.interrupt(); + } + } +} diff --git a/src/java/com/jogamp/common/util/InterruptedRuntimeException.java b/src/java/com/jogamp/common/util/InterruptedRuntimeException.java new file mode 100644 index 0000000..af6fea8 --- /dev/null +++ b/src/java/com/jogamp/common/util/InterruptedRuntimeException.java @@ -0,0 +1,80 @@ +/** + * Copyright 2015 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ + +package com.jogamp.common.util; + +import com.jogamp.common.JogampRuntimeException; + +/** + * <i>Unchecked exception</i> propagating an {@link InterruptedException} + * where handling of the latter is not desired. + * <p> + * {@link InterruptedRuntimeException} may be thrown either by waiting for any {@link Runnable} + * to be completed, or during its execution. + * </p> + * <p> + * The propagated {@link InterruptedException} may be of type {@link SourcedInterruptedException}. + * </p> + * <p> + * </p> + */ +@SuppressWarnings("serial") +public class InterruptedRuntimeException extends JogampRuntimeException { + + /** + * Constructor attempts to {@link SourcedInterruptedException#wrap(InterruptedException) wrap} + * the given {@link InterruptedException} {@code cause} into a {@link SourcedInterruptedException}. + * + * @param message the message of this exception + * @param cause the propagated {@link InterruptedException} + */ + public InterruptedRuntimeException(final String message, final InterruptedException cause) { + super(message, SourcedInterruptedException.wrap(cause)); + } + + /** + * Constructor attempts to {@link SourcedInterruptedException#wrap(InterruptedException) wrap} + * the given {@link InterruptedException} {@code cause} into a {@link SourcedInterruptedException}. + * + * @param cause the propagated {@link InterruptedException} + */ + public InterruptedRuntimeException(final InterruptedException cause) { + super(SourcedInterruptedException.wrap(cause)); + } + + /** + * Returns the propagated {@link InterruptedException}, i.e. the cause of this exception. + * <p> + * {@inheritDoc} + * </p> + */ + @Override + public InterruptedException getCause() { + return (InterruptedException)super.getCause(); + } +} diff --git a/src/java/com/jogamp/common/util/SourcedInterruptedException.java b/src/java/com/jogamp/common/util/SourcedInterruptedException.java new file mode 100644 index 0000000..7646652 --- /dev/null +++ b/src/java/com/jogamp/common/util/SourcedInterruptedException.java @@ -0,0 +1,165 @@ +/** + * Copyright 2015 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ + +package com.jogamp.common.util; + +import java.io.PrintStream; + +import com.jogamp.common.ExceptionUtils; +import com.jogamp.common.ExceptionUtils.CustomStackTrace; + +/** + * {@link InterruptedException}, which may include the source, see {@link #getInterruptSource()}. + * <p> + * This exception may be created directly where {@link #getCause()} returns {@code null}, + * or by propagating an existing {@link InterruptedException} as returned by {@link #getCause()}. + * </p> + */ +@SuppressWarnings("serial") +public class SourcedInterruptedException extends InterruptedException implements CustomStackTrace { + final Throwable interruptSource; + + /** + * Wraps the given {@link InterruptedException} into a {@link SourcedInterruptedException} + * if it is not yet of the desired type and + * if the current thread if a {@link InterruptSource}, i.e. the source is known. + * <p> + * Otherwise the given {@link InterruptedException} instance is returned. + * </p> + * <p> + * In case method is creating a new wrapping instance, + * {@link InterruptSource#clearInterruptSource()} is being issued. + * </p> + * + * @param ie the to be wrapped {@link InterruptedException} + */ + public static InterruptedException wrap(final InterruptedException ie) { + return wrap(ie, InterruptSource.Util.currentThread()); + } + + /** + * Wraps the given {@link InterruptedException} into a {@link SourcedInterruptedException} + * if it is not yet of the same type and if {@code source} is not {@code null}. + * <p> + * Otherwise the given {@link InterruptedException} instance is returned. + * </p> + * <p> + * In case method is creating a new wrapping instance, + * {@link InterruptSource#clearInterruptSource()} is being issued. + * </p> + * + * @param ie the to be wrapped {@link InterruptedException} + * @param source the {@link InterruptSource} + */ + public static InterruptedException wrap(final InterruptedException ie, final InterruptSource source) { + if( !(ie instanceof SourcedInterruptedException) && null != source ) { + return new SourcedInterruptedException(ie, source.getInterruptSource(true)); + } else { + return ie; + } + } + + /** + * @param message mandatory message of this exception + * @param cause optional propagated cause + * @param interruptSource optional propagated source of {@link Thread#interrupt()} call + */ + public SourcedInterruptedException(final String message, final InterruptedException cause, final Throwable interruptSource) { + super(message); + if( null != cause ) { + initCause(cause); + } + this.interruptSource = interruptSource; + } + + /** + * @param cause mandatory propagated cause + * @param interruptSource optional propagated source of {@link Thread#interrupt()} call + */ + public SourcedInterruptedException(final InterruptedException cause, final Throwable interruptSource) { + super(cause.getMessage()); + initCause(cause); + this.interruptSource = interruptSource; + } + + /** + * Returns the source of the {@link Thread#interrupt()} call if known, + * otherwise {@code null} is returned. + */ + public final Throwable getInterruptSource() { + return interruptSource; + } + + /** + * Returns the propagated {@link InterruptedException}, i.e. the cause of this exception, + * or {@code null} if not applicable. + * <p> + * {@inheritDoc} + * </p> + */ + @Override + public InterruptedException getCause() { + return (InterruptedException)super.getCause(); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(256); + sb.append(getClass().getSimpleName()).append(": "); + if (null != interruptSource) { + sb.append("[sourced]"); + } else { + sb.append("[unknown]"); + } + final String m = getLocalizedMessage(); + if( null != m ) { + sb.append(" ").append(m); + } + return sb.toString(); + } + + @Override + public final void dumpCauseStack(final PrintStream s, final String causeStr, final int causeDepth) { + final String s0 = causeStr+"["+causeDepth+"]"; + s.println(s0+" by "+getClass().getSimpleName()+": "+getMessage()+" on thread "+Thread.currentThread().getName()); + ExceptionUtils.dumpStack(s, getStackTrace(), 0, -1); + if( null != interruptSource ) { + ExceptionUtils.dumpCause(s, s0, interruptSource, 1); + } + } + + @Override + public final void printStackTrace(final PrintStream s) { + s.println(getClass().getSimpleName()+": "+getMessage()+" on thread "+Thread.currentThread().getName()); + ExceptionUtils.dumpStack(s, getStackTrace(), 0, -1); + final int causeDepth = ExceptionUtils.dumpCause(s, "Caused", getCause(), 1); + if( null != interruptSource ) { + ExceptionUtils.dumpCause(s, "InterruptSource", interruptSource, causeDepth); + } + } +} |