Добавить стек трассировки потока вызывающей стороны в создаваемый новый поток для упрощения отладки

Я часто сталкивался с проблемой при отладке.

Иногда поток заканчивается, выбрасывая исключение.

И причина этой проблемы - вызывающая сторона / стартер потока.

Вызывающий отправил неверный параметр или вызвал поток без инициализации чего-либо.

Чтобы найти, откуда был вызван конкретный поток, требуется немного дополнительных усилий, так как трассировка стека бесполезна.

Что если бы мы могли добавить трассировку стека потока вызывающей стороны в вызываемый поток.

Рассмотрим следующий пример:

public class ThreadTest {
        Thread t = new Thread("executeNonBlocking") {
            @Override public void run() {
                // What would be a good way to 
                // append callerStackTrace to the stack
                // trace of this thread
                System.out.println("inside");
                new Throwable().printStackTrace();
            }
        };

    public void executeNonBlocking() {
        final StackTraceElement[] callerStackTrace = new Throwable().getStackTrace();
        new Throwable().printStackTrace();
        t.start();
    }

    public static void main(String[] args) {
        new ThreadTest().executeNonBlocking();
    }
}

Выход

java.lang.Throwable
        at ThreadTest.executeNonBlocking(ThreadTest.java:27)
        at ThreadTest.main(ThreadTest.java:41)
inside
java.lang.Throwable
        at ThreadTest$1.run(ThreadTest.java:34)

Желаемый вывод

java.lang.Throwable
        at ThreadTest.executeNonBlocking(ThreadTest.java:27)
        at ThreadTest.main(ThreadTest.java:41)
inside
java.lang.Throwable
        at ThreadTest.executeNonBlocking(ThreadTest.java:27)
        at ThreadTest.main(ThreadTest.java:41)
        at ThreadTest$1.run(ThreadTest.java:34)

Изменить: вот решение, полученное после обсуждения с @ peter-lawrey

public class StackTraceInheritingThread {

    private final Runnable r;

    private volatile Thread th = null;
    private String title;
    private boolean daemon;

    private InheritedStackTrace ist ;

    private static final ThreadLocal<InheritedStackTrace> tl = new ThreadLocal<InheritedStackTrace>();

    public StackTraceInheritingThread(Runnable r) {
        this.r = r;
    }

    private final class StackTraceInheritingUncaughtExceptionHandler implements  Thread.UncaughtExceptionHandler {

        @Override public void uncaughtException(Thread t, Throwable e) {
            if(ist!=null){ 
                e.addSuppressed(ist);
            }
            e.printStackTrace(System.err);
        }

    }

    public StackTraceInheritingThread setName(String nm){
        this.title = nm;
        return this;
    }

    public StackTraceInheritingThread setDaemon(boolean daemon) {
        this.daemon = daemon;
        return this;
    }

    public void start(){
        if(th!=null){
            throw new IllegalStateException("Already started");
        }

        th = new Thread(new Runnable() {
            @Override public void run() {
                tl.set(ist);
                r.run();
            }
        },title);
        th.setUncaughtExceptionHandler(new StackTraceInheritingUncaughtExceptionHandler());
        if(daemon)th.setDaemon(true);
        ist = new InheritedStackTrace();
        th.start();
    }

    public static Throwable getInheritedStackTrace(){
        return tl.get();
    }

    public static StackTraceInheritingThread make(Runnable r1){
        return new StackTraceInheritingThread(r1);
    }


    private static final class InheritedStackTrace extends Exception {

    }


    public static void main(String[] args) {
        StackTraceInheritingThread.make(new Runnable() {

            @Override
            public void run() {
                System.out.println("heelo");
                throw new RuntimeException();
            }
        }).setName("ExperimentalThread").start();
    }
}

1 ответ

Решение

Вы можете сохранить Throwable, используемый для создания потока, в локальной переменной потока.

public enum Throwables {
    ;

    private static final InheritableThreadLocal<Throwable> STARTING_THREAD = new InheritableThreadLocal<>();

    public static void set(Throwable t) {
        STARTING_THREAD.set(t);
    }

    public static Throwable get() {
        return STARTING_THREAD.get();
    }

    public static void printStartingThrowable() {
        Throwable throwable = get();
        if (throwable == null) return;
        throwable.printStackTrace();
    }

    public static Thread start(Runnable run, String name, boolean daemon) {
        Throwable tmp = new Throwable("Started here");
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                set(tmp);
                run.run();
            }
        }, name);
        t.setDaemon(daemon);
        t.start();
        return t;
    }

    public static void main(String... ignored) {
        try {
            method();
        } catch (Throwable t) {
            System.err.println("\nThrown in " + Thread.currentThread());
            t.printStackTrace();
            printStartingThrowable();
        }

        start(new Runnable() {
            @Override
            public void run() {
                try {
                    method();
                } catch (Throwable t) {
                    System.err.println("\nThrown in " + Thread.currentThread());
                    t.printStackTrace();
                    printStartingThrowable();
                }
            }
        }, "Test thread", false);

    }

    private static void method() {
        throw new UnsupportedOperationException();
    }
}

печать

Thrown in Thread[main,5,main]
java.lang.UnsupportedOperationException
    at Throwables.method(Throwables.java:59)
    at Throwables.main(Throwables.java:36)

Thrown in Thread[Test thread,5,main]
java.lang.UnsupportedOperationException
    at Throwables.method(Throwables.java:59)
    at Throwables.access$000(Throwables.java:1)
    at Throwables$2.run(Throwables.java:47)
    at Throwables$1.run(Throwables.java:26)
    at java.lang.Thread.run(Thread.java:744)
java.lang.Throwable: Started here
    at Throwables.start(Throwables.java:21)
    at Throwables.main(Throwables.java:43)
Другие вопросы по тегам