7777import json
7878import token
7979import types
80+ import atexit
8081import codeop
8182import pprint
8283import signal
9394import traceback
9495import linecache
9596import selectors
97+ import threading
9698import _colorize
9799
98100from contextlib import ExitStack
@@ -2906,15 +2908,15 @@ def default(self, line):
29062908
29072909
29082910class _PdbClient :
2909- def __init__ (self , pid , server_socket , interrupt_script ):
2911+ def __init__ (self , pid , server_socket , interrupt_sock ):
29102912 self .pid = pid
29112913 self .read_buf = b""
29122914 self .signal_read = None
29132915 self .signal_write = None
29142916 self .sigint_received = False
29152917 self .raise_on_sigint = False
29162918 self .server_socket = server_socket
2917- self .interrupt_script = interrupt_script
2919+ self .interrupt_sock = interrupt_sock
29182920 self .pdb_instance = Pdb ()
29192921 self .pdb_commands = set ()
29202922 self .completion_matches = []
@@ -3136,14 +3138,11 @@ def send_interrupt(self):
31363138 # PyErr_CheckSignals is called or the eval loop regains control.
31373139 os .kill (self .pid , signal .SIGINT )
31383140 else :
3139- # On Windows, inject a remote script that calls Pdb.set_trace()
3140- # when the eval loop regains control. This cannot interrupt IO, and
3141- # also cannot interrupt statements executed at a PDB prompt.
3142- print (
3143- "\n *** Program will stop at the next bytecode instruction."
3144- " (Use 'cont' to resume)."
3145- )
3146- sys .remote_exec (self .pid , self .interrupt_script )
3141+ # On Windows, write to a socket that the PDB server listens on.
3142+ # This triggers the remote to raise a SIGINT for itself. We do this
3143+ # because Windows doesn't allow triggering SIGINT remotely.
3144+ # See https://stackoverflow.com/a/35792192 for many more details.
3145+ self .interrupt_sock .sendall (signal .SIGINT .to_bytes ())
31473146
31483147 def process_payload (self , payload ):
31493148 match payload :
@@ -3226,6 +3225,41 @@ def complete(self, text, state):
32263225 return None
32273226
32283227
3228+ def _start_interrupt_listener (host , port ):
3229+ def sigint_listener (host , port ):
3230+ with closing (
3231+ socket .create_connection ((host , port ), timeout = 5 )
3232+ ) as sock :
3233+ # Check if the interpreter is finalizing every quarter of a second.
3234+ # Clean up and exit if so.
3235+ sock .settimeout (0.25 )
3236+ sock .shutdown (socket .SHUT_WR )
3237+ while not shut_down .is_set ():
3238+ try :
3239+ data = sock .recv (1024 )
3240+ except socket .timeout :
3241+ continue
3242+ if data == b"" :
3243+ return # EOF
3244+ signal .raise_signal (signal .SIGINT )
3245+
3246+ def stop_thread ():
3247+ shut_down .set ()
3248+ thread .join ()
3249+
3250+ # Use a daemon thread so that we don't detach until after all non-daemon
3251+ # threads are done. Use an atexit handler to stop gracefully at that point,
3252+ # so that our thread is stopped before the interpreter is torn down.
3253+ shut_down = threading .Event ()
3254+ thread = threading .Thread (
3255+ target = sigint_listener ,
3256+ args = (host , port ),
3257+ daemon = True ,
3258+ )
3259+ atexit .register (stop_thread )
3260+ thread .start ()
3261+
3262+
32293263def _connect (host , port , frame , commands , version ):
32303264 with closing (socket .create_connection ((host , port ))) as conn :
32313265 sockfile = conn .makefile ("rwb" )
@@ -3255,9 +3289,14 @@ def attach(pid, commands=()):
32553289 server = stack .enter_context (
32563290 closing (socket .create_server (("localhost" , 0 )))
32573291 )
3258-
32593292 port = server .getsockname ()[1 ]
32603293
3294+ if sys .platform == "win32" :
3295+ commands = [
3296+ f"__import__('pdb')._start_interrupt_listener('localhost', { port } )" ,
3297+ * commands ,
3298+ ]
3299+
32613300 connect_script = stack .enter_context (
32623301 tempfile .NamedTemporaryFile ("w" , delete_on_close = False )
32633302 )
@@ -3281,20 +3320,16 @@ def attach(pid, commands=()):
32813320
32823321 # TODO Add a timeout? Or don't bother since the user can ^C?
32833322 client_sock , _ = server .accept ()
3284-
32853323 stack .enter_context (closing (client_sock ))
32863324
3287- interrupt_script = stack .enter_context (
3288- tempfile .NamedTemporaryFile ("w" , delete_on_close = False )
3289- )
3290- interrupt_script .write (
3291- 'import pdb, sys\n '
3292- 'if inst := pdb.Pdb._last_pdb_instance:\n '
3293- ' inst.set_trace(sys._getframe(1))\n '
3294- )
3295- interrupt_script .close ()
3325+ if sys .platform == "win32" :
3326+ interrupt_sock , _ = server .accept ()
3327+ stack .enter_context (closing (interrupt_sock ))
3328+ interrupt_sock .setblocking (False )
3329+ else :
3330+ interrupt_sock = None
32963331
3297- _PdbClient (pid , client_sock , interrupt_script . name ).cmdloop ()
3332+ _PdbClient (pid , client_sock , interrupt_sock ).cmdloop ()
32983333
32993334
33003335# Post-Mortem interface
0 commit comments