Crash report
What happened?
import _interpchannels as c x = [] while True: x.append(c.create())
For me on a linux aarch64 VM with 2gb RAM, this reliably reproduces either an abort, or an 'Exception ignored' message.
Modules/_interpchannelsmodule.c:443:
int handle_channel_error(int, PyObject *, int64_t):
Assertion `PyErr_Occurred()' failed.
Aborted
LLDB
The failing assertion occurs in handle_channel_error() with err=-1, while the channel id argument contains -7:
frame #5: handle_channel_error(err=-1, mod=..., cid=-7)
frame #6: channelsmod_create(...)
-7 corresponds to ERR_CHANNEL_MUTEX_INIT.
So the low-level create path correctly identifies the failure, but channelsmod_create() passes the wrong value as the error code.
Root Cause
channel_create() returns a a negative error code on failure, but does not always set an exception. The caller, channelsmod_create converts the error code to -1 which enters a path that asserts an exception has been raised.
| PyThread_type_lock mutex = PyThread_allocate_lock(); | |
| if (mutex == NULL) { | |
| return ERR_CHANNEL_MUTEX_INIT; | |
| } |
If PyThread_allocate_lock() fails, it returns NULL but does not set a Python exception:
| PyMutex *lock = (PyMutex *)PyMem_RawMalloc(sizeof(PyMutex)); | |
| if (lock) { | |
| *lock = (PyMutex){0}; | |
| } | |
| return (PyThread_type_lock)lock; |
Then channel_create correctly returns ERR_CHANNEL_MUTEX_INIT:
| PyThread_type_lock mutex = PyThread_allocate_lock(); | |
| if (mutex == NULL) { | |
| return ERR_CHANNEL_MUTEX_INIT; | |
| } |
channelsmod_create() sees the error code, but passes -1 to handle_channel_error:
| int64_t cid = channel_create(&_globals.channels, defaults); | |
| if (cid < 0) { | |
| (void)handle_channel_error(-1, self, cid); | |
| return NULL; | |
| } |
handle_channel_error() then falls through the switch statement because -1 isn't a known error code, resulting in:
| else { | |
| assert(PyErr_Occurred()); | |
| } |
There is a branch for the -7: ERR_CHANNEL_MUTEX_INIT error code, but this is not hit because of the -1:
| else if (err == ERR_CHANNEL_MUTEX_INIT) { | |
| PyErr_SetString(state->ChannelError, | |
| "can't initialize mutex for new channel"); | |
| } |
Fix
I'm not an expert in this code, but it seems to me that a reasonable fix here might be to pass cid twice to handle_channel_error(), once as the error code and once as the channel id. This would allow handle_channel_error() to see the actual error code and handle it appropriately:
if (cid < 0) { (void)handle_channel_error(cid, self, cid); return NULL; }
This way, handle_channel_error picks up the -7 error code from cid (already confirmed as < 0) and the switch calls PyErr_SetString, rather than assert(PyErr_Occurred()).
CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Output from running 'python -VV' on the command line:
Python 3.16.0a0 (heads/main-dirty:9751e1d, Jun 29 2026, 14:39:58) [Clang 22.1.6 ]
Linked PRs
- gh-152635: Raise MemoryError rather than fail assert() when the lock allocation fails in _interpchannels.create() #152642
- [3.15] gh-152635: Raise MemoryError when the lock allocation fails in
_interpchannels.create()(GH-152642) #152671 - [3.14] gh-152635: Raise MemoryError when the lock allocation fails in
_interpchannels.create()(GH-152642) #152672 - [3.13] gh-152635: Raise MemoryError when the lock allocation fails in
_interpchannels.create()(GH-152642) #152673