Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ctrl+C sends EOFError once after cancelling process [duplicate]

I'm making a very basic command line utility using python, and I'm trying to set it up such that the user has the ability to interrupt any running operation during execution with Ctrl+C, and exit the program by sending an EOF with Ctrl+Z. However, I've been fighting with this frustrating issue for the last day where after cancelling a running operation with a KeyboardInterrupt, pressing Ctrl+C a second time sends an EOFError instead of a KeyboardInterrupt, which causes the program to exit. Hitting Ctrl+C subsequent times sends KeyboardInterrupt's as usual, except until I input any command or an empty line, where an additional KeyboardInterrupt is sent instead of the input I give. After doing so, hitting Ctrl+C again will send an EOFError again, and continues from there. Here's a minimal example of code demonstrating my issue;

import time

def parse(inp):
    time.sleep(1)
    print(inp)
    if inp == 'e':
        return 'exit'

while True:
    try:
        user_in = input("> ").strip()
    except (KeyboardInterrupt, Exception) as e:
        print(e.__class__.__name__)
        continue

    if not user_in:
        continue

    try:
        if parse(user_in) == 'exit':
            break
    except KeyboardInterrupt:
        print("Cancelled")

And here's some sample output of my code;

>
>
>
> KeyboardInterrupt
> KeyboardInterrupt
> KeyboardInterrupt
> KeyboardInterrupt
> KeyboardInterrupt
> KeyboardInterrupt
>
>
>
> ^Z
EOFError
> ^Z
EOFError
> ^Z
EOFError
> KeyboardInterrupt
> KeyboardInterrupt
> KeyboardInterrupt
>
>
> ^Z
EOFError
> KeyboardInterrupt
>
>

As you can see, when hitting Ctrl+C, Ctrl+Z, or a blank line, the prompt responds as you would expect with each error appropriately. However, if I run a command and try cancelling it during execution by hitting Ctrl+C;

> test
Cancelled
>
>
>
> EOFError
> KeyboardInterrupt
> KeyboardInterrupt
> KeyboardInterrupt
> KeyboardInterrupt
>
KeyboardInterrupt
>
>
> EOFError
> KeyboardInterrupt
> KeyboardInterrupt
> KeyboardInterrupt
>
KeyboardInterrupt
>
>
>

I only hit Ctrl+C and Enter in the above example; I first hit enter to send several blank lines, then hit Ctrl+C, which sends an EOFError at first. Hitting Ctrl+C mutliple times afterwards then sends KeyboardInterrupt's correctly. Afterwards, sending a blank line instead then sends a KeyboardInterrupt, and subsequent blank lines entered are received normally. This functionality is repeated onward from the program's execution there.

Why is this happening, and how can I fix it?

like image 309
Maurdekye Avatar asked Aug 30 '19 15:08

Maurdekye


People also ask

How do you use Ctrl-C in Python?

Python allows us to set up signal -handlers so when a particular signal arrives to our program we can have a behavior different from the default. For example when you run a program on the terminal and press Ctrl-C the default behavior is to quit the program.

How does control c work in Linux?

While in a command line such as MS-DOS, Linux, and Unix, Ctrl + C is used to send a SIGINT signal, which cancels or terminates the currently-running program. For example, if a script or program is frozen or stuck in an infinite loop, pressing Ctrl + C cancels that command and returns you to the command line.

What is control C in Unix?

When you type CTRL-C, you tell the shell to send the INT (for "interrupt") signal to the current job; [CTRL-Z] sends TSTP (on most systems, for "terminal stop"). You can also send the current job a QUIT signal by typing CTRL-\ (control-backslash); this is sort of like a "stronger" version of [CTRL-C].


1 Answers

So. You've found a pretty old Python bug. It's to do with the async nature of keyboard interrupts, AND how if you send a KeyboardInterrupt to Python while hanging, and it doesn't respond to the interrupt, the second interrupt will raise the even stronger EOFError. However, it seems, that these two collide, if you have an async KeyboardInterrupt caught followed by an input with a second KeyboardInterrupt, there will some stuff left in some buffer, which triggered EOFError.

I know this isn't a great explanation, nor is it very clear. However, it allows for a pretty simple fix. Let the buffer to catch up with all the async interrupts, and than start waiting for an input:

import time

def parse(inp):
    time.sleep(1)
    print(inp)
    if inp == 'e':
        return 'exit'

while True:
    try:
        user_in = input("> ").strip()
    except (KeyboardInterrupt, Exception) as e:
        print(e.__class__.__name__)
        continue

    if not user_in:
        continue

    try:
        if parse(user_in) == 'exit':
            break
    except KeyboardInterrupt:
        print("Cancelled")
        time.sleep(0.1)    # This is the only line that's added

Now doing the same actions you did produces this:

> test
Cancelled
>
>
>
>
> KeyboardInterrupt
> KeyboardInterrupt
> KeyboardInterrupt
> KeyboardInterrupt
> KeyboardInterrupt
>
> KeyboardInterrupt
>
>
> KeyboardInterrupt
> KeyboardInterrupt
>
like image 72
tituszban Avatar answered Sep 18 '22 21:09

tituszban