`_interpchannels`: missing `PyErr_NoMemory()` causes `SystemErroron` OOM in `{create,close}()`
Bug report
Bug description:
Bug description
parent issue:#146102
refs:https://gist.github.com/devdanzin/d9975354c27e57f2589acfd0a777a2f1
Several allocation failure paths in Modules/_interpchannelsmodule.c
return NULL or -1 without setting an exception.
This violates CPython's C API convention that an error return should have
an exception set. In release builds, this can surface as:
SystemError: <built-in function ...> returned NULL without setting an exception
instead of the expected MemoryError. In debug builds, the same paths may
trip assert(PyErr_Occurred()) in handle_channel_error().
Affected functions
The affected allocation sites are:
| Function | Triggered via |
|---|---|
_channelends_new() |
_channels.create() |
_channel_new() |
_channels.create() |
_channelref_new() |
_channels.create() |
_channel_set_closing() |
_channels.close(send=True) on a non-empty channel |
Nearby helper functions in the same file already handle this correctly by
calling PyErr_NoMemory() on allocation failure, for example:
| Function |
|---|
_channelitem_new() |
_channelqueue_new() |
_channelend_new() |
Reproducer
import _testcapi import _interpchannels as _channels from concurrent.interpreters import _crossinterp REPLACE = _crossinterp._UNBOUND_CONSTANT_TO_FLAG[_crossinterp.UNBOUND] # Case 1: _channels.create() for n in range(1, 20): _testcapi.set_nomemory(n, n + 1) try: cid = _channels.create(REPLACE) except MemoryError: pass except SystemError as exc: print(f"[create] n={n}: {type(exc).__name__}: {exc}") else: _channels.destroy(cid) finally: _testcapi.remove_mem_hooks() # Case 2: _channels.close(send=True) on a non-empty channel for n in range(1, 30): cid = _channels.create(REPLACE) _channels.send(cid, b"spam", blocking=False) _testcapi.set_nomemory(n, n + 1) try: _channels.close(cid, send=True) except MemoryError: pass except SystemError as exc: print(f"[close] n={n}: {type(exc).__name__}: {exc}") finally: _testcapi.remove_mem_hooks() try: _channels.close(cid, force=True) except Exception: pass try: _channels.destroy(cid) except Exception: pass
Sample output on main, CPython 3.16.0a0, macOS, release build:
[create] n=1: SystemError: <built-in function create> returned NULL without setting an exception
[create] n=3: SystemError: <built-in function create> returned NULL without setting an exception
[create] n=4: SystemError: <built-in function create> returned NULL without setting an exception
[close] n=1: SystemError: <built-in function close> returned NULL without setting an exception
Expected behavior
All affected allocation failure paths should raise MemoryError, consistent
with nearby helper functions and CPython's general C API error handling
contract.
Fix
Call PyErr_NoMemory() before returning an error from the affected
allocation failure paths.
CPython versions tested on:
CPython main branch
Operating systems tested on:
macOS