@@ -2909,6 +2909,8 @@ def __init__(self, pid, server_socket, interrupt_script):
29092909 self .read_buf = b""
29102910 self .signal_read = None
29112911 self .signal_write = None
2912+ self .sigint_received = False
2913+ self .raise_on_sigint = False
29122914 self .server_socket = server_socket
29132915 self .interrupt_script = interrupt_script
29142916 self .pdb_instance = Pdb ()
@@ -2961,42 +2963,39 @@ def _send(self, **kwargs):
29612963 self .write_failed = True
29622964
29632965 def _readline (self ):
2966+ if self .sigint_received :
2967+ # There's a pending unhandled SIGINT. Handle it now.
2968+ self .sigint_received = False
2969+ raise KeyboardInterrupt
2970+
29642971 # Wait for either a SIGINT or a line or EOF from the PDB server.
29652972 selector = selectors .DefaultSelector ()
29662973 selector .register (self .signal_read , selectors .EVENT_READ )
29672974 selector .register (self .server_socket , selectors .EVENT_READ )
29682975
2969- old_wakeup_fd = signal .set_wakeup_fd (
2970- self .signal_write .fileno (),
2971- warn_on_full_buffer = False ,
2972- )
2973-
2974- got_sigint = False
2975- def sigint_handler (* args , ** kwargs ):
2976- nonlocal got_sigint
2977- got_sigint = True
2976+ while b"\n " not in self .read_buf :
2977+ for key , _ in selector .select ():
2978+ if key .fileobj == self .signal_read :
2979+ self .signal_read .recv (1024 )
2980+ if self .sigint_received :
2981+ # If not, we're reading wakeup events for sigints that
2982+ # we've previously handled, and can ignore them.
2983+ self .sigint_received = False
2984+ raise KeyboardInterrupt
2985+ elif key .fileobj == self .server_socket :
2986+ data = self .server_socket .recv (16 * 1024 )
2987+ self .read_buf += data
2988+ if not data and b"\n " not in self .read_buf :
2989+ # EOF without a full final line. Drop the partial line.
2990+ self .read_buf = b""
2991+ return b""
29782992
2979- old_handler = signal .signal (signal .SIGINT , sigint_handler )
2980- try :
2981- while b"\n " not in self .read_buf :
2982- for key , _ in selector .select ():
2983- if key .fileobj == self .signal_read :
2984- self .signal_read .recv (1024 )
2985- if got_sigint :
2986- raise KeyboardInterrupt
2987- elif key .fileobj == self .server_socket :
2988- self .read_buf += self .server_socket .recv (16 * 1024 )
2989- if not self .read_buf :
2990- return b""
2991-
2992- ret , sep , self .read_buf = self .read_buf .partition (b"\n " )
2993- return ret + sep
2994- finally :
2995- signal .set_wakeup_fd (old_wakeup_fd )
2996- signal .signal (signal .SIGINT , old_handler )
2993+ ret , sep , self .read_buf = self .read_buf .partition (b"\n " )
2994+ return ret + sep
29972995
29982996 def read_command (self , prompt ):
2999- reply = input (prompt )
2997+ with self ._sigint_raises_keyboard_interrupt ():
2998+ reply = input (prompt )
30002999
30013000 if self .state == "dumb" :
30023001 # No logic applied whatsoever, just pass the raw reply back.
@@ -3022,7 +3021,8 @@ def read_command(self, prompt):
30223021 # Otherwise, valid first line of a multi-line statement
30233022 continue_prompt = "..." .ljust (len (prompt ))
30243023 while codeop .compile_command (reply , "<stdin>" , "single" ) is None :
3025- reply += "\n " + input (continue_prompt )
3024+ with self ._sigint_raises_keyboard_interrupt ():
3025+ reply += "\n " + input (continue_prompt )
30263026
30273027 return prefix + reply
30283028
@@ -3047,65 +3047,71 @@ def readline_completion(self, completer):
30473047 finally :
30483048 readline .set_completer (old_completer )
30493049
3050- if not hasattr (signal , "pthread_sigmask" ):
3051- # On Windows, we must drop signals arriving while we're ignoring them.
3052- @contextmanager
3053- def _block_sigint (self ):
3054- old_handler = signal .signal (signal .SIGINT , signal .SIG_IGN )
3055- try :
3056- yield
3057- finally :
3058- signal .signal (signal .SIGINT , old_handler )
3050+ @contextmanager
3051+ def _sigint_handler (self ):
3052+ # Signal handling strategy:
3053+ # - When we call input() we want a SIGINT to raise KeyboardInterrupt
3054+ # - Otherwise we want to write to the wakeup FD and set a flag.
3055+ # We'll break out of select() when the wakeup FD is written to,
3056+ # and we'll check the flag whenever we're about to accept input.
3057+ def handler (signum , frame ):
3058+ self .sigint_received = True
3059+ if self .raise_on_sigint :
3060+ # One-shot; don't raise again until the flag is set again.
3061+ self .raise_on_sigint = False
3062+ self .sigint_received = False
3063+ raise KeyboardInterrupt
3064+
3065+ sentinel = object ()
3066+ old_handler = sentinel
3067+ old_wakeup_fd = sentinel
30593068
3060- @contextmanager
3061- def _handle_sigint (self , handler ):
3062- old_handler = signal .signal (signal .SIGINT , handler )
3063- try :
3064- yield
3065- finally :
3066- signal .signal (signal .SIGINT , old_handler )
3067- else :
3068- # On Unix, we can save them to be processed later.
3069- @contextmanager
3070- def _block_sigint (self ):
3071- signal .pthread_sigmask (signal .SIG_BLOCK , {signal .SIGINT })
3072- try :
3073- yield
3074- finally :
3075- signal .pthread_sigmask (signal .SIG_UNBLOCK , {signal .SIGINT })
3069+ self .signal_read , self .signal_write = socket .socketpair ()
3070+ with (closing (self .signal_read ), closing (self .signal_write )):
3071+ self .signal_read .setblocking (False )
3072+ self .signal_write .setblocking (False )
30763073
3077- @contextmanager
3078- def _handle_sigint (self , handler ):
3079- old_handler = signal .signal (signal .SIGINT , handler )
30803074 try :
3081- signal .pthread_sigmask (signal .SIG_UNBLOCK , {signal .SIGINT })
3082- yield
3075+ old_handler = signal .signal (signal .SIGINT , handler )
3076+
3077+ try :
3078+ old_wakeup_fd = signal .set_wakeup_fd (
3079+ self .signal_write .fileno (),
3080+ warn_on_full_buffer = False ,
3081+ )
3082+ yield
3083+ finally :
3084+ # Restore the old wakeup fd if we installed a new one
3085+ if old_wakeup_fd is not sentinel :
3086+ signal .set_wakeup_fd (old_wakeup_fd )
3087+ self .signal_read = self .signal_write = None
30833088 finally :
3084- signal .pthread_sigmask (signal .SIG_BLOCK , {signal .SIGINT })
3085- signal .signal (signal .SIGINT , old_handler )
3089+ if old_handler is not sentinel :
3090+ # Restore the old handler if we installed a new one
3091+ signal .signal (signal .SIGINT , old_handler )
30863092
30873093 @contextmanager
3088- def _signal_socket_pair (self ):
3089- self .signal_read , self .signal_write = socket .socketpair ()
3094+ def _sigint_raises_keyboard_interrupt (self ):
3095+ if self .sigint_received :
3096+ # There's a pending unhandled SIGINT. Handle it now.
3097+ self .sigint_received = False
3098+ raise KeyboardInterrupt
3099+
30903100 try :
3091- with (closing (self .signal_read ), closing (self .signal_write )):
3092- self .signal_read .setblocking (False )
3093- self .signal_write .setblocking (False )
3094- yield
3101+ self .raise_on_sigint = True
3102+ yield
30953103 finally :
3096- self .signal_read = self . signal_write = None
3104+ self .raise_on_sigint = False
30973105
30983106 def cmdloop (self ):
30993107 with (
3100- self ._block_sigint (),
3101- self ._signal_socket_pair (),
3108+ self ._sigint_handler (),
31023109 self .readline_completion (self .complete ),
31033110 ):
31043111 while not self .write_failed :
31053112 try :
3106- with self ._handle_sigint (signal .default_int_handler ):
3107- if not (payload_bytes := self ._readline ()):
3108- break
3113+ if not (payload_bytes := self ._readline ()):
3114+ break
31093115 except KeyboardInterrupt :
31103116 self .send_interrupt ()
31113117 continue
@@ -3161,8 +3167,7 @@ def process_payload(self, payload):
31613167 def prompt_for_reply (self , prompt ):
31623168 while True :
31633169 try :
3164- with self ._handle_sigint (signal .default_int_handler ):
3165- payload = {"reply" : self .read_command (prompt )}
3170+ payload = {"reply" : self .read_command (prompt )}
31663171 except EOFError :
31673172 payload = {"signal" : "EOF" }
31683173 except KeyboardInterrupt :
@@ -3202,8 +3207,7 @@ def complete(self, text, state):
32023207 if self .write_failed :
32033208 return None
32043209
3205- with self ._handle_sigint (signal .default_int_handler ):
3206- payload = self ._readline ()
3210+ payload = self ._readline ()
32073211 if not payload :
32083212 return None
32093213
0 commit comments