0

What title says.

Java Scanner throws IndexOutOfBoundsException if an asynchronous thread is interrupted while waiting on nextLine() and another nextLine() is called.

Here is some code that reproduces the problem.

import java.util.NoSuchElementException;
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;
                synchronized (scanner) {
                    str = scanner.nextLine();
                }
                System.out.println("READ THREAD");
                inputQueue.add(str);
            }
        };
        Future<?> task = executor.submit(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);

        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");
                        timer.cancel();
                        task.cancel(true);
                        executor.shutdown();
                        break; // goes to end of main below
                    }
                }
            } catch (InterruptedException e) {
                System.err.println("INTERRUPT!");
            }
            if(!input.isEmpty()) {
                //use input here
                System.out.println("INPUT RECEIVED: " + input);
                input = "";
            }
        }

        //this is only executed AFTER breakIfTrueAsynchronous IS TRUE
        System.out.println("Task cancelled: " + task.isCancelled());
        try{
            String str;
            synchronized (scanner) {
                str = scanner.nextLine();
            }
            System.out.println("INPUT RECEIVED CORRECTLY: " + str);
        }catch (IndexOutOfBoundsException e) {
            System.out.println("ERROR. IndexOutOfBoundsException thrown:");
            e.printStackTrace();
        }
    }
}

I would expect the final scanner.nextLine(); to not throw anything and keep reading System.in as normal.

Instead I got the following stackTrace:

java.lang.IndexOutOfBoundsException: end    at
java.base/java.util.regex.Matcher.region(Matcher.java:1553)     at
java.base/java.util.Scanner.findPatternInBuffer(Scanner.java:1097)  at
java.base/java.util.Scanner.findWithinHorizon(Scanner.java:1800)    at
java.base/java.util.Scanner.nextLine(Scanner.java:1658)     at 
MainThreaded.main(MainThreaded.java:70)

EDIT:

Synchronizing on Scanner solves the exception issue (thank you to @user207421), but now I don't get why the first task isn't interrupted by task.cancel(true).

My working hypothesis now is that the issue lies in the interrupt being received while the thread is waiting on scanner.nextLine().

Big thank you to anyone who can sort this out T.T

7
  • 5
    Did somebody say it was thread-safe? Did somebody say it was safe to ignore exceptions? Especially those that indicate end of stream? Commented May 31, 2024 at 11:50
  • 2
    All methods that are in the java.* package that are declared with throws InterruptedException are guaranteed to be interruptable. readLine() is not one of those methods - which means it is or isn't interruptable. Depends on the OS, JVM version, vendor, chip, and phase of the moon. You had problem X and went: I know! I'll interrupt an active read on sysin! ... oh now I have questions about that. But, that was the wrong 'I know!'. Whatever you're trying to do, '... start trying to interrupt an active untimed read call by calling future.cancel(true)' is unlikely to be right. Commented May 31, 2024 at 12:29
  • The better question is to focus on whatever caused you to think that was the solution. What are you trying to accomplish? Without knowing it's not really possible to craft an answer, but to give a general idea: Set up timed reads, uncook the terminal mode, make it a webapp, use lanterna, use swing - something along those lines perhaps. Commented May 31, 2024 at 12:30
  • @rzwitserloot Thank you! I thought that Threads could be interrupted whenever, I'll keep in mind that some method calls are non-interruptable. The program is for a project, I must use a CLI and I'm trying to make this CLI exit when the boolean "breakIfTrueAsynchronous" is set to true (by another part of the application). By "exit" I mean throw an Exception to the caller of this function. Commented May 31, 2024 at 12:57
  • 1
    Interruption works by checking a flag. Code blocked waiting on i/o cant check a flag. Commented May 31, 2024 at 15:24

1 Answer 1

1

I solved this by only using the thread for reading input and always using inputQueue.poll() or inputQueue.take() to use the inputs in other parts of the code.

Never calling scanner.nextLine() outside of that single thread prevents synch errors, and the rest is handled by the queue. This way I also never stop the thread until the end of the application.

It should be noted that the thread doesn't stop even on a System.exit().
Calling System.exit() while the thread is waiting on scanner.nextLine() instead waits for the input before closing the Thread along with the application.

Big thank you to @rzwitserloot for all the info on interruptible methods:

All methods that are in the java.* package that are declared with throws InterruptedException are guaranteed to be interruptable. readLine() is not one of those methods - which means it is or isn't interruptable. Depends on the OS, JVM version, vendor, chip, and phase of the moon.

And thanks @Nathan who commented:

Interruption works by checking a flag. Code blocked waiting on i/o cant check a flag

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.