Skip to content
Advertisement

Preserve Java stack trace across threads

I am using ExecutorService to send mails asynchronously, so there is a class:

class Mailer implements Runnable { ...

That handles the sending. Any exception that gets caught is logged, for (anonymized) example:

javax.mail.internet.AddressException: foo is bar
    at javax.mail.internet.InternetAddress.checkAddress(InternetAddress.java:1213) ~[mail.jar:1.4.5]
    at javax.mail.internet.InternetAddress.parse(InternetAddress.java:1091) ~[mail.jar:1.4.5]
    at javax.mail.internet.InternetAddress.parse(InternetAddress.java:633) ~[mail.jar:1.4.5]
    at javax.mail.internet.InternetAddress.parse(InternetAddress.java:610) ~[mail.jar:1.4.5]
    at mycompany.Mailer.sendMail(Mailer.java:107) [Mailer.class:?]
    at mycompany.Mailer.run(Mailer.java:88) [Mailer.class:?]
    ... suppressed 5 lines
    at java.lang.Thread.run(Thread.java:680) [?:1.6.0_35]

Not very helpful – I need to see the stacktrace that invoked the ExecutorService that caused all of this. My solution is to create an empty Exception and pass it into Mailer:

executorService.submit(new Mailer(foo, bar, new Exception()));
...
// constructor
public Mailer(foo, bar, Exception cause) { this.cause = cause; ...

And now in the case of exception I want to log the problem itself and its cause from the other thread:

try {
  // send the mail...
} catch (Throwable t) {
  LOG.error("Stuff went wrong", t);
  LOG.error("This guy invoked us", cause);
}

This works great but produces two logs. I want to combine t and cause into a single exception and log that one. In my opinion, t caused cause, so using cause.initCause(t) should be the right way. And works. I see a full stack trace: from where the call originated all the way up to the AddressException.

Problem is, initCause() works only once and then crashes. Question 1: can I clone Exception? I’d clone cause and init it with t every time.

I tried t.initCause(cause), but that crashes right away.

Question 2: is there another smart way to combine these 2 exceptions? Or just keep one thread context in the other thread context for logging purposes?

Advertisement

Answer

Following my comment, this is actually what I had in mind. Mind you, I don’t have a way to test it at the moment.

What you pass from your parent thread is New Exception().getStackTrace(). Or better yet, as @Radiodef commented, Thread.currentThread().getStackTrace(). So it’s basically a StackTraceElement[] array.

Now, you can have something like:

public class CrossThreadException extends Exception {

    public CrossThreadException( Throwable cause, StackTraceElement[] originalStackTrace ) {

        // No message, given cause, no supression, stack trace writable
        super( null, cause, false, true );

        setStackTrace( originalStackTrace );
    }
}

Now in your catch clause you can do something like:

catch ( Throwable cause ) {
   LOG( "This happened", new CrossThreadException( cause, originalStackTrace ) );
}

Which will give you a boundary between the two stack traces.

User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement