PyDev.Debugger
PyDev.Debugger copied to clipboard
Debugging Subinterpreters
We are using jep and Subinterpreters to invoke Python code from within a Java process. Ideally we would like to debug the execution of such code with PyDev's remote debugger.
Using the following code example we are able to use a remote debugger.
JepConfig config = new JepConfig();
config.addIncludePaths("path/to/pydevd");
try(Interpreter interp = new SubInterpreter(config)){
// Setup debugger
interp.exec("""
import pydevd
pydevd.settrace(suspend=False)
""");
// Run code that we want to debug
interp.runScript("some_file.py");
// Stop debugger
interp.exec("""
pydevd.stoptrace()
""");
}
However, after calling pydevd.stoptrace()
it seems that there still is a daemon thread alive which then causes an error when the Subinterpreter is closed, as it requires that no other thread (daemon or not) is still running:
Fatal Python error: Py_EndInterpreter: not the last thread
Python runtime state: initialized
Thread 0x00003344 (most recent call first):
File "...\Eclipse_2023-12\eclipse\plugins\org.python.pydev.core_12.0.0.202402010911\pysrc\_pydevd_bundle\pydevd_comm.py", line 204 in _read_line
File "...\Eclipse_2023-12\eclipse\plugins\org.python.pydev.core_12.0.0.202402010911\pysrc\_pydevd_bundle\pydevd_comm.py", line 222 in _on_run
File "...\Eclipse_2023-12\eclipse\plugins\org.python.pydev.core_12.0.0.202402010911\pysrc\_pydevd_bundle\pydevd_daemon_thread.py", line 50 in run
File "...\Python\env\lib\threading.py", line 980 in _bootstrap_inner
File "...\Python\env\lib\threading.py", line 937 in _bootstrap
Current thread 0x00000784 (most recent call first):
<no Python frame>
From the stacktrace we can see that the ReaderThread
is still running. From the comment on do_kill_pydev_thread
it seems that this is intentional.
I guess that keeping the ReaderThread alive is reasonable when debugging a regular Interpreter, but not when running within a Subinterpreter. Our current workaround is to manually shutdown the ReaderThread by essentially running the code that is commented out in it's ReaderThread.do_kill_pydev_thread
:
// Stop debugger hard
interp.exec("""
dbg = pydevd.get_global_debugger()
pydevd.stoptrace()
for thread in list(dbg.created_pydb_daemon_threads):
if not hasattr(thread, "sock"):
continue
try:
import socket
thread.sock.shutdown(socket.SHUT_RD)
except BaseException as e:
print(e)
try:
thread.sock.close()
except BaseException as e:
print(e)
thread.join()
""");
Would it be possible to add an argument to the stoptrace
function which makes sure that all pydevd threads are actually killed?
Are you aware of any quirks that one might encounter when debugging within a Subinterpreter?
I guess that this issue might also become more relevant with PEP-734
Hi @FlorianKroiss I haven't really done much with subinterpreters so far, so, I don't really know what'd be the repercussions.
Now, related to the reader thread being alive, what the comment says is that there should be kind of a ping-pong, where the writer thread is closed, then the clients reader thread becomes closed which in turn should close its writer which should then automatically stop the reader thread.
I guess that in this case things happen too fast for that ping-pong to happen (the subinterpreter probably gets shutdown before all of that happens).
In that case, I think you could call:
py_db = pydevd.get_global_debugger()
pydevd.stoptrace()
py_db.dispose_and_kill_all_pydevd_threads()
Hi @fabioz, thanks for the quick reply.
Unfortunately, your suggestion did not solve the problem at hand. From what I understand, dispose_and_kill_all_pydevd_threads()
is already called within pydevd.stoptrace()
, so calling it again will just cause it to wait another 0.5 seconds for all threads to finish.
It seems to me that the ping-pong that you described is not happening, i.e., the ReaderThread is still running, even after a longer wait time. Trying to explicitly join on the ReaderThread hangs indefinitely, so I don't think it's a timing problem:
py_db = pydevd.get_global_debugger()
pydevd.stoptrace()
for thread in list(py_db.created_pydb_daemon_threads):
thread.join()
I'm using PyDev from within Eclipse to start the Debug Server. When I explicitly stop the running interpreter from Eclipse, using 'Terminate (Ctrl+F2)', the ReaderThread properly closes, but when I just resume the Python program after a breakpoint, the ReaderThread keeps running.
I would be grateful if you had any more ideas.
Humm, if you call stoptrace()
and the client is not closing the socket on its writer side it may be a bug in the client side (in this case in the java (Eclipse) side on PyDev).
I think you can use the workaround you're suggesting for now (the only downside is that you could still have some upcoming message to do something which wouldn't be received in this case... usually not a problem, the main issue is that some output message would not be reaching the client and you'd loose that message).