Skip to content

Segfault / access violation awaiting any JS promise on Python 3.14.0–3.14.3 (thread-state offset from 3.14.4+ headers) #515

Open

Description

@dandydarshan

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

Relevant 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.0

Additional 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

Assignees

Labels

No labels
No labels

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions