Skip to content

Commit d8343bf

Browse files
committed
gh-142414: Make value, not identity, matter for UNBOUND* objects
1 parent 4c95ad8 commit d8343bf

File tree

5 files changed

+40
-133
lines changed

5 files changed

+40
-133
lines changed

Lib/concurrent/interpreters/_crossinterp.py

Lines changed: 21 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -29,79 +29,39 @@ def __get__(self, obj, cls):
2929
return self.getter(None, cls)
3030

3131

32-
class UnboundItem:
33-
"""Represents a cross-interpreter item no longer bound to an interpreter.
32+
class UnboundBehavior:
33+
__slots__ = ('name', '_unboundop')
3434

35-
An item is unbound when the interpreter that added it to the
36-
cross-interpreter container is destroyed.
37-
"""
38-
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}')
35+
def __init__(self, name, *, _unboundop):
36+
self.name = name
37+
self._unboundop = _unboundop
6638

6739
def __repr__(self):
68-
return f'{self._MODULE}.{self._NAME}'
69-
# return f'interpreters._queues.UNBOUND'
40+
return f'<{self.name}>'
7041

7142

72-
UNBOUND = object.__new__(UnboundItem)
73-
UNBOUND_ERROR = object()
74-
UNBOUND_REMOVE = object()
43+
UNBOUND = UnboundBehavior('UNBOUND', _unboundop=3)
44+
UNBOUND_ERROR = UnboundBehavior('UNBOUND_ERROR', _unboundop=2)
45+
UNBOUND_REMOVE = UnboundBehavior('UNBOUND_REMOVE', _unboundop=1)
7546

76-
_UNBOUND_CONSTANT_TO_FLAG = {
77-
UNBOUND_REMOVE: 1,
78-
UNBOUND_ERROR: 2,
79-
UNBOUND: 3,
80-
}
81-
_UNBOUND_FLAG_TO_CONSTANT = {v: k
82-
for k, v in _UNBOUND_CONSTANT_TO_FLAG.items()}
8347

84-
85-
def serialize_unbound(unbound):
86-
op = unbound
48+
def unbound_to_flag(unbounditems):
49+
if unbounditems is None:
50+
return -1
8751
try:
88-
flag = _UNBOUND_CONSTANT_TO_FLAG[op]
89-
except KeyError:
90-
raise NotImplementedError(f'unsupported unbound replacement op {op!r}')
91-
return flag,
92-
52+
return unbounditems._unboundop
53+
except AttributeError:
54+
raise NotImplementedError(
55+
f'unsupported unbound replacement object {unbounditems!r}')
9356

9457
def resolve_unbound(flag, exctype_destroyed):
95-
try:
96-
op = _UNBOUND_FLAG_TO_CONSTANT[flag]
97-
except KeyError:
98-
raise NotImplementedError(f'unsupported unbound replacement op {flag!r}')
99-
if op is UNBOUND_REMOVE:
58+
if flag == UNBOUND_REMOVE._unboundop:
10059
# "remove" not possible here
10160
raise NotImplementedError
102-
elif op is UNBOUND_ERROR:
61+
elif flag == UNBOUND_ERROR._unboundop:
10362
raise exctype_destroyed("item's original interpreter destroyed")
104-
elif op is UNBOUND:
63+
elif flag == UNBOUND._unboundop:
10564
return UNBOUND
10665
else:
107-
raise NotImplementedError(repr(op))
66+
raise NotImplementedError(
67+
f'unsupported unbound replacement op {flag!r}')

Lib/concurrent/interpreters/_queues.py

Lines changed: 8 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
QueueError, QueueNotFoundError,
1212
)
1313
from ._crossinterp import (
14-
UNBOUND_ERROR, UNBOUND_REMOVE,
14+
UNBOUND, UNBOUND_ERROR, UNBOUND_REMOVE,
1515
)
1616

1717
__all__ = [
@@ -42,24 +42,12 @@ class ItemInterpreterDestroyed(QueueError,
4242
"""Raised from get() and get_nowait()."""
4343

4444

45-
_SHARED_ONLY = 0
46-
_PICKLED = 1
47-
48-
49-
UNBOUND = _crossinterp.UnboundItem.singleton('queue', __name__)
50-
51-
52-
def _serialize_unbound(unbound):
53-
if unbound is UNBOUND:
54-
unbound = _crossinterp.UNBOUND
55-
return _crossinterp.serialize_unbound(unbound)
45+
def _resolve_unbound(flag):
46+
return _crossinterp.resolve_unbound(flag, ItemInterpreterDestroyed)
5647

5748

58-
def _resolve_unbound(flag):
59-
resolved = _crossinterp.resolve_unbound(flag, ItemInterpreterDestroyed)
60-
if resolved is _crossinterp.UNBOUND:
61-
resolved = UNBOUND
62-
return resolved
49+
_SHARED_ONLY = 0
50+
_PICKLED = 1
6351

6452

6553
def create(maxsize=0, *, unbounditems=UNBOUND):
@@ -71,8 +59,7 @@ def create(maxsize=0, *, unbounditems=UNBOUND):
7159
supported values. The default value is UNBOUND, which replaces
7260
the unbound item.
7361
"""
74-
unbound = _serialize_unbound(unbounditems)
75-
unboundop, = unbound
62+
unboundop = _crossinterp.unbound_to_flag(unbounditems)
7663
qid = _queues.create(maxsize, unboundop, -1)
7764
self = Queue(qid)
7865
self._set_unbound(unboundop, unbounditems)
@@ -211,10 +198,7 @@ def put(self, obj, block=True, timeout=None, *,
211198
"""
212199
if not block:
213200
return self.put_nowait(obj, unbounditems=unbounditems)
214-
if unbounditems is None:
215-
unboundop = -1
216-
else:
217-
unboundop, = _serialize_unbound(unbounditems)
201+
unboundop = _crossinterp.unbound_to_flag(unbounditems)
218202
if timeout is not None:
219203
timeout = int(timeout)
220204
if timeout < 0:
@@ -231,10 +215,7 @@ def put(self, obj, block=True, timeout=None, *,
231215
break
232216

233217
def put_nowait(self, obj, *, unbounditems=None):
234-
if unbounditems is None:
235-
unboundop = -1
236-
else:
237-
unboundop, = _serialize_unbound(unbounditems)
218+
unboundop = _crossinterp.unbound_to_flag(unbounditems)
238219
_queues.put(self._id, obj, unboundop)
239220

240221
def get(self, block=True, timeout=None, *,

Lib/test/support/channels.py

Lines changed: 9 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
ChannelEmptyError, ChannelNotEmptyError, # noqa: F401
1111
)
1212
from concurrent.interpreters._crossinterp import (
13-
UNBOUND_ERROR, UNBOUND_REMOVE,
13+
UNBOUND, UNBOUND_ERROR, UNBOUND_REMOVE,
1414
)
1515

1616

@@ -28,20 +28,8 @@ class ItemInterpreterDestroyed(ChannelError,
2828
"""Raised from get() and get_nowait()."""
2929

3030

31-
UNBOUND = _crossinterp.UnboundItem.singleton('queue', __name__)
32-
33-
34-
def _serialize_unbound(unbound):
35-
if unbound is UNBOUND:
36-
unbound = _crossinterp.UNBOUND
37-
return _crossinterp.serialize_unbound(unbound)
38-
39-
4031
def _resolve_unbound(flag):
41-
resolved = _crossinterp.resolve_unbound(flag, ItemInterpreterDestroyed)
42-
if resolved is _crossinterp.UNBOUND:
43-
resolved = UNBOUND
44-
return resolved
32+
return _crossinterp.resolve_unbound(flag, ItemInterpreterDestroyed)
4533

4634

4735
def create(*, unbounditems=UNBOUND):
@@ -53,8 +41,9 @@ def create(*, unbounditems=UNBOUND):
5341
See SendChannel.send() for supported values. The default value
5442
is UNBOUND, which replaces the unbound item when received.
5543
"""
56-
unbound = _serialize_unbound(unbounditems)
57-
unboundop, = unbound
44+
if unbounditems is None:
45+
raise TypeError(f'unbounditems must not be None')
46+
unboundop = _crossinterp.unbound_to_flag(unbounditems)
5847
cid = _channels.create(unboundop, -1)
5948
recv, send = RecvChannel(cid), SendChannel(cid)
6049
send._set_unbound(unboundop, unbounditems)
@@ -179,17 +168,6 @@ class SendChannel(_ChannelEnd):
179168

180169
_end = 'send'
181170

182-
# def __new__(cls, cid, *, _unbound=None):
183-
# if _unbound is None:
184-
# try:
185-
# op = _channels.get_channel_defaults(cid)
186-
# _unbound = (op,)
187-
# except ChannelNotFoundError:
188-
# _unbound = _serialize_unbound(UNBOUND)
189-
# self = super().__new__(cls, cid)
190-
# self._unbound = _unbound
191-
# return self
192-
193171
def _set_unbound(self, op, items=None):
194172
assert not hasattr(self, '_unbound')
195173
if items is None:
@@ -219,10 +197,7 @@ def send(self, obj, timeout=None, *,
219197
220198
This blocks until the object is received.
221199
"""
222-
if unbounditems is None:
223-
unboundop = -1
224-
else:
225-
unboundop, = _serialize_unbound(unbounditems)
200+
unboundop = _crossinterp.unbound_to_flag(unbounditems)
226201
_channels.send(self._id, obj, unboundop, timeout=timeout, blocking=True)
227202

228203
def send_nowait(self, obj, *,
@@ -233,10 +208,7 @@ def send_nowait(self, obj, *,
233208
If the object is immediately received then return True
234209
(else False). Otherwise this is the same as send().
235210
"""
236-
if unbounditems is None:
237-
unboundop = -1
238-
else:
239-
unboundop, = _serialize_unbound(unbounditems)
211+
unboundop = _crossinterp.unbound_to_flag(unbounditems)
240212
# XXX Note that at the moment channel_send() only ever returns
241213
# None. This should be fixed when channel_send_wait() is added.
242214
# See bpo-32604 and gh-19829.
@@ -249,10 +221,7 @@ def send_buffer(self, obj, timeout=None, *,
249221
250222
This blocks until the object is received.
251223
"""
252-
if unbounditems is None:
253-
unboundop = -1
254-
else:
255-
unboundop, = _serialize_unbound(unbounditems)
224+
unboundop = _crossinterp.unbound_to_flag(unbounditems)
256225
_channels.send_buffer(self._id, obj, unboundop,
257226
timeout=timeout, blocking=True)
258227

@@ -264,10 +233,7 @@ def send_buffer_nowait(self, obj, *,
264233
If the object is immediately received then return True
265234
(else False). Otherwise this is the same as send().
266235
"""
267-
if unbounditems is None:
268-
unboundop = -1
269-
else:
270-
unboundop, = _serialize_unbound(unbounditems)
236+
unboundop = _crossinterp.unbound_to_flag(unbounditems)
271237
return _channels.send_buffer(self._id, obj, unboundop, blocking=False)
272238

273239
def close(self):

Lib/test/test__interpchannels.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
)
1818

1919

20-
REPLACE = _crossinterp._UNBOUND_CONSTANT_TO_FLAG[_crossinterp.UNBOUND]
20+
REPLACE = _crossinterp.UNBOUND._unboundop
2121

2222

2323
# Additional tests are found in Lib/test/test_interpreters/test_channels.py.

Lib/test/test_interpreters/test_queues.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from .utils import _run_output, TestBase as _TestBase
1313

1414
HUGE_TIMEOUT = 3600
15-
REPLACE = _crossinterp._UNBOUND_CONSTANT_TO_FLAG[_crossinterp.UNBOUND]
15+
REPLACE = _crossinterp.UNBOUND._unboundop
1616

1717

1818
def get_num_queues():

0 commit comments

Comments
 (0)