Skip to content

Commit eb22e2b

Browse files
gh-115490: Make the interpreter.channels and interpreter.queues Modules Handle Reloading Properly (gh-115493)
The problem manifested when the .py module got reloaded and the corresponding extension module didn't. The .py module registers types with the extension and the extension was not allowing that to happen more than once. The solution: let it happen more than once.
1 parent 207030f commit eb22e2b

File tree

6 files changed

+60
-19
lines changed

6 files changed

+60
-19
lines changed

Lib/test/libregrtest/main.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -384,15 +384,10 @@ def run_tests_sequentially(self, runtests) -> None:
384384

385385
result = self.run_test(test_name, runtests, tracer)
386386

387-
# Unload the newly imported test modules (best effort
388-
# finalization). To work around gh-115490, don't unload
389-
# test.support.interpreters and its submodules even if they
390-
# weren't loaded before.
391-
keep = "test.support.interpreters"
387+
# Unload the newly imported test modules (best effort finalization)
392388
new_modules = [module for module in sys.modules
393389
if module not in save_modules and
394-
module.startswith(("test.", "test_"))
395-
and not module.startswith(keep)]
390+
module.startswith(("test.", "test_"))]
396391
for module in new_modules:
397392
sys.modules.pop(module, None)
398393
# Remove the attribute of the parent module.

Lib/test/test__xxinterpchannels.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818
channels = import_helper.import_module('_xxinterpchannels')
1919

2020

21+
# Additional tests are found in Lib/test/test_interpreters/test_channels.py.
22+
# New tests should be added there.
23+
# XXX The tests here should be moved there. See the note under LowLevelTests.
24+
25+
2126
##################################
2227
# helpers
2328

Lib/test/test_interpreters/test_channels.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import importlib
12
import threading
23
from textwrap import dedent
34
import unittest
@@ -11,6 +12,24 @@
1112
from .utils import _run_output, TestBase
1213

1314

15+
class LowLevelTests(TestBase):
16+
17+
# The behaviors in the low-level module is important in as much
18+
# as it is exercised by the high-level module. Therefore the
19+
# most # important testing happens in the high-level tests.
20+
# These low-level tests cover corner cases that are not
21+
# encountered by the high-level module, thus they
22+
# mostly shouldn't matter as much.
23+
24+
# Additional tests are found in Lib/test/test__xxinterpchannels.py.
25+
# XXX Those should be either moved to LowLevelTests or eliminated
26+
# in favor of high-level tests in this file.
27+
28+
def test_highlevel_reloaded(self):
29+
# See gh-115490 (https://github.com/python/cpython/issues/115490).
30+
importlib.reload(channels)
31+
32+
1433
class TestChannels(TestBase):
1534

1635
def test_create(self):

Lib/test/test_interpreters/test_queues.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import importlib
12
import threading
23
from textwrap import dedent
34
import unittest
@@ -20,6 +21,20 @@ def tearDown(self):
2021
pass
2122

2223

24+
class LowLevelTests(TestBase):
25+
26+
# The behaviors in the low-level module is important in as much
27+
# as it is exercised by the high-level module. Therefore the
28+
# most # important testing happens in the high-level tests.
29+
# These low-level tests cover corner cases that are not
30+
# encountered by the high-level module, thus they
31+
# mostly shouldn't matter as much.
32+
33+
def test_highlevel_reloaded(self):
34+
# See gh-115490 (https://github.com/python/cpython/issues/115490).
35+
importlib.reload(queues)
36+
37+
2338
class QueueTests(TestBase):
2439

2540
def test_create(self):

Modules/_xxinterpchannelsmodule.c

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2675,12 +2675,17 @@ set_channelend_types(PyObject *mod, PyTypeObject *send, PyTypeObject *recv)
26752675
return -1;
26762676
}
26772677

2678-
if (state->send_channel_type != NULL
2679-
|| state->recv_channel_type != NULL)
2680-
{
2681-
PyErr_SetString(PyExc_TypeError, "already registered");
2682-
return -1;
2678+
// Clear the old values if the .py module was reloaded.
2679+
if (state->send_channel_type != NULL) {
2680+
(void)_PyCrossInterpreterData_UnregisterClass(state->send_channel_type);
2681+
Py_CLEAR(state->send_channel_type);
26832682
}
2683+
if (state->recv_channel_type != NULL) {
2684+
(void)_PyCrossInterpreterData_UnregisterClass(state->recv_channel_type);
2685+
Py_CLEAR(state->recv_channel_type);
2686+
}
2687+
2688+
// Add and register the types.
26842689
state->send_channel_type = (PyTypeObject *)Py_NewRef(send);
26852690
state->recv_channel_type = (PyTypeObject *)Py_NewRef(recv);
26862691
if (ensure_xid_class(send, _channelend_shared) < 0) {

Modules/_xxinterpqueuesmodule.c

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,9 @@ static int
169169
clear_module_state(module_state *state)
170170
{
171171
/* external types */
172+
if (state->queue_type != NULL) {
173+
(void)_PyCrossInterpreterData_UnregisterClass(state->queue_type);
174+
}
172175
Py_CLEAR(state->queue_type);
173176

174177
/* QueueError */
@@ -1078,15 +1081,18 @@ set_external_queue_type(PyObject *module, PyTypeObject *queue_type)
10781081
{
10791082
module_state *state = get_module_state(module);
10801083

1084+
// Clear the old value if the .py module was reloaded.
10811085
if (state->queue_type != NULL) {
1082-
PyErr_SetString(PyExc_TypeError, "already registered");
1083-
return -1;
1086+
(void)_PyCrossInterpreterData_UnregisterClass(
1087+
state->queue_type);
1088+
Py_CLEAR(state->queue_type);
10841089
}
1085-
state->queue_type = (PyTypeObject *)Py_NewRef(queue_type);
10861090

1091+
// Add and register the new type.
10871092
if (ensure_xid_class(queue_type, _queueobj_shared) < 0) {
10881093
return -1;
10891094
}
1095+
state->queue_type = (PyTypeObject *)Py_NewRef(queue_type);
10901096

10911097
return 0;
10921098
}
@@ -1703,10 +1709,6 @@ module_clear(PyObject *mod)
17031709
{
17041710
module_state *state = get_module_state(mod);
17051711

1706-
if (state->queue_type != NULL) {
1707-
(void)_PyCrossInterpreterData_UnregisterClass(state->queue_type);
1708-
}
1709-
17101712
// Now we clear the module state.
17111713
clear_module_state(state);
17121714
return 0;

0 commit comments

Comments
 (0)