diff --git a/Lib/test/test__interpchannels.py b/Lib/test/test__interpchannels.py index 2b0aba42896c06..c5b830b63cdfe2 100644 --- a/Lib/test/test__interpchannels.py +++ b/Lib/test/test__interpchannels.py @@ -1838,5 +1838,68 @@ def test_force_close(self): fix.clean_up() +class InterpChannelsOOMTest(TestBase): + def test_create_no_memory(self): + _testcapi = import_helper.import_module('_testcapi') + + raised_memoryerror = False + for start in range(1, 20): + with self.subTest(start=start): + cid = None + _testcapi.set_nomemory(start, start + 1) + try: + try: + cid = _channels.create(REPLACE) + except MemoryError: + raised_memoryerror = True + except SystemError as exc: + self.fail( + f"missing MemoryError at start={start}: {exc}") + finally: + _testcapi.remove_mem_hooks() + if cid is not None: + _channels.destroy(cid) + + self.assertTrue( + raised_memoryerror, + "_testcapi.set_nomemory did not trigger a MemoryError from " + "_channels.create() within start=1..20; widen range") + + def test_close_nonempty_no_memory(self): + _testcapi = import_helper.import_module('_testcapi') + + raised_memoryerror = False + for start in range(1, 30): + with self.subTest(start=start): + cid = _channels.create(REPLACE) + try: + _channels.send(cid, b'spam', blocking=False) + + _testcapi.set_nomemory(start, start + 1) + try: + _channels.close(cid, send=True) + except MemoryError: + raised_memoryerror = True + except SystemError as exc: + self.fail( + f"missing MemoryError at start={start}: {exc}") + finally: + _testcapi.remove_mem_hooks() + finally: + try: + _channels.close(cid, force=True) + except Exception: + pass + try: + _channels.destroy(cid) + except Exception: + pass + + self.assertTrue( + raised_memoryerror, + "_testcapi.set_nomemory did not trigger a MemoryError from " + "_channel_set_closing within start=1..30; widen range") + + if __name__ == '__main__': unittest.main() diff --git a/Misc/NEWS.d/next/Library/2026-05-22-10-13-00.gh-issue-150213.Vw3u8K.rst b/Misc/NEWS.d/next/Library/2026-05-22-10-13-00.gh-issue-150213.Vw3u8K.rst new file mode 100644 index 00000000000000..4f5ba17abcc0da --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-05-22-10-13-00.gh-issue-150213.Vw3u8K.rst @@ -0,0 +1,3 @@ +Fix missing :exc:`MemoryError` in several out-of-memory error paths in +:mod:`!_interpchannels`. Previously these paths could raise +:exc:`SystemError` or trigger an assertion failure in debug builds. diff --git a/Modules/_interpchannelsmodule.c b/Modules/_interpchannelsmodule.c index c6d107d243dda0..3fe47db24b53d5 100644 --- a/Modules/_interpchannelsmodule.c +++ b/Modules/_interpchannelsmodule.c @@ -922,6 +922,7 @@ _channelends_new(void) { _channelends *ends = GLOBAL_MALLOC(_channelends); if (ends== NULL) { + PyErr_NoMemory(); return NULL; } ends->numsendopen = 0; @@ -1115,6 +1116,7 @@ _channel_new(PyThread_type_lock mutex, struct _channeldefaults defaults) assert(check_unbound(defaults.unboundop)); _channel_state *chan = GLOBAL_MALLOC(_channel_state); if (chan == NULL) { + PyErr_NoMemory(); return NULL; } chan->mutex = mutex; @@ -1313,6 +1315,7 @@ _channelref_new(int64_t cid, _channel_state *chan) { _channelref *ref = GLOBAL_MALLOC(_channelref); if (ref == NULL) { + PyErr_NoMemory(); return NULL; } ref->cid = cid; @@ -1698,6 +1701,7 @@ _channel_set_closing(_channelref *ref, PyThread_type_lock mutex) { } chan->closing = GLOBAL_MALLOC(struct _channel_closing); if (chan->closing == NULL) { + PyErr_NoMemory(); goto done; } chan->closing->ref = ref; @@ -2948,7 +2952,7 @@ channelsmod_create(PyObject *self, PyObject *args, PyObject *kwds) if (handle_channel_error(err, self, cid)) { assert(cidobj == NULL); err = channel_destroy(&_globals.channels, cid); - if (handle_channel_error(err, self, cid)) { + if (err != 0 && handle_channel_error(err, self, cid)) { // XXX issue a warning? } return NULL;