Skip to content

Commit 4cb814c

Browse files
committed
asyncio, Tulip issue 220: Merge JoinableQueue with Queue.
Merge JoinableQueue with Queue. To more closely match the standard Queue, asyncio.Queue has "join" and "task_done". JoinableQueue is deleted. Docstring for Queue.join shouldn't mention threads. Restore JoinableQueue as a deprecated alias for Queue. To more closely match the standard Queue, asyncio.Queue has "join" and "task_done". JoinableQueue remains as a deprecated alias for Queue to avoid needlessly breaking too much code that depended on it. Patch written by A. Jesse Jiryu Davis <jesse@mongodb.com>.
1 parent 4e82fb9 commit 4cb814c

File tree

2 files changed

+48
-64
lines changed

2 files changed

+48
-64
lines changed

Lib/asyncio/queues.py

Lines changed: 43 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Queues"""
22

3-
__all__ = ['Queue', 'PriorityQueue', 'LifoQueue', 'JoinableQueue',
4-
'QueueFull', 'QueueEmpty']
3+
__all__ = ['Queue', 'PriorityQueue', 'LifoQueue', 'QueueFull', 'QueueEmpty',
4+
'JoinableQueue']
55

66
import collections
77
import heapq
@@ -49,6 +49,9 @@ def __init__(self, maxsize=0, *, loop=None):
4949
self._getters = collections.deque()
5050
# Pairs of (item, Future).
5151
self._putters = collections.deque()
52+
self._unfinished_tasks = 0
53+
self._finished = locks.Event(loop=self._loop)
54+
self._finished.set()
5255
self._init(maxsize)
5356

5457
def _init(self, maxsize):
@@ -59,6 +62,8 @@ def _get(self):
5962

6063
def _put(self, item):
6164
self._queue.append(item)
65+
self._unfinished_tasks += 1
66+
self._finished.clear()
6267

6368
def __repr__(self):
6469
return '<{} at {:#x} {}>'.format(
@@ -75,6 +80,8 @@ def _format(self):
7580
result += ' _getters[{}]'.format(len(self._getters))
7681
if self._putters:
7782
result += ' _putters[{}]'.format(len(self._putters))
83+
if self._unfinished_tasks:
84+
result += ' tasks={}'.format(self._unfinished_tasks)
7885
return result
7986

8087
def _consume_done_getters(self):
@@ -126,9 +133,6 @@ def put(self, item):
126133
'queue non-empty, why are getters waiting?')
127134

128135
getter = self._getters.popleft()
129-
130-
# Use _put and _get instead of passing item straight to getter, in
131-
# case a subclass has logic that must run (e.g. JoinableQueue).
132136
self._put(item)
133137

134138
# getter cannot be cancelled, we just removed done getters
@@ -154,9 +158,6 @@ def put_nowait(self, item):
154158
'queue non-empty, why are getters waiting?')
155159

156160
getter = self._getters.popleft()
157-
158-
# Use _put and _get instead of passing item straight to getter, in
159-
# case a subclass has logic that must run (e.g. JoinableQueue).
160161
self._put(item)
161162

162163
# getter cannot be cancelled, we just removed done getters
@@ -219,6 +220,38 @@ def get_nowait(self):
219220
else:
220221
raise QueueEmpty
221222

223+
def task_done(self):
224+
"""Indicate that a formerly enqueued task is complete.
225+
226+
Used by queue consumers. For each get() used to fetch a task,
227+
a subsequent call to task_done() tells the queue that the processing
228+
on the task is complete.
229+
230+
If a join() is currently blocking, it will resume when all items have
231+
been processed (meaning that a task_done() call was received for every
232+
item that had been put() into the queue).
233+
234+
Raises ValueError if called more times than there were items placed in
235+
the queue.
236+
"""
237+
if self._unfinished_tasks <= 0:
238+
raise ValueError('task_done() called too many times')
239+
self._unfinished_tasks -= 1
240+
if self._unfinished_tasks == 0:
241+
self._finished.set()
242+
243+
@coroutine
244+
def join(self):
245+
"""Block until all items in the queue have been gotten and processed.
246+
247+
The count of unfinished tasks goes up whenever an item is added to the
248+
queue. The count goes down whenever a consumer calls task_done() to
249+
indicate that the item was retrieved and all work on it is complete.
250+
When the count of unfinished tasks drops to zero, join() unblocks.
251+
"""
252+
if self._unfinished_tasks > 0:
253+
yield from self._finished.wait()
254+
222255

223256
class PriorityQueue(Queue):
224257
"""A subclass of Queue; retrieves entries in priority order (lowest first).
@@ -249,54 +282,5 @@ def _get(self):
249282
return self._queue.pop()
250283

251284

252-
class JoinableQueue(Queue):
253-
"""A subclass of Queue with task_done() and join() methods."""
254-
255-
def __init__(self, maxsize=0, *, loop=None):
256-
super().__init__(maxsize=maxsize, loop=loop)
257-
self._unfinished_tasks = 0
258-
self._finished = locks.Event(loop=self._loop)
259-
self._finished.set()
260-
261-
def _format(self):
262-
result = Queue._format(self)
263-
if self._unfinished_tasks:
264-
result += ' tasks={}'.format(self._unfinished_tasks)
265-
return result
266-
267-
def _put(self, item):
268-
super()._put(item)
269-
self._unfinished_tasks += 1
270-
self._finished.clear()
271-
272-
def task_done(self):
273-
"""Indicate that a formerly enqueued task is complete.
274-
275-
Used by queue consumers. For each get() used to fetch a task,
276-
a subsequent call to task_done() tells the queue that the processing
277-
on the task is complete.
278-
279-
If a join() is currently blocking, it will resume when all items have
280-
been processed (meaning that a task_done() call was received for every
281-
item that had been put() into the queue).
282-
283-
Raises ValueError if called more times than there were items placed in
284-
the queue.
285-
"""
286-
if self._unfinished_tasks <= 0:
287-
raise ValueError('task_done() called too many times')
288-
self._unfinished_tasks -= 1
289-
if self._unfinished_tasks == 0:
290-
self._finished.set()
291-
292-
@coroutine
293-
def join(self):
294-
"""Block until all items in the queue have been gotten and processed.
295-
296-
The count of unfinished tasks goes up whenever an item is added to the
297-
queue. The count goes down whenever a consumer thread calls task_done()
298-
to indicate that the item was retrieved and all work on it is complete.
299-
When the count of unfinished tasks drops to zero, join() unblocks.
300-
"""
301-
if self._unfinished_tasks > 0:
302-
yield from self._finished.wait()
285+
JoinableQueue = Queue
286+
"""Deprecated alias for Queue."""

Lib/test/test_asyncio/test_queues.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -408,14 +408,14 @@ def test_order(self):
408408
self.assertEqual([1, 2, 3], items)
409409

410410

411-
class JoinableQueueTests(_QueueTestBase):
411+
class QueueJoinTests(_QueueTestBase):
412412

413413
def test_task_done_underflow(self):
414-
q = asyncio.JoinableQueue(loop=self.loop)
414+
q = asyncio.Queue(loop=self.loop)
415415
self.assertRaises(ValueError, q.task_done)
416416

417417
def test_task_done(self):
418-
q = asyncio.JoinableQueue(loop=self.loop)
418+
q = asyncio.Queue(loop=self.loop)
419419
for i in range(100):
420420
q.put_nowait(i)
421421

@@ -452,7 +452,7 @@ def test():
452452
self.loop.run_until_complete(asyncio.wait(tasks, loop=self.loop))
453453

454454
def test_join_empty_queue(self):
455-
q = asyncio.JoinableQueue(loop=self.loop)
455+
q = asyncio.Queue(loop=self.loop)
456456

457457
# Test that a queue join()s successfully, and before anything else
458458
# (done twice for insurance).
@@ -465,7 +465,7 @@ def join():
465465
self.loop.run_until_complete(join())
466466

467467
def test_format(self):
468-
q = asyncio.JoinableQueue(loop=self.loop)
468+
q = asyncio.Queue(loop=self.loop)
469469
self.assertEqual(q._format(), 'maxsize=0')
470470

471471
q._unfinished_tasks = 2

0 commit comments

Comments
 (0)