javabufferedreader

Command Prompt seems to not echo System.in characters when using BufferedReader.ready() method


I'm building an application for a university project. While building the TUI that must use directly the terminal to receive input and display it. I wanted to achieve the following, not block the main thread on which I should listen for input in order to be able to receive an asynchronous information and throw the exception that would basically bring the user back to the start screen. My code for this is the following:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 * Test class for asynchronous input
 * Please note that while running in CMD the input is actually visualized AFTER pressing enter.
 */
public class Main
{
    public static final int SLEEP_MILLIS = 200;

    public static void main (String[] args) {
        String input = "";
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

        System.out.println("Test asynchronous input started, write anything below:");
        while(!input.toLowerCase().matches("q|quit|exit")) {
            try {
                // wait until we have data to complete a readLine()
                while (!reader.ready()) {
                    Thread.sleep(SLEEP_MILLIS);
                    //the other asynchronous condition goes here
                }
                input = reader.readLine();
            } catch (InterruptedException | IOException ignored) {
                System.err.println("ERROR OF READER!");
            }
            System.out.println("INPUT RECEIVED: " + input);
        }
    }
}

The BufferedReader.ready() method should return false if the buffer is empty or true if the buffered reader is ready as the documentation states.

By what I understood the application should be able to cycle into the while whilst the user doesn't write anything. In fact on a linux terminal (using WSL) everything works correctly, here's a screenshot: How the app looks on a linux terminal

The problem me and my team encountered is that on cmd and on powershell it seems like the terminal doesn't echo properly the user input. What I mean is that if the user writes something and than enters to send the command everything works fine, even displaying works correctly. The only problem is that the input written by the user doesn't show up.

We have tried to change it from a while to an if. We have tried to remove the sleep because we thought it could mess with windows thread management. We didn't have many more ideas because we have no clue on what the problem could be since on linux it works fine.

The only thing that maybe could solve my problem I found in this discussion: how to read from standard input non-blocking?. Though it is not exactly what my problem is.

EDIT: replaced the code above with a minimal reproducible example that has the exact same problem described.


Solution

  • I tried the thread with BlockingQueue approach and it solves the problem. Everything shows up properly on any console.

    Thank you to @user85421 and to @MarkRotteveel for the suggestion.

    This solution has the added benefit of immediately handling the input even for high values of SLEEP_MILLIS.

    import java.util.Scanner;
    import java.util.Timer;
    import java.util.TimerTask;
    import java.util.concurrent.*;
    
    /**
     * Test class for asynchronous input
     */
    public class MainThreaded
    {
        public static final int SLEEP_MILLIS = 200;
        public static final Object LOCK = new Object();
        public static boolean breakIfTrueAsynchronous = false;
    
        public static void main (String[] args) {
            BlockingQueue<String> inputQueue = new LinkedBlockingDeque<>();
            Scanner scanner = new Scanner(System.in);
            ExecutorService executor = Executors.newSingleThreadExecutor();
            Runnable inputReader = () -> {
                while(true) {
                    String str = scanner.nextLine();
                    inputQueue.add(str);
                }
            };
            executor.execute(inputReader);
    
            //run a timer to get the asynchronous error every 3 sec
            Timer timer = new Timer();
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    synchronized (LOCK) {
                        breakIfTrueAsynchronous = true;
                    }
                }
            }, 3000, 3000);
    
            System.out.println("Test asynchronous input started, write anything below:");
            String input = "";
            while(true) { // use ctrl+C to exit
                try {
                    input = inputQueue.poll(SLEEP_MILLIS, TimeUnit.MILLISECONDS);
                    if(input == null) input = ""; // if timeout elapses, avoid null value
                    synchronized (LOCK){
                        if(breakIfTrueAsynchronous){
                            System.err.println("BREAK RECEIVED");
                            //here our code would throw Exception and do something else
                            breakIfTrueAsynchronous = false;
                        }
                    }
                } catch (InterruptedException ignored) {
                    System.err.println("ERROR OF READER!");
                }
                if(!input.isEmpty()) {
                    //use input here
                    System.out.println("INPUT RECEIVED: " + input);
                    input = "";
                }
            }
        }
    }
    

    EDIT: I added a Timer to trigger the asynchronous break condition for testing purposes.

    EDIT2: This solution gives a different problem in our project. Here I posted another question if anyone is interested.