Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ctrl-C + Return crashes jline #590

Closed
ermingol23 opened this issue Oct 15, 2020 · 6 comments
Closed

Ctrl-C + Return crashes jline #590

ermingol23 opened this issue Oct 15, 2020 · 6 comments

Comments

@ermingol23
Copy link

I was playing around with the picocli.shell.jline3.example.Example application found here
https://github.com/remkop/picocli/blob/master/picocli-shell-jline3/README.md
and I noticed that if I press Ctrl-C followed by Return I got the following crash

java.io.IOError: java.io.InterruptedIOException: Command interrupted
at org.jline.terminal.impl.AbstractPosixTerminal.setAttributes(AbstractPosixTerminal.java:54)
at org.jline.reader.impl.LineReaderImpl.readLine(LineReaderImpl.java:715)
at picocli.shell.jline3.example.Example.main(Example.java:190)
Caused by: java.io.InterruptedIOException: Command interrupted
at org.jline.utils.ExecHelper.exec(ExecHelper.java:46)
at org.jline.terminal.impl.ExecPty.doGetConfig(ExecPty.java:175)
at org.jline.terminal.impl.ExecPty.getAttr(ExecPty.java:87)
at org.jline.terminal.impl.ExecPty.doSetAttr(ExecPty.java:93)
at org.jline.terminal.impl.AbstractPty.setAttr(AbstractPty.java:29)
at org.jline.terminal.impl.AbstractPosixTerminal.setAttributes(AbstractPosixTerminal.java:52)
... 2 more
Caused by: java.lang.InterruptedException
at java.base/java.lang.ProcessImpl.waitFor(ProcessImpl.java:552)
at org.jline.utils.ExecHelper.waitAndCapture(ExecHelper.java:66)
at org.jline.utils.ExecHelper.exec(ExecHelper.java:36)
... 7 more

Furthermore, when editing a line I press Ctrl-C and then Down-arrow/Ctrl-n a UserInterruptedException is thrown, but only the first time!

And then I modified the while-loop that starts on line 187 so it looked like this

                while (true) {
                    try {
                        systemRegistry.cleanUp();
                        line = reader.readLine(prompt, rightPrompt, (MaskingCallback) null, null);
                        systemRegistry.execute(line);
                    } catch (java.io.InterruptedIOException e) {
                      System.out.println("InterruptedIOException!");
                    } catch (UserInterruptException e) {
                      System.out.println("UserInterruptException!");
                    } catch (EndOfFileException e) {
                        return;
                    } catch (Exception e) {
                      System.out.println("Exception!");
                      systemRegistry.trace(e);
                    } catch (Throwable e) {
                      System.out.println("Throwable!");
                      e.printStackTrace();
                    }
                }

This resulted in a never-ending loop that printed lots of "Exception!" followed by lots of "Caught exception: java.lang.IllegalStateException" followed by "Exception!" followed by in another color (red?)... And the Mintty terminal ended up in a strange state where it no longer echoes what I type...

I dont know if it matters or not, but I'm currently using git bash running in the default Mintty terminal on Windows 10.

Regardless it would of course be great if this bug could be fixed, and I also think that it would be nice if pressing Ctrl-C would abort the editing of the current line (just like it does in for instance Bash). Is this possible to do?

@ermingol23
Copy link
Author

After modifying the 'while(true)' loop so it instead looked like this 'while(exceptionCounter < 1)' plus incrementing the counter in the different catch clauses plus printing the stack trace in these I get the following result

prompt>
IOError!
java.io.IOError: java.io.InterruptedIOException: Command interrupted
        at org.jline.terminal.impl.AbstractPosixTerminal.setAttributes(AbstractPosixTerminal.java:54)
        at org.jline.reader.impl.LineReaderImpl.readLine(LineReaderImpl.java:715)
        at volla.Example.main(Example.java:191)
Caused by: java.io.InterruptedIOException: Command interrupted
        at org.jline.utils.ExecHelper.exec(ExecHelper.java:46)
        at org.jline.terminal.impl.ExecPty.doGetConfig(ExecPty.java:175)
        at org.jline.terminal.impl.ExecPty.getAttr(ExecPty.java:87)
        at org.jline.terminal.impl.ExecPty.doSetAttr(ExecPty.java:93)
        at org.jline.terminal.impl.AbstractPty.setAttr(AbstractPty.java:29)
        at org.jline.terminal.impl.AbstractPosixTerminal.setAttributes(AbstractPosixTerminal.java:52)
        ... 2 more
Caused by: java.lang.InterruptedException
        at java.base/java.lang.ProcessImpl.waitFor(ProcessImpl.java:552)
        at org.jline.utils.ExecHelper.waitAndCapture(ExecHelper.java:66)
        at org.jline.utils.ExecHelper.exec(ExecHelper.java:36)
        ... 7 more
Exception!
java.lang.IllegalStateException
        at org.jline.reader.impl.LineReaderImpl.readLine(LineReaderImpl.java:538)
        at volla.Example.main(Example.java:191)
Caught exception: java.lang.IllegalStateException

How to recover from this?

@mattirn
Copy link
Collaborator

mattirn commented Oct 15, 2020

Might be git bash/Mintty problem. There are problems reported on the latest git bash versions, see #560.

I'm using git bash on Windows 10 and I'm not able to reproduce your problem using my app.

$ git --version
git version 2.24.1.windows.2

$ mintty --version
mintty 3.1.0 (x86_64-pc-msys)
© 2013/2019 Andy Koppe / Thomas Wolff
License GPLv3+: GNU GPL version 3 or later
There is no warranty, to the extent permitted by law.

@mattirn
Copy link
Collaborator

mattirn commented Nov 22, 2020

The problem has been reproduced in git bash version 2.29.2.windows.2 and mintty version 3.4.2.
IMHO the problem is to be fixed in mintty.

Pressing ctrl-c new signal handlers should be created. When you press ctrl-c INT signal handler is called, after enter accept-line widget is called and because no signal handlers are created your app will crash:

groovy-repl> Calling handler org.jline.terminal.impl.PosixSysTerminal$$Lambda$27/0x00000001000c8c40@6680f714 for signal INT
Binding: Reference[accept-line]

Running: [stty.exe,-a]
java.io.IOError: java.io.InterruptedIOException: Command interrupted
        at org.jline.terminal.impl.AbstractPosixTerminal.setAttributes(AbstractPosixTerminal.java:55)
        at org.jline.reader.impl.LineReaderImpl.readLine(LineReaderImpl.java:720)
        at org.jline.reader.impl.LineReaderImpl.readLine(LineReaderImpl.java:459)
        at org.jline.demo.Repl.main(Repl.java:322)
Caused by: java.io.InterruptedIOException: Command interrupted
        at org.jline.utils.ExecHelper.exec(ExecHelper.java:46)
        at org.jline.terminal.impl.ExecPty.doGetConfig(ExecPty.java:175)
        at org.jline.terminal.impl.ExecPty.getAttr(ExecPty.java:87)
        at org.jline.terminal.impl.ExecPty.doSetAttr(ExecPty.java:93)
        at org.jline.terminal.impl.AbstractPty.setAttr(AbstractPty.java:29)
        at org.jline.terminal.impl.AbstractPosixTerminal.setAttributes(AbstractPosixTerminal.java:52)
        ... 3 more
Caused by: java.lang.InterruptedException
        at java.base/java.lang.ProcessImpl.waitFor(ProcessImpl.java:486)
        at org.jline.utils.ExecHelper.waitAndCapture(ExecHelper.java:66)
        at org.jline.utils.ExecHelper.exec(ExecHelper.java:36)
        ... 8 more
Running all shutdown-hook tasks

If after ctrl-c you press an other key different than return the self-insert widget is called, new signal handlers are registered and your app won't crash:

groovy-repl> Calling handler org.jline.terminal.impl.PosixSysTerminal$$Lambda$27/0x00000001000c8c40@6680f714 for signal INT
Binding: Reference[self-insert]

Running: [stty.exe,-a]
Result: speed 38400 baud; rows 34; columns 154; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>;
eol2 = <undef>; swtch = ^Z; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R;
werase = ^W; lnext = ^V; discard = ^O; min = 0; time = 1;
-parenb -parodd cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr -icrnl -ixon -ixoff
-iuclc ixany imaxbel iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig -icanon -iexten -echo echoe echok -echonl -noflsh -tostop echoctl echoke
-flusho

Running: [stty.exe,icrnl,ixon,echo,icanon,iexten,min,1,time,0]
Result:
Registering signal INT with handler org.jline.terminal.impl.PosixSysTerminal$$Lambda$27/0x00000001000c8c40@3b4a1a75
Registering signal WINCH with handler SIG_DFL
Ignoring unsupported signal WINCH
Registering signal CONT with handler SIG_DFL
Ignoring unsupported signal CONT
Registering signal INT with handler org.jline.terminal.impl.PosixSysTerminal$$Lambda$27/0x00000001000c8c40@65bad087
Registering signal WINCH with handler org.jline.terminal.impl.PosixSysTerminal$$Lambda$27/0x00000001000c8c40@276cc8dc
Ignoring unsupported signal WINCH
Registering signal CONT with handler org.jline.terminal.impl.PosixSysTerminal$$Lambda$27/0x00000001000c8c40@66cd621b
Ignoring unsupported signal CONT

In linux where we do not have the problem after ctrl-c new signal handlers are registered:

groovy-repl> Calling handler org.jline.terminal.impl.PosixSysTerminal$$Lambda$30/0x0000000840072440@656922a0 for signal INT

Running: [stty,-a]
Result: speed 38400 baud; rows 24; columns 134; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>;
eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R;
werase = ^W; lnext = ^V; discard = ^O; min = 0; time = 1;
-parenb -parodd -cmspar cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr -icrnl -ixon -ixoff
-iuclc -ixany -imaxbel iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig -icanon -iexten -echo echoe echok -echonl -noflsh -xcase -tostop -echoprt
echoctl echoke -flusho -extproc

Running: [stty,icrnl,ixon,echo,icanon,iexten,min,1,time,0]
Result: 
Registering signal INT with handler org.jline.terminal.impl.PosixSysTerminal$$Lambda$30/0x0000000840072440@737db7f8
Registering signal WINCH with handler SIG_DFL
Registering signal CONT with handler SIG_DFL
Registering signal INT with handler org.jline.terminal.impl.PosixSysTerminal$$Lambda$30/0x0000000840072440@5f2de715
Registering signal WINCH with handler org.jline.terminal.impl.PosixSysTerminal$$Lambda$30/0x0000000840072440@5922d3e9
Registering signal CONT with handler org.jline.terminal.impl.PosixSysTerminal$$Lambda$30/0x0000000840072440@7d57dbb5

@mattirn
Copy link
Collaborator

mattirn commented Nov 23, 2020

Mintty Ctrl-C handling mintty/mintty#684

As a work around you can disable entirely the interrupt function of Ctrl-C entering the command stty intr undef before launching your JLine application.

@l-atanasov
Copy link

I have encountered the same issue with jline 3.20.0. It is caused by a bug in the LineReaderImpl class.
When you press ctrl+c your main thread will get interrupted. The LineReader will handle this by catching an IOError and transforming it into a UserInterruptException that is supposed to be handled by your program loop. However, before it exits the readLine() method, it has some cleaning up to do and signal handlers to invoke (if applicable). Here's what the code does:

} catch (IOError e) {
    if (e.getCause() instanceof InterruptedIOException) {
        throw new UserInterruptException(buf.toString());
    } else {
        throw e;
    }
}
finally {
    try {
        lock.lock();

        this.reading = false;

        cleanup();
        if (originalAttributes != null) {
            terminal.setAttributes(originalAttributes);
        }
        if (previousIntrHandler != null) {
            terminal.handle(Signal.INT, previousIntrHandler);
        }
        if (previousWinchHandler != null) {
            terminal.handle(Signal.WINCH, previousWinchHandler);
        }
        if (previousContHandler != null) {
            terminal.handle(Signal.CONT, previousContHandler);
        }
    } finally {
        lock.unlock();
    }
    startedReading.set(false);
}

Notice two things: first the finally block tries to set the terminal's original attributes. On certain system terminals this involves actually starting a new process and reading its output. Since our thread has been interrupted and the interrupt flag has not been reset yet, the internal call to Process.waitFor() immediately throws an exception, which is not handled and propagates to the user code. The second thing to notice is that at the very end of the finally block, an AtomicBoolean value is set to false. This is important, since this value controls how the LineReader behaves - if the value is true, you cannot call readLine() again until it has been reset.
So here's the issue - the already interrupted thread tries to start a new process and read its output, but this causes another exception to be thrown (suppressing the original exception) and the startReading flag never gets reset. Even if you handle the IOError that reaches your program loop, you can never use the same LineReader again and the terminal might be left with an inconsistent state.
I think the simple fix here would be to reset the thread interrupt flag before a UserInterruptException is thrown, although I am not sure whether this would be the intended behaviour of the library.

@alexitx
Copy link

alexitx commented Apr 20, 2022

Any update on this? I'm still getting the same problem on the latest Git snapshot 2.36.0.rc2.windows.1 with bash 4.4.23 and couldn't find much related to this in git-for-windows/git.

At the moment I'm unable to test the latest jline directly in Java, but the issue is present in Paper which uses jline 3.21.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants