Skip to content

Commit c25ac11

Browse files
committed
Fix: gh-152635 _interpchannels channel lock alloc failure
now raises MemoryError Previously, an allocation failure when creating the lock for a channel in _interpchannels would trigger an assert. Caused by `handle_channel_error` being passed an error code of -1 which is only allowed if an exception has been set. (in this case, no exception was set) `channelsmod_create` now forwards the error code from `channel_create` which `handle_channel_error` already handled. Because the only way to get a -7 error code (was `ERR_CHANNEL_MUTEX_INIT`) is via an allocation failure in `PyThread_allocate_lock`, I made `handle_channel_error` raise a `MemoryError` for this case rather than a `ChannelError`. I also renamed the constant from `ERR_CHANNEL_MUTEX_INIT` to `ERR_CHANNEL_ALLOC_LOCK` to make this clearer for future changes.
1 parent 5717518 commit c25ac11

3 files changed

Lines changed: 24 additions & 6 deletions

File tree

Lib/test/test_interpreters/test_channels.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from concurrent import interpreters
1212
from test.support import channels
1313
from .utils import _run_output, TestBase
14+
import _testcapi
1415

1516

1617
class LowLevelTests(TestBase):
@@ -30,6 +31,22 @@ def test_highlevel_reloaded(self):
3031
# See gh-115490 (https://github.com/python/cpython/issues/115490).
3132
importlib.reload(channels)
3233

34+
def test_lock_allocation_failure(self):
35+
# see gh-152635 (https://github.com/python/cpython/issues/152635)
36+
# The first allocation to happen is the lock, which
37+
# historically triggered an assert if alloc failed.
38+
cid = None
39+
_testcapi.set_nomemory(0, 1)
40+
try:
41+
cid = _channels.create()
42+
except MemoryError:
43+
pass # MemoryError is expected behavior
44+
finally:
45+
_testcapi.remove_mem_hooks()
46+
if cid is not None:
47+
_channels.close(cid, force=True)
48+
_channels.destroy(cid)
49+
3350

3451
class TestChannels(TestBase):
3552

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a crash caused when running out of memory creating a
2+
:mod:`!_interpchannels` channel. Now a :exc:`MemoryError` is correctly raised.

Modules/_interpchannelsmodule.c

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ clear_module_state(module_state *state)
354354
#define ERR_CHANNEL_INTERP_CLOSED -4
355355
#define ERR_CHANNEL_EMPTY -5
356356
#define ERR_CHANNEL_NOT_EMPTY -6
357-
#define ERR_CHANNEL_MUTEX_INIT -7
357+
#define ERR_CHANNEL_MUTEX_ALLOC_FAIL -7
358358
#define ERR_CHANNELS_MUTEX_INIT -8
359359
#define ERR_NO_NEXT_CHANNEL_ID -9
360360
#define ERR_CHANNEL_CLOSED_WAITING -10
@@ -427,9 +427,8 @@ handle_channel_error(int err, PyObject *mod, int64_t cid)
427427
"if not empty (try force=True)",
428428
cid);
429429
}
430-
else if (err == ERR_CHANNEL_MUTEX_INIT) {
431-
PyErr_SetString(state->ChannelError,
432-
"can't initialize mutex for new channel");
430+
else if (err == ERR_CHANNEL_MUTEX_ALLOC_FAIL) {
431+
PyErr_NoMemory();
433432
}
434433
else if (err == ERR_CHANNELS_MUTEX_INIT) {
435434
PyErr_SetString(state->ChannelError,
@@ -1744,7 +1743,7 @@ channel_create(_channels *channels, struct _channeldefaults defaults)
17441743
{
17451744
PyThread_type_lock mutex = PyThread_allocate_lock();
17461745
if (mutex == NULL) {
1747-
return ERR_CHANNEL_MUTEX_INIT;
1746+
return ERR_CHANNEL_MUTEX_ALLOC_FAIL;
17481747
}
17491748
_channel_state *chan = _channel_new(mutex, defaults);
17501749
if (chan == NULL) {
@@ -2938,7 +2937,7 @@ channelsmod_create(PyObject *self, PyObject *args, PyObject *kwds)
29382937

29392938
int64_t cid = channel_create(&_globals.channels, defaults);
29402939
if (cid < 0) {
2941-
(void)handle_channel_error(-1, self, cid);
2940+
(void)handle_channel_error(cid, self, cid);
29422941
return NULL;
29432942
}
29442943
module_state *state = get_module_state(self);

0 commit comments

Comments
 (0)