You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug] Investigate PyGILState_Release issue for client #300
I suppose that root cause is the python does not call destructor immediately, and as written in docs A Client does not have an explicit "close". When cpython is finalizing code, it acquires GIL and executes destructor of tokio::runtime::Runtime which is expecting while all tasks have finished. At the same time tokio::runtime got ping message and run python code to handle message, which is trying to acquire GIL as well.
It would be better if there was an explicit way to close client.
I am struggling to replicate. There is a slight bug where run accepts self, but fixing that, I didn't see it. Can you confirm your OS, temporalio SDK version, and Python version?
It would be better if there was an explicit way to close client.
I don't think this would solve it if the issue is Tokio runtime cleanup. A better solution may be like what hydro-project/hydro#699 did (linked from hydro-project/hydro#619 who linked here as having a common issue). Specifically using something like atexit.register to register runtime drop.
But ideally we could get a reliable replication first so we can confirm it is fixed once we make the change. I am struggling to get this currently.
My mistake. I have forgot to mention that this bug appears not every run, but rarely. So you have to rerun script several times.
I used gdb in the following manner:
$ gdb python
(gdb) set pagination off
(gdb) break _exit
(gdb) command> run
> end
(gdb) run script.py
And wait for several minutes.
Can you confirm your OS, temporalio SDK version, and Python version?
Specifically using something like atexit.register to register runtime drop
Please, take into account, that client destructor might be called even during execution process (not only at exit), when there is no references to the object. I would recommend to use context manager as appropriate solution for python.
that client destructor might be called even during execution process (not only at exit), when there is no references to the object.
This is not just about client destructor, this is about when it is dropped Rust side. If the client is in use in any way (Python or Rust), it is not closed/disconnected. We can't just use context manager because it is passed around (e.g. to a worker) and may only retain Rust references. I think this error is specific to the Python-Rust bridge and PyO3.
Update here, I am still struggling to replicate. I suspect this is actually a Tokio runtime lifetime issue and not specific to the client, but we are still trying to replicate reliably (running the script under gdb for me, at least on 3.10, just continually works forever).
Calling this function from a thread when the runtime is finalizing will terminate the thread, even if the thread was not created by Python. You can use Py_IsFinalizing() or sys.is_finalizing() to check if the interpreter is in process of being finalized before calling this function to avoid unwanted termination.
Unfortunately Py_IsFinalizing is 3.13+ and ideally we don't want the overhead of invoking sys.is_finalizing, but we may have to (and it's not that bad considering pyo3-asyncio already uses things like call_soon_threadsafe).
Unfortunately https://github.com/awestlake87/pyo3-asyncio is mostly abandoned, https://github.com/PyO3/pyo3-async-runtimes is just a simple fork, and built-in PyO3 asyncio with Rust futures is still being worked on. So we probably would just end up vendoring what we haven't already from pyo3-asyncio (we already copy and slightly alter some code to use the current Tokio runtime instead of one big static global).
Assuming our guess at the issue is correct, fixing this is daunting because it probably requires rewriting/vendoring a Rust library (pyo3-asyncio). Can you confirm this only happens on process/event-loop shutdown? The best way to avoid this is to not attempt to gracefully exit the program while the client is in use somewhere.
ghost added a commit that references this issue on Mar 24, 2026
A related case, in case it helps: a hang (not a crash) during interpreter finalization, after a short-lived client run has completed.
temporalio 1.34.0, CPython 3.14.8 (GIL build), Linux x86-64 container (python:3.14.8-trixie), Temporal server 1.32.1 (PostgreSQL 18).
The program connects a Client, starts one workflow, awaits its result, fetches its history, and returns from asyncio.run(). It prints its final line, then never exits. This is rare and seen only under heavy CI load; it does not reproduce on an idle machine (0 in 600 runs).
A timeout -s ABRT plus PYTHONFAULTHANDLER=1 gives the stack below: the main thread is in GC at finalization, with no Python frame, blocked in a syscall (futex) inside temporal_sdk_bridge.abi3.so, called from _Py_Dealloc.
Fatal Python error: Aborted
Current thread 0x00007f6947343380 [python] (most recent call first):
Garbage-collecting
<no Python frame>
Current thread's C stack trace (most recent call first):
Binary file "/usr/local/bin/../lib/libpython3.14.so.1.0", at _Py_DumpStack+0x4c [0x7f6947796750]
Binary file "/usr/local/bin/../lib/libpython3.14.so.1.0", at +0x170980 [0x7f694779f980]
Binary file "/lib/x86_64-linux-gnu/libc.so.6", at +0x3fd70 [0x7f6947473d70]
Binary file "/lib/x86_64-linux-gnu/libc.so.6", at syscall+0x19 [0x7f69475424f9]
Binary file "/tmp/v/lib/python3.14/site-packages/temporalio/bridge/temporal_sdk_bridge.abi3.so", at +0x610709 [0x7f6943bf4709]
Binary file "/tmp/v/lib/python3.14/site-packages/temporalio/bridge/temporal_sdk_bridge.abi3.so", at +0x1709150 [0x7f6944ced150]
Binary file "/tmp/v/lib/python3.14/site-packages/temporalio/bridge/temporal_sdk_bridge.abi3.so", at +0x1708356 [0x7f6944cec356]
Binary file "/tmp/v/lib/python3.14/site-packages/temporalio/bridge/temporal_sdk_bridge.abi3.so", at +0xf3e38d [0x7f694452238d]
Binary file "/tmp/v/lib/python3.14/site-packages/temporalio/bridge/temporal_sdk_bridge.abi3.so", at +0xf3e113 [0x7f6944522113]
Binary file "/tmp/v/lib/python3.14/site-packages/temporalio/bridge/temporal_sdk_bridge.abi3.so", at +0xf3df50 [0x7f6944521f50]
Binary file "/tmp/v/lib/python3.14/site-packages/temporalio/bridge/temporal_sdk_bridge.abi3.so", at +0x9c8e21 [0x7f6943face21]
Binary file "/tmp/v/lib/python3.14/site-packages/temporalio/bridge/temporal_sdk_bridge.abi3.so", at +0x10822e1 [0x7f69446662e1]
Binary file "/tmp/v/lib/python3.14/site-packages/temporalio/bridge/temporal_sdk_bridge.abi3.so", at +0xd2e0ef [0x7f69443120ef]
Binary file "/tmp/v/lib/python3.14/site-packages/temporalio/bridge/temporal_sdk_bridge.abi3.so", at +0xd2e380 [0x7f6944312380]
Binary file "/usr/local/bin/../lib/libpython3.14.so.1.0", at _Py_Dealloc+0x81 [0x7f69477be6c1]
Binary file "/usr/local/bin/../lib/libpython3.14.so.1.0", at PyObject_ClearManagedDict+0x93 [0x7f69478270a3]
Binary file "/usr/local/bin/../lib/libpython3.14.so.1.0", at +0x1f7a2a [0x7f6947826a2a]
Binary file "/usr/local/bin/../lib/libpython3.14.so.1.0", at _Py_Dealloc+0x81 [0x7f69477be6c1]
Binary file "/usr/local/bin/../lib/libpython3.14.so.1.0", at PyObject_ClearManagedDict+0x93 [0x7f69478270a3]
Binary file "/usr/local/bin/../lib/libpython3.14.so.1.0", at +0x1f7a2a [0x7f6947826a2a]
Binary file "/usr/local/bin/../lib/libpython3.14.so.1.0", at _Py_Dealloc+0x81 [0x7f69477be6c1]
Binary file "/usr/local/bin/../lib/libpython3.14.so.1.0", at PyObject_ClearManagedDict+0x93 [0x7f69478270a3]
Binary file "/usr/local/bin/../lib/libpython3.14.so.1.0", at +0x1f7a2a [0x7f6947826a2a]
Binary file "/usr/local/bin/../lib/libpython3.14.so.1.0", at _Py_Dealloc+0x81 [0x7f69477be6c1]
Binary file "/usr/local/bin/../lib/libpython3.14.so.1.0", at +0x19c23b [0x7f69477cb23b]
Python 3.14 documents that foreign threads attaching during finalization are blocked (C-API runtime finalization), which may make a bridge drop that waits on a Tokio thread hang instead of crash. We work around it with os._exit(0) after a verified success in our short-lived CLIs.
Activity
TiunovNN commentedon Sep 25, 2024
Hi @cretz . I've came across the similar problem. Here is a simple code to reproduce bug:
log from gdb:
Full stack trace of failed thread: https://gist.github.com/TiunovNN/84c253d62f03f11270a49facee6c7b52
I suppose that root cause is the python does not call destructor immediately, and as written in docs
A Client does not have an explicit "close". When cpython is finalizing code, it acquires GIL and executes destructor of tokio::runtime::Runtime which is expecting while all tasks have finished. At the same time tokio::runtime got ping message and run python code to handle message, which is trying to acquire GIL as well.It would be better if there was an explicit way to close client.
cretz commentedon Sep 25, 2024
I am struggling to replicate. There is a slight bug where
runacceptsself, but fixing that, I didn't see it. Can you confirm your OS,temporalioSDK version, and Python version?I don't think this would solve it if the issue is Tokio runtime cleanup. A better solution may be like what hydro-project/hydro#699 did (linked from hydro-project/hydro#619 who linked here as having a common issue). Specifically using something like
atexit.registerto register runtime drop.But ideally we could get a reliable replication first so we can confirm it is fixed once we make the change. I am struggling to get this currently.
TiunovNN commentedon Sep 25, 2024
My mistake. I have forgot to mention that this bug appears not every run, but rarely. So you have to rerun script several times.
I used gdb in the following manner:
And wait for several minutes.
The bug appears on the following sets:
cretz commentedon Sep 25, 2024
Thanks! We will increase the priority on this.
TiunovNN commentedon Oct 2, 2024
@cretz
Please, take into account, that client destructor might be called even during execution process (not only at exit), when there is no references to the object. I would recommend to use context manager as appropriate solution for python.
cretz commentedon Oct 2, 2024
This is not just about client destructor, this is about when it is dropped Rust side. If the client is in use in any way (Python or Rust), it is not closed/disconnected. We can't just use context manager because it is passed around (e.g. to a worker) and may only retain Rust references. I think this error is specific to the Python-Rust bridge and PyO3.
cretz commentedon Oct 15, 2024
Update here, I am still struggling to replicate. I suspect this is actually a Tokio runtime lifetime issue and not specific to the client, but we are still trying to replicate reliably (running the script under gdb for me, at least on 3.10, just continually works forever).
cretz commentedon Oct 15, 2024
I can replicate a similar issue changing the code to just run a task in the background and then completing the app before waiting on complete, e.g.:
This will give:
My current guess is this is because pyo3-asyncio assumes
Python::with_gilwill succeed, but that delegates toPyGILState_Ensurewhich documents at https://docs.python.org/3/c-api/init.html#c.PyGILState_Ensure:Unfortunately
Py_IsFinalizingis 3.13+ and ideally we don't want the overhead of invokingsys.is_finalizing, but we may have to (and it's not that bad considering pyo3-asyncio already uses things likecall_soon_threadsafe).Unfortunately https://github.com/awestlake87/pyo3-asyncio is mostly abandoned, https://github.com/PyO3/pyo3-async-runtimes is just a simple fork, and built-in PyO3 asyncio with Rust futures is still being worked on. So we probably would just end up vendoring what we haven't already from pyo3-asyncio (we already copy and slightly alter some code to use the current Tokio runtime instead of one big static global).
i3Cheese commentedon Nov 20, 2024
@cretz
Hello! I encountered the same issue, and I would like to help fix it. Where do I start?
cretz commentedon Nov 20, 2024
Assuming our guess at the issue is correct, fixing this is daunting because it probably requires rewriting/vendoring a Rust library (pyo3-asyncio). Can you confirm this only happens on process/event-loop shutdown? The best way to avoid this is to not attempt to gracefully exit the program while the client is in use somewhere.
fix(temporal): force gc after asyncio.run to prevent GIL crash on exit
fix(cli): mission-* commands exit without interpreter finalization; R…
fix(cli): exit every Temporal command without interpreter finalization
daubercyanthony-dotcom commentedon Oct 13, 2026
A related case, in case it helps: a hang (not a crash) during interpreter finalization, after a short-lived client run has completed.
python:3.14.8-trixie), Temporal server 1.32.1 (PostgreSQL 18).Client, starts one workflow, awaits its result, fetches its history, and returns fromasyncio.run(). It prints its final line, then never exits. This is rare and seen only under heavy CI load; it does not reproduce on an idle machine (0 in 600 runs).timeout -s ABRTplusPYTHONFAULTHANDLER=1gives the stack below: the main thread is in GC at finalization, with no Python frame, blocked in asyscall(futex) insidetemporal_sdk_bridge.abi3.so, called from_Py_Dealloc.Python 3.14 documents that foreign threads attaching during finalization are blocked (C-API runtime finalization), which may make a bridge drop that waits on a Tokio thread hang instead of crash. We work around it with
os._exit(0)after a verified success in our short-lived CLIs.