Skip to content

Commit 4b7d67c

Browse files
ericsnowcurrentlyGlyphack
authored andcommitted
pythongh-76785: More Fixes for test.support.interpreters (pythongh-113012)
This brings the module (along with the associated extension modules) mostly in sync with PEP 734. There are only a few small things to wrap up.
1 parent 6e38b20 commit 4b7d67c

File tree

13 files changed

+1899
-88
lines changed

13 files changed

+1899
-88
lines changed

Lib/test/support/interpreters/queues.py

+87-71
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@
33
import queue
44
import time
55
import weakref
6-
import _xxinterpchannels as _channels
7-
import _xxinterpchannels as _queues
6+
import _xxinterpqueues as _queues
87

98
# aliases:
10-
from _xxinterpchannels import (
11-
ChannelError as QueueError,
12-
ChannelNotFoundError as QueueNotFoundError,
9+
from _xxinterpqueues import (
10+
QueueError, QueueNotFoundError,
1311
)
1412

1513
__all__ = [
@@ -19,14 +17,27 @@
1917
]
2018

2119

20+
class QueueEmpty(_queues.QueueEmpty, queue.Empty):
21+
"""Raised from get_nowait() when the queue is empty.
22+
23+
It is also raised from get() if it times out.
24+
"""
25+
26+
27+
class QueueFull(_queues.QueueFull, queue.Full):
28+
"""Raised from put_nowait() when the queue is full.
29+
30+
It is also raised from put() if it times out.
31+
"""
32+
33+
2234
def create(maxsize=0):
2335
"""Return a new cross-interpreter queue.
2436
2537
The queue may be used to pass data safely between interpreters.
2638
"""
27-
# XXX honor maxsize
28-
qid = _queues.create()
29-
return Queue._with_maxsize(qid, maxsize)
39+
qid = _queues.create(maxsize)
40+
return Queue(qid)
3041

3142

3243
def list_all():
@@ -35,53 +46,37 @@ def list_all():
3546
for qid in _queues.list_all()]
3647

3748

38-
class QueueEmpty(queue.Empty):
39-
"""Raised from get_nowait() when the queue is empty.
40-
41-
It is also raised from get() if it times out.
42-
"""
43-
44-
45-
class QueueFull(queue.Full):
46-
"""Raised from put_nowait() when the queue is full.
47-
48-
It is also raised from put() if it times out.
49-
"""
50-
5149

5250
_known_queues = weakref.WeakValueDictionary()
5351

5452
class Queue:
5553
"""A cross-interpreter queue."""
5654

57-
@classmethod
58-
def _with_maxsize(cls, id, maxsize):
59-
if not isinstance(maxsize, int):
60-
raise TypeError(f'maxsize must be an int, got {maxsize!r}')
61-
elif maxsize < 0:
62-
maxsize = 0
63-
else:
64-
maxsize = int(maxsize)
65-
self = cls(id)
66-
self._maxsize = maxsize
67-
return self
68-
6955
def __new__(cls, id, /):
7056
# There is only one instance for any given ID.
7157
if isinstance(id, int):
72-
id = _channels._channel_id(id, force=False)
73-
elif not isinstance(id, _channels.ChannelID):
58+
id = int(id)
59+
else:
7460
raise TypeError(f'id must be an int, got {id!r}')
75-
key = int(id)
7661
try:
77-
self = _known_queues[key]
62+
self = _known_queues[id]
7863
except KeyError:
7964
self = super().__new__(cls)
8065
self._id = id
81-
self._maxsize = 0
82-
_known_queues[key] = self
66+
_known_queues[id] = self
67+
_queues.bind(id)
8368
return self
8469

70+
def __del__(self):
71+
try:
72+
_queues.release(self._id)
73+
except QueueNotFoundError:
74+
pass
75+
try:
76+
del _known_queues[self._id]
77+
except KeyError:
78+
pass
79+
8580
def __repr__(self):
8681
return f'{type(self).__name__}({self.id})'
8782

@@ -90,39 +85,58 @@ def __hash__(self):
9085

9186
@property
9287
def id(self):
93-
return int(self._id)
88+
return self._id
9489

9590
@property
9691
def maxsize(self):
97-
return self._maxsize
98-
99-
@property
100-
def _info(self):
101-
return _channels.get_info(self._id)
92+
try:
93+
return self._maxsize
94+
except AttributeError:
95+
self._maxsize = _queues.get_maxsize(self._id)
96+
return self._maxsize
10297

10398
def empty(self):
104-
return self._info.count == 0
99+
return self.qsize() == 0
105100

106101
def full(self):
107-
if self._maxsize <= 0:
108-
return False
109-
return self._info.count >= self._maxsize
102+
return _queues.is_full(self._id)
110103

111104
def qsize(self):
112-
return self._info.count
105+
return _queues.get_count(self._id)
113106

114-
def put(self, obj, timeout=None):
115-
# XXX block if full
116-
_channels.send(self._id, obj, blocking=False)
107+
def put(self, obj, timeout=None, *,
108+
_delay=10 / 1000, # 10 milliseconds
109+
):
110+
"""Add the object to the queue.
111+
112+
This blocks while the queue is full.
113+
"""
114+
if timeout is not None:
115+
timeout = int(timeout)
116+
if timeout < 0:
117+
raise ValueError(f'timeout value must be non-negative')
118+
end = time.time() + timeout
119+
while True:
120+
try:
121+
_queues.put(self._id, obj)
122+
except _queues.QueueFull as exc:
123+
if timeout is not None and time.time() >= end:
124+
exc.__class__ = QueueFull
125+
raise # re-raise
126+
time.sleep(_delay)
127+
else:
128+
break
117129

118130
def put_nowait(self, obj):
119-
# XXX raise QueueFull if full
120-
return _channels.send(self._id, obj, blocking=False)
131+
try:
132+
return _queues.put(self._id, obj)
133+
except _queues.QueueFull as exc:
134+
exc.__class__ = QueueFull
135+
raise # re-raise
121136

122137
def get(self, timeout=None, *,
123-
_sentinel=object(),
124-
_delay=10 / 1000, # 10 milliseconds
125-
):
138+
_delay=10 / 1000, # 10 milliseconds
139+
):
126140
"""Return the next object from the queue.
127141
128142
This blocks while the queue is empty.
@@ -132,25 +146,27 @@ def get(self, timeout=None, *,
132146
if timeout < 0:
133147
raise ValueError(f'timeout value must be non-negative')
134148
end = time.time() + timeout
135-
obj = _channels.recv(self._id, _sentinel)
136-
while obj is _sentinel:
137-
time.sleep(_delay)
138-
if timeout is not None and time.time() >= end:
139-
raise QueueEmpty
140-
obj = _channels.recv(self._id, _sentinel)
149+
while True:
150+
try:
151+
return _queues.get(self._id)
152+
except _queues.QueueEmpty as exc:
153+
if timeout is not None and time.time() >= end:
154+
exc.__class__ = QueueEmpty
155+
raise # re-raise
156+
time.sleep(_delay)
141157
return obj
142158

143-
def get_nowait(self, *, _sentinel=object()):
159+
def get_nowait(self):
144160
"""Return the next object from the channel.
145161
146162
If the queue is empty then raise QueueEmpty. Otherwise this
147163
is the same as get().
148164
"""
149-
obj = _channels.recv(self._id, _sentinel)
150-
if obj is _sentinel:
151-
raise QueueEmpty
152-
return obj
165+
try:
166+
return _queues.get(self._id)
167+
except _queues.QueueEmpty as exc:
168+
exc.__class__ = QueueEmpty
169+
raise # re-raise
153170

154171

155-
# XXX add this:
156-
#_channels._register_queue_type(Queue)
172+
_queues._register_queue_type(Queue)

Lib/test/test_interpreters/test_queues.py

+81-15
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,21 @@
55

66
from test.support import import_helper
77
# Raise SkipTest if subinterpreters not supported.
8-
import_helper.import_module('_xxinterpchannels')
9-
#import_helper.import_module('_xxinterpqueues')
8+
_queues = import_helper.import_module('_xxinterpqueues')
109
from test.support import interpreters
1110
from test.support.interpreters import queues
1211
from .utils import _run_output, TestBase
1312

1413

14+
class TestBase(TestBase):
15+
def tearDown(self):
16+
for qid in _queues.list_all():
17+
try:
18+
_queues.destroy(qid)
19+
except Exception:
20+
pass
21+
22+
1523
class QueueTests(TestBase):
1624

1725
def test_create(self):
@@ -32,20 +40,47 @@ def test_create(self):
3240
self.assertEqual(queue.maxsize, 0)
3341

3442
with self.subTest('negative maxsize'):
35-
queue = queues.create(-1)
36-
self.assertEqual(queue.maxsize, 0)
43+
queue = queues.create(-10)
44+
self.assertEqual(queue.maxsize, -10)
3745

3846
with self.subTest('bad maxsize'):
3947
with self.assertRaises(TypeError):
4048
queues.create('1')
4149

42-
@unittest.expectedFailure
4350
def test_shareable(self):
4451
queue1 = queues.create()
45-
queue2 = queues.create()
46-
queue1.put(queue2)
47-
queue3 = queue1.get()
48-
self.assertIs(queue3, queue1)
52+
53+
interp = interpreters.create()
54+
interp.exec_sync(dedent(f"""
55+
from test.support.interpreters import queues
56+
queue1 = queues.Queue({queue1.id})
57+
"""));
58+
59+
with self.subTest('same interpreter'):
60+
queue2 = queues.create()
61+
queue1.put(queue2)
62+
queue3 = queue1.get()
63+
self.assertIs(queue3, queue2)
64+
65+
with self.subTest('from current interpreter'):
66+
queue4 = queues.create()
67+
queue1.put(queue4)
68+
out = _run_output(interp, dedent("""
69+
queue4 = queue1.get()
70+
print(queue4.id)
71+
"""))
72+
qid = int(out)
73+
self.assertEqual(qid, queue4.id)
74+
75+
with self.subTest('from subinterpreter'):
76+
out = _run_output(interp, dedent("""
77+
queue5 = queues.create()
78+
queue1.put(queue5)
79+
print(queue5.id)
80+
"""))
81+
qid = int(out)
82+
queue5 = queue1.get()
83+
self.assertEqual(queue5.id, qid)
4984

5085
def test_id_type(self):
5186
queue = queues.create()
@@ -137,7 +172,6 @@ def test_put_get_main(self):
137172

138173
self.assertEqual(actual, expected)
139174

140-
@unittest.expectedFailure
141175
def test_put_timeout(self):
142176
queue = queues.create(2)
143177
queue.put(None)
@@ -147,7 +181,6 @@ def test_put_timeout(self):
147181
queue.get()
148182
queue.put(None)
149183

150-
@unittest.expectedFailure
151184
def test_put_nowait(self):
152185
queue = queues.create(2)
153186
queue.put_nowait(None)
@@ -179,31 +212,64 @@ def test_put_get_same_interpreter(self):
179212
assert obj is not orig, 'expected: obj is not orig'
180213
"""))
181214

182-
@unittest.expectedFailure
183215
def test_put_get_different_interpreters(self):
216+
interp = interpreters.create()
184217
queue1 = queues.create()
185218
queue2 = queues.create()
219+
self.assertEqual(len(queues.list_all()), 2)
220+
186221
obj1 = b'spam'
187222
queue1.put(obj1)
223+
188224
out = _run_output(
189-
interpreters.create(),
225+
interp,
190226
dedent(f"""
191-
import test.support.interpreters.queue as queues
227+
from test.support.interpreters import queues
192228
queue1 = queues.Queue({queue1.id})
193229
queue2 = queues.Queue({queue2.id})
230+
assert queue1.qsize() == 1, 'expected: queue1.qsize() == 1'
194231
obj = queue1.get()
232+
assert queue1.qsize() == 0, 'expected: queue1.qsize() == 0'
195233
assert obj == b'spam', 'expected: obj == obj1'
196234
# When going to another interpreter we get a copy.
197235
assert id(obj) != {id(obj1)}, 'expected: obj is not obj1'
198236
obj2 = b'eggs'
199237
print(id(obj2))
238+
assert queue2.qsize() == 0, 'expected: queue2.qsize() == 0'
200239
queue2.put(obj2)
240+
assert queue2.qsize() == 1, 'expected: queue2.qsize() == 1'
201241
"""))
202-
obj2 = queue2.get()
242+
self.assertEqual(len(queues.list_all()), 2)
243+
self.assertEqual(queue1.qsize(), 0)
244+
self.assertEqual(queue2.qsize(), 1)
203245

246+
obj2 = queue2.get()
204247
self.assertEqual(obj2, b'eggs')
205248
self.assertNotEqual(id(obj2), int(out))
206249

250+
def test_put_cleared_with_subinterpreter(self):
251+
interp = interpreters.create()
252+
queue = queues.create()
253+
254+
out = _run_output(
255+
interp,
256+
dedent(f"""
257+
from test.support.interpreters import queues
258+
queue = queues.Queue({queue.id})
259+
obj1 = b'spam'
260+
obj2 = b'eggs'
261+
queue.put(obj1)
262+
queue.put(obj2)
263+
"""))
264+
self.assertEqual(queue.qsize(), 2)
265+
266+
obj1 = queue.get()
267+
self.assertEqual(obj1, b'spam')
268+
self.assertEqual(queue.qsize(), 1)
269+
270+
del interp
271+
self.assertEqual(queue.qsize(), 0)
272+
207273
def test_put_get_different_threads(self):
208274
queue1 = queues.create()
209275
queue2 = queues.create()

0 commit comments

Comments
 (0)