3

There are many threads on SO about interrupting reading the system.in but what I am looking for here is some kind of advice as to how to best code what I am trying to achieve.

I have a getlogin() method that needs to do the following: ask a user to input the desired login environnement details, if after 6 seconds user have not input a valid value ("live" or "test") then set userlogin variable to "test" and return it to the caller.

I have taken the following approach for the getlogin() implementation:

  1. launch two threads which do the following:

    • thread1 creates a scanner object then calls scanner.nextline() and depending on user input set a variable userlogin. Interrupts thread2 before exiting thread1.
    • thread2 waits 6 seconds and if after that userlogin is still not set, then set a default value for userlogin. Interrupts thread1 before exiting thread2.
  2. join thread2 to stop main Thread from returning userlogin as null

  3. return userlogin

The problem I have with the approach is that scanner.nextline() does not interrupt when thread2 calls thread1.interrupt, which is why I do not join thread1 in step 2 as the main Thread would hang. Is there a way to get thread1 to complete after thread2 interrupts it? Or else is this approach completely overkill and there is a much simpler way to achieve the contract?

Neuron
  • 5,141
  • 5
  • 38
  • 59
jule64
  • 487
  • 1
  • 6
  • 19
  • @Mathkute's answer with `available` is the simplest. One poll. Though it doesn't interrupt per se, it makes sure blocking happens when there's data. However no need to use awt, as in accepted answer. If you found this question with a similar search to mine, scroll down for Mathkute's answer. – LAFK 4Monica_banAI_modStrike Jan 09 '20 at 23:53

4 Answers4

4

The simplest solution is to expose the underlying stream in the "reading" thread and close that stream from the timeout thread. This should interrupt the reading and raise an exception. Handle this exception and you should be able to proceed with your logic. The only gotcha is that you won't be able to re-use the same stream again. Unfortunately there is no easy way to deal with interruption of blocking system calls.

EDIT:

Following a completely different line of reasoning; given that we can't close the input stream just to interrupt it, the only way I can think of is to use the "programmatic user input" facilities offered by the Robot class. Here is an example which works out for me:

import java.awt.Robot;
import java.awt.event.KeyEvent;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;

public class ConsoleTest {

    /**
     * @param args
     */
    public static void main(String[] args) {
        new TimeoutThread().start();
        new ReaderThread().start();
    }

}

class ReaderThread extends Thread {

    @Override
    public void run() {
        System.out.print("Please enter your name: ");
        try(Scanner in = new Scanner(System.in)) {
            String name = in.nextLine();
            if(name.trim().isEmpty()) {
                name = "TEST"; // default user name
            }
            System.out.println("Name entered = " + name);
        }
    }

}

class TimeoutThread extends Thread {

    @Override
    public void run() {
        try {
            Thread.sleep(TimeUnit.SECONDS.toMillis(5));
            Robot robot = new Robot();
            robot.keyPress(KeyEvent.VK_ENTER);
            robot.keyRelease(KeyEvent.VK_ENTER);
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

}

The above code uses the logic that once that timeout has expired, we simulate a newline which will cause the "name" variable to be blank. Then we have a check which performs the necessary logic and sets the appropriate user name.

The gotcha about the above approach is that it:

  • Uses Robot class of AWT so might not play well with headless terminals (?)
  • Assumes that the focus window is the console window. If the focus is somewhere else, the ENTER key-press will be registered for that window as opposed to your application window.

Hope this helps you out. I'm really out of ideas now. :)

Sanjay T. Sharma
  • 22,857
  • 4
  • 59
  • 71
  • Thanks for your suggestion. Currently my inputstream is system.in so if I close it I have troubles. Is there a way to raise an exception inside scanner that would fakes an inputstream.in.close event? – jule64 Oct 09 '12 at 17:03
  • @jule64: Updated my post with a new approach. – Sanjay T. Sharma Oct 10 '12 at 06:27
  • Thanks so much for looking further into it. Give me some time to see if it works for my problem but I already feel I am getting closer to a working solution thanks to your work. +1 – jule64 Oct 10 '12 at 14:48
  • I am also exploring the solution suggested by @bmargulies that involves commons-io's CloseShieldInputStream in response to a similar question on this thread http://stackoverflow.com/questions/8203981/closing-bufferedreader-and-system-in. Will try to get back today with results – jule64 Oct 10 '12 at 14:51
  • Sorry for the delayed response. I went for your solution in the end. Very happy with the result. Thanks again! – jule64 Oct 25 '12 at 22:50
  • @jule64: Glad I could help you out. Good luck with your project! :) – Sanjay T. Sharma Oct 26 '12 at 05:41
3

Why not just poll with System.in.available() if there are bytes to read? It is non-blocking: one can do the call to Scanner.nextLine(), which is blocking, when sure it works and does not block.

Mathkute
  • 71
  • 5
0

A FutureTask together with a lambda expression can also be used:

FutureTask<String> readNextLine = new FutureTask<String>(() -> {
  return scanner.nextLine();
});

ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(readNextLine);

try {
  String token = readNextLine.get(5000, TimeUnit.MILLISECONDS);
  ...
} catch (TimeoutException e) {
  // handle time out
}
geri
  • 346
  • 2
  • 6
-1

Another version of geri's answer would be:

ExecutorService executor = Executors.newFixedThreadPool(1);

Future<String> future = executor.submit(() -> {
    try (Scanner in = new Scanner(System.in)) {
        return in.nextLine();
    }
});

try {
    return future.get(5, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e1) {
    return ...;
}
Joern
  • 1,926
  • 1
  • 13
  • 18
  • Also a nice solution, I posted a JUnit test that is maybe useful for investigation and experiments, see https://pastebin.com/nfgkLQda – geri May 20 '17 at 17:54
  • This leaves the read hanging in its own thread, preventing JVM termination. – Jim Garrison Nov 21 '17 at 00:39