0

I can't find a way (neither through SO nor debugging my code) to allow an exception thrown from a tread to be propagated to the main thread. I have already tried using Thread.setUncaughtExceptionHandler() and using CompletableFuture: .exceptionally(), .handle(),...

The point is that with these mechanisms (as I debug), the actual handling is performed on the worker thread, not main thread -and I cannot manage to get it to to the main thread.

The overall point is that I'm writing a test and that if the exception raises in the worker thread, it never gets to the main thread where the test is running, making the test to pass even when something went wrong.

I would need that exception would raise asynchronous; I cannot wait for the future to complete as I need to immediately return a Stream (a PipedStream) without blocking, from the main tread.

The only tip I get is a console error (and only when I use the traditional Thread + uncaughtExceptionHandler approach, no log at all when I try with CompletableFuture):

Exception: com.example.exception.MyException thrown from the UncaughtExceptionHandler in thread "Thread-5"

or this if I don't define the exception handler:

Exception in thread "Thread-5" com.example.MyException: Exception message

I provide some code:

try {
    @SuppressWarnings("squid:S2095") final PipedOutputStream pos = new PipedOutputStream();
    final PipedInputStream pis = new PipedInputStream(pos);

    CompletableFuture.runAsync(pipeDecryptorRunnable(inputStream, pos));

    return pis;

} catch (IOException e) {
    throw new CryptographyException(e.getMessage(), e);
}

Inside pipeDecryptorRunnable is a CipherStream that decrypts data. The exception is thrown there. But I cannot catch it in the main thread and becomes invisible. The pisstream returning from this method is used to read and worker thread decrypts data on-the-fly as its being read prom pis.

EDIT: UncaughtExceptionHandler as the answers in similar questions suggests does not work for my scenario as the handler code is invoked by worker thread, not the main one.

Gerard Bosch
  • 648
  • 1
  • 7
  • 18
  • "I cannot wait for the future to complete" => the future will complete as soon as an exception is thrown. It would probably help if you showed a [mcve] explaining what behaviour you would expect. – assylias Nov 09 '17 at 15:30
  • How do you expect something like this to work? Just throw a random exception at a random point of execution within the main thread? – Sotirios Delimanolis Nov 09 '17 at 15:34
  • The way describe your requirements, it simply can't be done. If you just fire and forget your worker thread and never wait for its completion, the main thread might well have finished completely before the worker comes to the exception-throwing point. – Ralf Kleberhoff Nov 09 '17 at 15:38
  • You could have a queue somewhere, where you could push caught exceptions. And then inside main thread periodically check it if there is something inside, and act accordingly. – Coderino Javarino Nov 09 '17 at 15:40
  • @CoderinoJavarino I have seen this pattern in some posts. Do you think is a good approach? If so I need to think how to do it in my case. – Gerard Bosch Nov 09 '17 at 15:58
  • @RalfKleberhoff I have provided some code. Main thread should not end before worker as main is reading content from a stream provided by worker thread. Worker will write the last byte and finish before Main reads it. – Gerard Bosch Nov 09 '17 at 16:00
  • 1
    You have established one communication path between the worker thread and the caller (main) thread, being the pipe. You could use this to transport the exception: extend PipedInputStream (and maybe PipedOutputStream), making the read() methods throw an IOException, wrapping the original exception provided by the worker side. – Ralf Kleberhoff Nov 09 '17 at 16:28
  • @RalfKleberhoff I see a bit of what you mean, but could you elaborate a little more? Maybe posting an answer. I can't see completely how would it work :) – Gerard Bosch Nov 09 '17 at 18:26
  • 1
    Can't post an answer, as the question is closed :-(. Extend PipedInputStream with a `setException(Throwable foreignException)` method that just stores the exception object. Override the various `read()` methods to throw a `new IOException(foreignException)` if one exists. Make the `PipedInputStream pis` available to your worker thread, so you can call `pis.setException()` in your catch clause. One way to get access to the PipedInputStream might be to also extend the PipedOutputStream with a `setException()` method that communicates with its consumer `PipedInputStream`. – Ralf Kleberhoff Nov 09 '17 at 18:43
  • @RalfKleberhoff your tips brought me to the solution :) Leveraging my piped stream as you suggested helped me to notify Exceptions across threads as piped streams serves as an inter thread communication mechanism. I will try to re-open the question to post the code for my solution. Thx! – Gerard Bosch Nov 10 '17 at 13:56

1 Answers1

0

Thanks to the tip of @RalfKleberhoff I came with the solution I was looking for. The fact is that to achieve the desired behavior an inter thread communication mechanism is required. And given that I were already working with a PipedStreams, I can leverage it to accomplish the goal –I also thought in some kind of solution involving an event bus to signal or communicate from one thread to another (main thread/worker thread) and I think some event bus libraries can achieve it as well.

So back to piped streams I had a pis to read in the main thread and its connected pos to write in the worker thread. So that, when an exception was raised in the worker, I need the main thread to notice that.

To achieve that you can extend the PipedOutputStream class, adding a method to signal the connected pipe when an exception occurs. The same way, you need to extend the connected PipedInputStream to be signaled on exception, store the exception and override the read methods in order to check if an exception occurred first and, in that case, throw the exception wrapped in the IOException of the read method.

Here the code:

/**
 * This piped stream class allows to signal Exception between threads, allowing an exception produced in the writing
 * thread to reach the reading thread.
 *
 * @author Gerard on 10/11/2017.
 */
public class ExceptionAwarePipedOutputStream extends PipedOutputStream {

    private final ExceptionAwarePipedInputStream sink;

    public ExceptionAwarePipedOutputStream(ExceptionAwarePipedInputStream sink) throws IOException {
        super(sink);
        this.sink = sink;
    }

    /**
     * Signals connected {@link ExceptionAwarePipedInputStream} that an exception ocurred allowing to propagate it
     * across respective threads. This works as inter thread communication mechanism. So it allows to the reading thread
     * notice that an exception was thrown in the writing thread.
     *
     * @param exception The exception to propagate.
     */
    public void signalException(Throwable exception) {
        sink.signalException(exception);
    }
}

·

/**
 * This piped stream class allows to signal Exception between threads, allowing an exception produced in the writing
 * thread to reach the reading thread.
 *
 * @author Gerard on 10/11/2017.
 */
public class ExceptionAwarePipedInputStream extends PipedInputStream {

    private volatile Throwable exception;

    void signalException(Throwable exception) {
        this.exception = exception;
    }

    @Override
    public int read(byte[] b) throws IOException {
        final int read = super.read(b);
        checkException();
        return read;
    }

    @Override
    public synchronized int read() throws IOException {
        final int read = super.read();
        checkException();
        return read;
    }

    @Override
    public synchronized int read(byte[] b, int off, int len) throws IOException {
        final int read = super.read(b, off, len);
        checkException();
        return read;
    }

    private void checkException() throws IOException {
        if (exception != null) {
            throw new IOException(exception.getMessage(), exception);
        }
    }
}

The client code:

public InputStream decrypt(InputStream inputStream) {

    assert supportedStreamModes.contains(mode) : "Unsupported cipher mode for stream decryption " + mode;

    @SuppressWarnings("squid:S2095") final ExceptionAwarePipedInputStream pis = new ExceptionAwarePipedInputStream();
    final ExceptionAwarePipedOutputStream pos = newConnectedPipedOutputStream(pis);
    final Cipher decryptor = newDecryptorInitialized(inputStream);

    CompletableFuture.runAsync(
        pipeDecryptorRunnable(inputStream, pos, decryptor));

    return pis;
}

private ExceptionAwarePipedOutputStream newConnectedPipedOutputStream(ExceptionAwarePipedInputStream pis) {
    try {
        return new ExceptionAwarePipedOutputStream(pis);
    } catch (IOException e) {
        throw new CryptographyException(e.getMessage(), e);
    }
}

And the exception handling part (notice the thread signaling):

private Runnable pipeDecryptorRunnable(InputStream inputStream, ExceptionAwarePipedOutputStream pos, Cipher decryptor) {
    return () -> {
        try {

            // do stuff... and write to pos

        } catch (Exception e) {
            // Signaling any (checked or unchecked) exception
            pos.signalException(new CryptographyException(e.getMessage(), e));
        } finally {
            closePipedStream(pos);
        }
    };
}
Gerard Bosch
  • 648
  • 1
  • 7
  • 18