Skip to content

_interpchannels: missing PyErr_NoMemory() causes SystemErroron OOM in {create,close}() #150213

@lpyu001

Description

@lpyu001

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

Linked PRs

Metadata

Metadata

Assignees

No one assigned
    No fields configured for issues without a type.

    Projects

    Status

    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions