Skip to content

Commit a49b427

Browse files
gh-76785: More Fixes for test.support.interpreters (gh-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 cde1417 commit a49b427

File tree

13 files changed

+1899
-88
lines changed

13 files changed

+1899
-88
lines changed

Lib/test/support/interpreters/queues.py

Lines changed: 87 additions & 71 deletions
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

Lines changed: 81 additions & 15 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy