Skip to content

_interpchannels.create() debug aborts if channel lock allocation fails #152635

Description

@stestagg

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:

cpython/Python/thread.c

Lines 86 to 91 in 0a29d84

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    extension-modulesC modules in the Modules dirtopic-subprocessSubprocess issues.type-crashA hard crash of the interpreter, possibly with a core dump
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions