Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Debugging Subinterpreters #273

Open
FlorianKroiss opened this issue Feb 12, 2024 · 3 comments
Open

Debugging Subinterpreters #273

FlorianKroiss opened this issue Feb 12, 2024 · 3 comments

Comments

@FlorianKroiss
Copy link

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

@fabioz
Copy link
Owner

fabioz commented Feb 12, 2024

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()

@FlorianKroiss
Copy link
Author

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.

@fabioz
Copy link
Owner

fabioz commented Feb 12, 2024

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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants