Interrupt BufferedReader#readLine() without closing InputStream
Asked Answered
T

2

11

The InputStream of my Process should attach and detach whenever the user wants to see it or not. The attaching works fine, but the detach fails. Default answer to interrupt the readLine() method is always to close the stream, but I cant in this case or the Process will finish or at least not available for future attachments. This is how the stream is read:

BufferedReader reader = new BufferedReader(new InputStreamReader(getProcess().getInputStream()));
String line;

while ((line = reader.readLine()) != null) {
    System.out.println(line);
}

To detach I tried some stuff:

  • Close any of the streams, failed: close method is blocking and waits for the readLine()
  • Implement another stream to send null / abortion value with SequenceInputStream, failed: when one InputStream was waiting for input, the other was not even called
  • Use reflections to unlock the read() method inside any of the streams, failed: not sure why, but did not work. Should we go on with this try? Here is the sourcecode:

    try {
    
        Field modifiers = Field.class.getDeclaredField("modifiers");
        modifiers.setAccessible(true);
    
        Field fdecoder = stream.getClass().getDeclaredField("sd");
        fdecoder.setAccessible(true);
        modifiers.setInt(fdecoder, 1);
        StreamDecoder decoder = (StreamDecoder) fdecoder.get(stream);
    
        Field flock = decoder.getClass().getSuperclass().getDeclaredField("lock");
        flock.setAccessible(true);
        modifiers.setInt(flock, 1);
        Object lock = (Object) flock.get(decoder);
    
        synchronized (lock) {
            lock.notifyAll();
        }
    
    } catch (NoSuchFieldException | IllegalAccessException e) {
        Wrapper.handleException(Thread.currentThread(), e);
    }
    

Not sure how I can fix this. Could you help me interrupting the readLine() method without closing the stream, simple and performant? Thanks.

Edit: What do I mean by "performant"? My application has not much users, but a lot of processes. The answer by @EJP is not wrong - but unperformant in the case of my application. I cannot have hundreds of threads for hundreds of processes, but I can have as many processes as I have users watching. That's why I try to interrupt the process gracefully. Fewer threads, less running/blocked threads. Here is the application described (https://i.sstatic.net/KIb9G.png) The Thread that sends the information to the user is the same that reads the input.

Tessy answered 4/4, 2018 at 13:59 Comment(14)
This might help.Harlin
possible duplicate of #3596426Resilience
No and no.. you can easily see its neither a duplicate than help if you read the first sentence of the answer.. "Close the socket..." in my title: "without closing"Tessy
And the second answer does not work either, because I cannot write into the Inputstream, can I?Tessy
@PaulJanssens No. That question is about sockets. It does not apply. This question is about the pipe between a parent and a child process.Yvoneyvonne
Probably run the readline with a timeout? #6793335Dirkdirks
@Tarun not a bad approach, but how should I know how long the user wants to see it?Tessy
So the way I assume it would work is that you would is you will run readline with a timeout, catch the exception, in the exception check if user wanted to detach (i don't know how you are checking this), but in your exception handling you will check for it and leave the process as it is and if the detach has not been asked you will again attach to readline with a timeout and cycle will keep repeatingDirkdirks
I will be home soon and try itTessy
I'm not in a position to recommend it, but NuProcess looks interesting for this. This is about NIO style non-blocking I/O against processes. As you say, the streams around Process are blocking - this looks it uses a bit of native magic to break away from this.Mirilla
@Mirilla I will have a look on that later.. but I am thankful for all advicesTessy
Very interesting implementation @TarunLalwani, I bet I could make something out of the sourcecode behind itTessy
I got something that worked in my test scenario.. I will deploy it to the Software tomorrow evening and bring you up to dateTessy
Great to hear @FelixGaebler, waiting for your feedbackDirkdirks
T
10

I didn't expect it to work, but futures are actually cancelable (but why?). After @Tarun Lalwani mentioned the TimeLimiter of Googles Guava library, I inspected the code, tried it in my examples (worked!) and rewrote it a bit - make it not time-based, but method-call-based?!

Here is what I got from my research: A wrapper for the BufferedReader:

public class CancelableReader extends BufferedReader {

    private final ExecutorService executor;
    private Future future;

    public CancelableReader(Reader in) {
        super(in);
        executor = Executors.newSingleThreadExecutor();
    }

    @Override
    public String readLine() {

        future = executor.submit(super::readLine);

        try {
            return (String) future.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        } catch (CancellationException e) {
            return null;
        }

        return null;

    }

    public void cancelRead() {
        future.cancel(true);
    }

}

This class allows you to use the BufferedReader#readLine() when you need it and cancel it when you want to continue / interrupt the Thread it is running in. Here is some example code of it in action:

public static void main(String[] args) {

    System.out.println("START");

    CancelableReader reader = new CancelableReader(new InputStreamReader(System.in));
    String line;

    new Thread(() -> {

        try {

            Thread.sleep(10000);
            reader.cancelRead();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }).start();

    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }

    System.out.println("END");

}

And the output of it:

START
> Hello World!
Hello World!
> What's up?
What's up?
END //Exactly after 5 seconds, when the cancel was called
> Hey, you still there?
//No output as expected

And the last thing I wanna say is why this and not closing InputStream or create one Thread per process? In this case the InputStream is the stream of a Process, which means we cannot close it. One way would be to unblock readLine() and return null to finish the while-loop, but this is made with Reflection, which is not as beautiful as our solution now and didn't work for any reason. The application uses many processes but has a limited amount of users - thats why we decide for the amount of threads per user and not per process.

I hope you guys will find this Thread in the future and it is helpful for you. Would be awesome if you leave an upvote, so I can get back my rep of the bounty. Dont forget to upvote the comments either! They helped me alot and brought me to the right solution: Interrupt BufferedReader#readLine() without closing InputStream

Tessy answered 8/4, 2018 at 9:55 Comment(6)
This probably works because the reader is probably in Thread::wait looking for a notify, and cancelling a Future (depending on settings) calls Thread::interrupt. I would strongly reccommend against relying on this behavior across platforms and JRE implementations.Normative
@Normative what do you recommend then?Tessy
This is a bit of an XY problem - as @EJP mentions, ignoring the output from a Process can wind up stalling it when the inputbuffer overflows, so we need to consume all their output from the time we start them until the time they finish. I'd probably buffer the output (or the last N lines of output) in java or spit it out to a log file, and then address the user interface as a completely separate problem.Normative
That is already done by ProcessBuilder#redirectOutput(File file) Nevermind, that was not my problem, only interrupting the readline..Tessy
I just used the example in another project, but I feel like you cannot re-append to the stream anymore... weird javaTessy
I tried this example, but main thread is still running after cancelRead called. How can I break the readline loop?Subtitle
Y
4

You're going at this back to front.

You can't stop collecting the process's output, or you will stall the child process.

You want to stop displaying the output when the user doesn't want to see it. Look on it as a user interface issue only.

Yvoneyvonne answered 4/4, 2018 at 23:54 Comment(17)
But thats not performant is it? I could also have for every of my process instances (could be up to 600) an own Thread that sends the line if the User is attached. Otherwise it stays in its blocking state. Nobody wants 600 Threads. The application needs to be Scalable..Tessy
The best solution is properly to fire the readLine method with reflections and add a interrupted check to the while loopTessy
What exactly do you mean by 'not performant'? It is certainly not 'performant' to stall the child process because you aren't reading its output, and nor is it going to reduce the number of threads: although 600 isn't as many as you seem to think.Yvoneyvonne
Well I thought I open a "watcher" Thread when the user attaches and close it when the user detaches... only as many Threads open as users are watching processes.. and users are in my application definitely less than processes... users can start as many as they wantTessy
I have no idea what you're talking about, but stalling processes because you aren't reading their input is not 'performant' in any useful sense of the word, and can only increase the number of threads, as it will take longer for the child process to complete. What you need is for the child process to complete as quickly as possible, and selectively display its output according to user preferences. Surely this is obvious?Yvoneyvonne
Isn't that what I requested? You are completely right.. User wants to see the output of the process (actually a server console), thread starts and runs the BufferedReader User closes the console - Thread interrupts, no processing power required anymoreTessy
No, it isn't what you requested. You requested a way to 'interrupt BufferedReader.readline()', i.e. to stop and resume reading the output. My entire answer and comments have been devoted to expounding that that isn't what you want. You want to stop displaying the output, not interrupt its collection.Yvoneyvonne
I did not downvote. But your answer did sound wrong.. and the answer itself does not help me, sorryTessy
Let us continue this discussion in chat.Tessy
I haven't said anything about downvoting. I don't know why you even mention it, as there are no downvotes here. Yet. 'Sounds wrong' is not a problem description, and neither is 'does not help me'. I've expended enough energy on this. If you don't get it now you never will.Yvoneyvonne
Please stay polite. Lets move it to chat for a while, I think we talk at cross purposesTessy
The user views a webinterface. From the webinterface the user can choose between some processes to watch. If the user wants to watch the process that is started in the java application, it connects via websocket and a watcherThread is started... the thread reads the input stream of the process and sends the user any message if there is something new in it like in my readLine() while loop. When the user closes the windows the java application recieves the disconnect and should stop the watcherThread.Tessy
Threads should not be destroyed, thats why I try to interrupt it. But at the moment its stuck in the while loop and thats why I want to unblock the readLine() and return null to interrupt the watcherThread gracefullyTessy
Nothing to say about who did the downvote to my question. overthink your behaviour. A downvote means "This question does not show any research effort; it is unclear or not useful", thats not the caseTessy
The fact that displaying the input is so tightly coupled to reading it, is what forces you to interrupt the reading of the output to unblock the displaying of it. What @EJP is saying is : decouple the two, and your problem goes away. You won't need to interrupt reading the output, because your display logic will not be blocked by it.Theran
As this answer correctly states : "You can't stop collecting the process's output, or you will stall the child process."Theran
starecat.com/content/wp-content/uploads/…Tessy

© 2022 - 2024 — McMap. All rights reserved.