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
java.*package that are declared withthrows InterruptedExceptionare 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 callingfuture.cancel(true)' is unlikely to be right.