From 47495cd2a228534578731346c8baf2b190bcd241 Mon Sep 17 00:00:00 2001 From: Sven Gothel Date: Sat, 12 Sep 2015 17:13:52 +0200 Subject: 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. --- .../com/jogamp/common/util/InterruptSource.java | 132 +++++++++++++++++ .../common/util/InterruptedRuntimeException.java | 80 ++++++++++ .../common/util/SourcedInterruptedException.java | 165 +++++++++++++++++++++ 3 files changed, 377 insertions(+) create mode 100644 src/java/com/jogamp/common/util/InterruptSource.java create mode 100644 src/java/com/jogamp/common/util/InterruptedRuntimeException.java create mode 100644 src/java/com/jogamp/common/util/SourcedInterruptedException.java (limited to 'src/java/com/jogamp/common/util') 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; + +/** + * Unchecked exception propagating an {@link InterruptedException} + * where handling of the latter is not desired. + *

+ * {@link InterruptedRuntimeException} may be thrown either by waiting for any {@link Runnable} + * to be completed, or during its execution. + *

+ *

+ * The propagated {@link InterruptedException} may be of type {@link SourcedInterruptedException}. + *

+ *

+ *

+ */ +@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. + *

+ * {@inheritDoc} + *

+ */ + @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()}. + *

+ * This exception may be created directly where {@link #getCause()} returns {@code null}, + * or by propagating an existing {@link InterruptedException} as returned by {@link #getCause()}. + *

+ */ +@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. + *

+ * Otherwise the given {@link InterruptedException} instance is returned. + *

+ *

+ * In case method is creating a new wrapping instance, + * {@link InterruptSource#clearInterruptSource()} is being issued. + *

+ * + * @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}. + *

+ * Otherwise the given {@link InterruptedException} instance is returned. + *

+ *

+ * In case method is creating a new wrapping instance, + * {@link InterruptSource#clearInterruptSource()} is being issued. + *

+ * + * @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. + *

+ * {@inheritDoc} + *

+ */ + @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); + } + } +} -- cgit v1.2.3