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.stack.imgur.com/KIb9G.png) The Thread that sends the information to the user is the same that reads the input.

Felix Gaebler
  • 702
  • 4
  • 23
  • [This](https://stackoverflow.com/questions/3595926/how-to-interrupt-bufferedreaders-readline) might help. – Andrew S Apr 04 '18 at 14:40
  • possible duplicate of https://stackoverflow.com/questions/3595926/how-to-interrupt-bufferedreaders-readline – Paul Janssens Apr 04 '18 at 14:52
  • 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" – Felix Gaebler Apr 04 '18 at 15:46
  • And the second answer does not work either, because I cannot write into the Inputstream, can I? – Felix Gaebler Apr 04 '18 at 15:47
  • @PaulJanssens No. That question is about sockets. It does not apply. This question is about the pipe between a parent and a child process. – user207421 Apr 05 '18 at 10:36
  • 2
    Probably run the readline with a timeout? https://stackoverflow.com/questions/6792835/how-do-you-set-a-timeout-on-bufferedreader-and-printwriter-in-java-1-4 – Tarun Lalwani Apr 07 '18 at 05:05
  • @Tarun not a bad approach, but how should I know how long the user wants to see it? – Felix Gaebler Apr 07 '18 at 06:26
  • 1
    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 repeating – Tarun Lalwani Apr 07 '18 at 06:33
  • I will be home soon and try it – Felix Gaebler Apr 07 '18 at 06:36
  • 1
    I'm not in a position to recommend it, but [NuProcess](https://github.com/brettwooldridge/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. – df778899 Apr 07 '18 at 12:33
  • @df778899 I will have a look on that later.. but I am thankful for all advices – Felix Gaebler Apr 07 '18 at 14:10
  • Very interesting implementation @TarunLalwani, I bet I could make something out of the sourcecode behind it – Felix Gaebler Apr 07 '18 at 19:35
  • I got something that worked in my test scenario.. I will deploy it to the Software tomorrow evening and bring you up to date – Felix Gaebler Apr 07 '18 at 22:34
  • Great to hear @FelixGaebler, waiting for your feedback – Tarun Lalwani Apr 08 '18 at 05:36

2 Answers2

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

Felix Gaebler
  • 702
  • 4
  • 23
  • 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. – Falkreon Apr 11 '18 at 14:05
  • @Falkreon what do you recommend then? – Felix Gaebler Apr 11 '18 at 14:10
  • 1
    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. – Falkreon Apr 11 '18 at 14:25
  • That is already done by ProcessBuilder#redirectOutput(File file) Nevermind, that was not my problem, only interrupting the readline.. – Felix Gaebler Apr 11 '18 at 14:29
  • I just used the example in another project, but I feel like you cannot re-append to the stream anymore... weird java – Felix Gaebler Apr 24 '20 at 09:08
  • I tried this example, but main thread is still running after cancelRead called. How can I break the readline loop? – Eric sun Aug 14 '23 at 07:44
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.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • 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.. – Felix Gaebler Apr 05 '18 at 06:54
  • The best solution is properly to fire the readLine method with reflections and add a interrupted check to the while loop – Felix Gaebler Apr 05 '18 at 07:02
  • 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. – user207421 Apr 05 '18 at 10:34
  • 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 want – Felix Gaebler Apr 05 '18 at 10:48
  • 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? – user207421 Apr 05 '18 at 10:52
  • 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 anymore – Felix Gaebler Apr 05 '18 at 10:56
  • 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. – user207421 Apr 05 '18 at 10:57
  • I did not downvote. But your answer did sound wrong.. and the answer itself does not help me, sorry – Felix Gaebler Apr 05 '18 at 10:58
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/168293/discussion-between-felix-gaebler-and-ejp). – Felix Gaebler Apr 05 '18 at 10:59
  • 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. – user207421 Apr 05 '18 at 10:59
  • 1
    Please stay polite. Lets move it to chat for a while, I think we talk at cross purposes – Felix Gaebler Apr 05 '18 at 11:01
  • 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. – Felix Gaebler Apr 05 '18 at 11:07
  • 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 gracefully – Felix Gaebler Apr 05 '18 at 11:07
  • 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 case – Felix Gaebler Apr 05 '18 at 11:08
  • 1
    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. – bowmore Apr 06 '18 at 07:11
  • As this answer correctly states : "You can't stop collecting the process's output, or you will stall the child process." – bowmore Apr 07 '18 at 08:22
  • https://starecat.com/content/wp-content/uploads/stackoverflow-question-how-do-i-do-a-you-do-b-but-that-doesnt-do-a-yeah-nobody-does-a.jpg – Felix Gaebler Dec 10 '19 at 10:04