简体   繁体   中英

How can I make an unchecked exception thrown from a thread to be propagated to main thread?

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 pis stream 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.

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 PipedStream s, 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);
        }
    };
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM