Repository navigation
Description
Issue type
Bug
How did you install PythonMonkey?
Installed from pip
OS platform and distribution
All tested: Windows 11 x64, Ubuntu 24.04 x64 and arm64, macOS arm64 and x64 (GitHub Actions runners)
Python version (python --version)
3.14.3 (crashes); 3.14.10, 3.13, 3.12 and 3.15 work
PythonMonkey version (pip show pythonmonkey)
1.3.1 and 1.4.1 (both crash on 3.14.3)
Bug Description
On Python 3.14.0–3.14.3, awaiting any JS promise crashes the interpreter: a segfault (signal 11) on Linux/macOS, an access violation (0xC0000005) on Windows. Synchronous calls (pm.eval('1 + 1'), calling a JS function) work. On Python 3.14.4+ everything works.
Cause. PyEventLoop::_getLoopOnThread (src/PyEventLoop.cc) gets the running loop by casting the thread state to a local copy of CPython's private _PyThreadStateImpl and reading asyncio_running_loop at a fixed offset:
using PyThreadStateHolder = struct { // _PyThreadStateImpl
PyThreadState base;
Py_ssize_t refcount;
uintptr_t c_stack_top;
uintptr_t c_stack_soft_limit;
uintptr_t c_stack_hard_limit;
PyObject *asyncio_running_loop;
};Python 3.14.4 added a pointer field to the public PyThreadState (_PyStackChunk *datastack_cached_chunk;, python/cpython@19cbcc0f85, gh-142183). The wheels appear to be built against 3.14.4+ headers, so sizeof(PyThreadState) is 8 bytes larger than on 3.14.0–3.14.3. On those versions the read lands on the next field, asyncio_running_task. PythonMonkey then treats the running Task as the event loop. In the setTimeout case this surfaces as:
AttributeError: '_asyncio.Task' object has no attribute 'call_later'
before the process crashes. In the other cases it crashes outright (Py_INCREF and method calls on the wrong object).
The same mismatch would happen the other way round: a wheel built against 3.14.0–3.14.3 headers would misread on 3.14.4+. Since CPython changes these structs in patch releases, a compiled-in offset can break on any 3.x.y update.
Standalone code to reproduce the issue
import asyncio, pythonmonkey as pm
async def main():
return await pm.eval('Promise.resolve(1)')
print(asyncio.run(main())) # Python 3.14.3: segfault / access violation. 3.14.4+: prints 1.0Relevant log output or backtrace
Windows 11, Python 3.14.3 (MSC v.1944 64 bit), pythonmonkey 1.3.1:
eval_number ok 2.0
call_js_function ok 2.0
await_resolved CRASH 0xc0000005 Windows fatal exception: access violation
await_async_fn CRASH 0xc0000005
await_setTimeout CRASH 0xc0000005 AttributeError: '_asyncio.Task' object has no attribute 'call_later'
Ubuntu 24.04 x64, Python 3.14.3, pythonmonkey 1.4.1:
await_resolved CRASH signal 11 (Fatal Python error: Segmentation fault)
Same runner, Python 3.14.10, pythonmonkey 1.4.1:
await_resolved ok 1.0Additional info if applicable
Matrix run, 45 jobs: 5 OS/CPU x Python 3.12 / 3.13 / 3.14.3 / 3.14.10 / 3.15 x pythonmonkey 1.3.1 / 1.4.1.
All 10 Python 3.14.3 jobs crash; all 35 others pass.
https://github.com/dandydarshan/CodeWiki/actions/runs/42440036273
Probe scripts: https://github.com/dandydarshan/CodeWiki/tree/pythonmonkey-matrix/scripts (pm_narrow.py, pythonmonkey_repro.py)
Possible fixes, roughly from most to least robust:
- For the current thread, call asyncio's own _get_running_loop() (or the C equivalent) instead of reading the struct.
- For the main thread (getMainLoop, used from helper threads), remember the loop once it is found from the main thread instead of reading another thread's state.
- At minimum, at import time compare the runtime Py_Version with the PY_VERSION_HEX the wheel was built with, and refuse the struct read (raise) when the micro version crosses a known layout change, so users get an error instead of a crash.
Related reports that may share this cause or a similar one: #476, #271.What branch of PythonMonkey were you developing on? (If applicable)
No response
Metadata
Metadata
Labels
Type
Projects
- StatusShow more project fieldsDone
Activity
Xmader commentedon Nov 12, 2026
Thanks for the detailed report and the analysis. The diagnosis is right, but calling asyncio's own _get_running_loop() (or the C equivalent) instead of reading the struct would not make a fix. (Indeed in older commits it did call
asyncio.get_running_loop())PythonMonkey has two loop lookups:
getRunningLoop()finds the loop on the current thread. It's used for await, setTimeout and so on.getMainLoop()finds the loop on the main thread from one of SpiderMonkey's helper threads. One example is posting a finished WebAssembly.compile back to Python.SpiderMonkey runs some async work on its own helper threads (e.g.
WebAssembly.compile). When that work finishes,JobQueue::dispatchToEventLoophas to post the result to the event loop on the main thread. That's whatgetMainLoop()is for.asyncio._get_running_loop()only returns the loop of the thread that calls it.Fix asyncio loop lookup on Python 3.14.0–3.14.3 (#515)
Xmader commentedon Nov 12, 2026
It should be fixed in 27dcd28
Xmader commentedon Nov 12, 2026
It's included in the release v1.5.1