Skip to content

input() is blocking exceptions at some phase during execution #112585

Open

Description

@wh-timme

Bug report

Bug description:

The following loop will soon stuck at some value, due to some phase of built-in input() itself is blocking exceptions and will not pass the exception to upper scope until an ENTER is hit on keyboard.

import os, signal, threading

cnt = [0]

def interrupter(cnt):
	local_cnt = 0
	while 1:
		if local_cnt != cnt[0]:
			local_cnt = cnt[0]
			os.kill(os.getpid(), signal.SIGINT)

threading.Thread(target=interrupter, args=(cnt,), daemon=True).start()

while 1:
	cnt[0] += 1
	try:
		input(">")
	except KeyboardInterrupt:
		print(cnt[0])

CPython versions tested on:

3.8

Operating systems tested on:

macOS

Activity

  1. TuringKi commented on Dec 29, 2023

    @TuringKi

    I have come up with a solution, but I am not sure if it is robust enough:

    diff --git a/Parser/myreadline.c b/Parser/myreadline.c
    index 1825665..3d6abe5 100644
    --- a/Parser/myreadline.c
    +++ b/Parser/myreadline.c
    @@ -12,6 +12,7 @@
     #include "Python.h"
     #include "pycore_fileutils.h"     // _Py_BEGIN_SUPPRESS_IPH
     #include "pycore_pystate.h"   // _PyThreadState_GET()
    +#include <sys/types.h>
     #ifdef MS_WINDOWS
     #  ifndef WIN32_LEAN_AND_MEAN
     #    define WIN32_LEAN_AND_MEAN
    @@ -331,8 +332,9 @@ PyOS_StdioReadline(FILE *sys_stdin, FILE *sys_stdout, const char *prompt)
                 return NULL;
             }
             p = pr;
    -        int err = my_fgets(tstate, p + n, (int)incr, sys_stdin);
    -        if (err == 1) {
    +        int err = _Py_read(sys_stdin->_fileno, p + n, incr);
    +        //int err = my_fgets(tstate, p + n, (int)incr, sys_stdin);
    +        if (err == -1) {
                 // Interrupt
                 PyMem_RawFree(p);
                 return NULL;
    @@ -393,8 +395,8 @@ PyOS_Readline(FILE *sys_stdin, FILE *sys_stdout, const char *prompt)
         }
     
         _PyOS_ReadlineTState = tstate;
    -    Py_BEGIN_ALLOW_THREADS
    -    PyThread_acquire_lock(_PyOS_ReadlineLock, 1);
    +    //Py_BEGIN_ALLOW_THREADS
    +    //PyThread_acquire_lock(_PyOS_ReadlineLock, 1);
     
         /* This is needed to handle the unlikely case that the
          * interpreter is in interactive mode *and* stdin/out are not
    @@ -418,9 +420,9 @@ PyOS_Readline(FILE *sys_stdin, FILE *sys_stdout, const char *prompt)
         else {
             rv = (*PyOS_ReadlineFunctionPointer)(sys_stdin, sys_stdout, prompt);
         }
    -    Py_END_ALLOW_THREADS
    +    //Py_END_ALLOW_THREADS
     
    -    PyThread_release_lock(_PyOS_ReadlineLock);
    +    //PyThread_release_lock(_PyOS_ReadlineLock);
     
         _PyOS_ReadlineTState = NULL;
  2. duaneg commented on May 25, 2025

    @duaneg
    Contributor

    This is a very interesting test case.

    Unfortunately, the short answer is that using SIGINT like this is just inherently racy and unsafe. The main thread may receive it at any point after cnt[0] += 1. If that happens to be before the main thread calls fgets and blocks it will not be interrupted by the signal, and there we are.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    interpreter-core(Objects, Python, Grammar, and Parser dirs)type-bugAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions