Skip to content

Commit d2f7b30

Browse files
committed
Remove singleton design pattern in _crossinterp.py.
1 parent 726e8e8 commit d2f7b30

File tree

5 files changed

+20
-58
lines changed

5 files changed

+20
-58
lines changed

Lib/concurrent/interpreters/_crossinterp.py

Lines changed: 8 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -5,71 +5,23 @@ class ItemInterpreterDestroyed(Exception):
55
"""Raised when trying to get an item whose interpreter was destroyed."""
66

77

8-
class classonly:
9-
"""A non-data descriptor that makes a value only visible on the class.
10-
11-
This is like the "classmethod" builtin, but does not show up on
12-
instances of the class. It may be used as a decorator.
13-
"""
14-
15-
def __init__(self, value):
16-
self.value = value
17-
self.getter = classmethod(value).__get__
18-
self.name = None
19-
20-
def __set_name__(self, cls, name):
21-
if self.name is not None:
22-
raise TypeError('already used')
23-
self.name = name
24-
25-
def __get__(self, obj, cls):
26-
if obj is not None:
27-
raise AttributeError(self.name)
28-
# called on the class
29-
return self.getter(None, cls)
30-
31-
328
class UnboundItem:
339
"""Represents a cross-interpreter item no longer bound to an interpreter.
3410
3511
An item is unbound when the interpreter that added it to the
3612
cross-interpreter container is destroyed.
3713
"""
3814

39-
__slots__ = ()
40-
41-
@classonly
42-
def singleton(cls, kind, module, name='UNBOUND'):
43-
doc = cls.__doc__
44-
if doc:
45-
doc = doc.replace(
46-
'cross-interpreter container', kind,
47-
).replace(
48-
'cross-interpreter', kind,
49-
)
50-
subclass = type(
51-
f'Unbound{kind.capitalize()}Item',
52-
(cls,),
53-
{
54-
"_MODULE": module,
55-
"_NAME": name,
56-
"__doc__": doc,
57-
},
58-
)
59-
return object.__new__(subclass)
60-
61-
_MODULE = __name__
62-
_NAME = 'UNBOUND'
63-
64-
def __new__(cls):
65-
raise Exception(f'use {cls._MODULE}.{cls._NAME}')
15+
def __init__(self, kind, module, name='UNBOUND'):
16+
self._kind = kind
17+
self._module = module
18+
self._name = name
6619

6720
def __repr__(self):
68-
return f'{self._MODULE}.{self._NAME}'
69-
# return f'interpreters._queues.UNBOUND'
21+
return f'{self._module}.{self._name}'
7022

7123

72-
UNBOUND = object.__new__(UnboundItem)
24+
UNBOUND = UnboundItem('cross-interpreter', __name__)
7325
UNBOUND_ERROR = object()
7426
UNBOUND_REMOVE = object()
7527

@@ -84,6 +36,8 @@ def __repr__(self):
8436

8537
def serialize_unbound(unbound):
8638
op = unbound
39+
if isinstance(op, UnboundItem):
40+
return _UNBOUND_CONSTANT_TO_FLAG[UNBOUND],
8741
try:
8842
flag = _UNBOUND_CONSTANT_TO_FLAG[op]
8943
except KeyError:

Lib/concurrent/interpreters/_queues.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,10 @@ class ItemInterpreterDestroyed(QueueError,
4646
_PICKLED = 1
4747

4848

49-
UNBOUND = _crossinterp.UnboundItem.singleton('queue', __name__)
49+
UNBOUND = _crossinterp.UnboundItem('queue', __name__)
5050

5151

5252
def _serialize_unbound(unbound):
53-
if unbound is UNBOUND:
54-
unbound = _crossinterp.UNBOUND
5553
return _crossinterp.serialize_unbound(unbound)
5654

5755

Lib/test/support/channels.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class ItemInterpreterDestroyed(ChannelError,
2828
"""Raised from get() and get_nowait()."""
2929

3030

31-
UNBOUND = _crossinterp.UnboundItem.singleton('queue', __name__)
31+
UNBOUND = _crossinterp.UnboundItem('queue', __name__)
3232

3333

3434
def _serialize_unbound(unbound):

Lib/test/test_interpreters/test_queues.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
# Raise SkipTest if subinterpreters not supported.
99
_queues = import_helper.import_module('_interpqueues')
1010
from concurrent import interpreters
11+
from concurrent.futures import InterpreterPoolExecutor
1112
from concurrent.interpreters import _queues as queues, _crossinterp
1213
from .utils import _run_output, TestBase as _TestBase
1314

@@ -93,6 +94,14 @@ def test_bind_release(self):
9394
with self.assertRaises(queues.QueueError):
9495
_queues.release(qid)
9596

97+
def test_interpreter_pool_executor_after_reload(self):
98+
# Regression test for gh-142414 (KeyError in serialize_unbound).
99+
importlib.reload(queues)
100+
code = "import struct"
101+
with InterpreterPoolExecutor(max_workers=1) as executor:
102+
results = executor.map(exec, [code] * 1)
103+
self.assertEqual(list(results), [None] * 3)
104+
96105

97106
class QueueTests(TestBase):
98107

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix spurious KeyError when concurrent.interpreters is reloaded after import.

0 commit comments

Comments
 (0)