update queue to 3.13.3

This commit is contained in:
Ashwin Naren committed 2025-04-21 20:48:13 -07:00
1 parent a5214a0de7
commit 6567d1d6ec
2 files changed
+475 -1

No files matched your search

+59 -1
View File
@@ -10,7 +10,15 @@ try:
except ImportError:
SimpleQueue = None
__all__ = ['Empty', 'Full', 'Queue', 'PriorityQueue', 'LifoQueue', 'SimpleQueue']
__all__ = [
'Empty',
'Full',
'ShutDown',
'Queue',
'PriorityQueue',
'LifoQueue',
'SimpleQueue',
]
try:
@@ -25,6 +33,10 @@ class Full(Exception):
pass
class ShutDown(Exception):
'''Raised when put/get with shut-down queue.'''
class Queue:
'''Create a queue object with a given maximum size.
@@ -54,6 +66,9 @@ class Queue:
self.all_tasks_done = threading.Condition(self.mutex)
self.unfinished_tasks = 0
# Queue shutdown state
self.is_shutdown = False
def task_done(self):
'''Indicate that a formerly enqueued task is complete.
@@ -65,6 +80,9 @@ class Queue:
have been processed (meaning that a task_done() call was received
for every item that had been put() into the queue).
shutdown(immediate=True) calls task_done() for each remaining item in
the queue.
Raises a ValueError if called more times than there were items
placed in the queue.
'''
@@ -129,8 +147,12 @@ class Queue:
Otherwise ('block' is false), put an item on the queue if a free slot
is immediately available, else raise the Full exception ('timeout'
is ignored in that case).
Raises ShutDown if the queue has been shut down.
'''
with self.not_full:
if self.is_shutdown:
raise ShutDown
if self.maxsize > 0:
if not block:
if self._qsize() >= self.maxsize:
@@ -138,6 +160,8 @@ class Queue:
elif timeout is None:
while self._qsize() >= self.maxsize:
self.not_full.wait()
if self.is_shutdown:
raise ShutDown
elif timeout < 0:
raise ValueError("'timeout' must be a non-negative number")
else:
@@ -147,6 +171,8 @@ class Queue:
if remaining <= 0.0:
raise Full
self.not_full.wait(remaining)
if self.is_shutdown:
raise ShutDown
self._put(item)
self.unfinished_tasks += 1
self.not_empty.notify()
@@ -161,14 +187,21 @@ class Queue:
Otherwise ('block' is false), return an item if one is immediately
available, else raise the Empty exception ('timeout' is ignored
in that case).
Raises ShutDown if the queue has been shut down and is empty,
or if the queue has been shut down immediately.
'''
with self.not_empty:
if self.is_shutdown and not self._qsize():
raise ShutDown
if not block:
if not self._qsize():
raise Empty
elif timeout is None:
while not self._qsize():
self.not_empty.wait()
if self.is_shutdown and not self._qsize():
raise ShutDown
elif timeout < 0:
raise ValueError("'timeout' must be a non-negative number")
else:
@@ -178,6 +211,8 @@ class Queue:
if remaining <= 0.0:
raise Empty
self.not_empty.wait(remaining)
if self.is_shutdown and not self._qsize():
raise ShutDown
item = self._get()
self.not_full.notify()
return item
@@ -198,6 +233,29 @@ class Queue:
'''
return self.get(block=False)
def shutdown(self, immediate=False):
'''Shut-down the queue, making queue gets and puts raise ShutDown.
By default, gets will only raise once the queue is empty. Set
'immediate' to True to make gets raise immediately instead.
All blocked callers of put() and get() will be unblocked. If
'immediate', a task is marked as done for each item remaining in
the queue, which may unblock callers of join().
'''
with self.mutex:
self.is_shutdown = True
if immediate:
while self._qsize():
self._get()
if self.unfinished_tasks > 0:
self.unfinished_tasks -= 1
# release all blocked threads in `join()`
self.all_tasks_done.notify_all()
# All getters need to re-check queue-empty to raise ShutDown
self.not_empty.notify_all()
self.not_full.notify_all()
# Override these methods to implement other queue organizations
# (e.g. stack or priority queue).
# These will only be called with appropriate locks held
+416
View File
@@ -2,6 +2,7 @@
# to ensure the Queue locks remain stable.
import itertools
import random
import sys
import threading
import time
import unittest
@@ -10,6 +11,8 @@ from test.support import gc_collect
from test.support import import_helper
from test.support import threading_helper
# queue module depends on threading primitives
threading_helper.requires_working_threading(module=True)
py_queue = import_helper.import_fresh_module('queue', blocked=['_queue'])
c_queue = import_helper.import_fresh_module('queue', fresh=['_queue'])
@@ -239,6 +242,418 @@ class BaseQueueTestMixin(BlockingTestMixin):
with self.assertRaises(self.queue.Full):
q.put_nowait(4)
def test_shutdown_empty(self):
q = self.type2test()
q.shutdown()
with self.assertRaises(self.queue.ShutDown):
q.put("data")
with self.assertRaises(self.queue.ShutDown):
q.get()
def test_shutdown_nonempty(self):
q = self.type2test()
q.put("data")
q.shutdown()
q.get()
with self.assertRaises(self.queue.ShutDown):
q.get()
def test_shutdown_immediate(self):
q = self.type2test()
q.put("data")
q.shutdown(immediate=True)
with self.assertRaises(self.queue.ShutDown):
q.get()
def test_shutdown_allowed_transitions(self):
# allowed transitions would be from alive via shutdown to immediate
q = self.type2test()
self.assertFalse(q.is_shutdown)
q.shutdown()
self.assertTrue(q.is_shutdown)
q.shutdown(immediate=True)
self.assertTrue(q.is_shutdown)
q.shutdown(immediate=False)
def _shutdown_all_methods_in_one_thread(self, immediate):
q = self.type2test(2)
q.put("L")
q.put_nowait("O")
q.shutdown(immediate)
with self.assertRaises(self.queue.ShutDown):
q.put("E")
with self.assertRaises(self.queue.ShutDown):
q.put_nowait("W")
if immediate:
with self.assertRaises(self.queue.ShutDown):
q.get()
with self.assertRaises(self.queue.ShutDown):
q.get_nowait()
with self.assertRaises(ValueError):
q.task_done()
q.join()
else:
self.assertIn(q.get(), "LO")
q.task_done()
self.assertIn(q.get(), "LO")
q.task_done()
q.join()
# on shutdown(immediate=False)
# when queue is empty, should raise ShutDown Exception
with self.assertRaises(self.queue.ShutDown):
q.get() # p.get(True)
with self.assertRaises(self.queue.ShutDown):
q.get_nowait() # p.get(False)
with self.assertRaises(self.queue.ShutDown):
q.get(True, 1.0)
def test_shutdown_all_methods_in_one_thread(self):
return self._shutdown_all_methods_in_one_thread(False)
def test_shutdown_immediate_all_methods_in_one_thread(self):
return self._shutdown_all_methods_in_one_thread(True)
def _write_msg_thread(self, q, n, results,
i_when_exec_shutdown, event_shutdown,
barrier_start):
# All `write_msg_threads`
# put several items into the queue.
for i in range(0, i_when_exec_shutdown//2):
q.put((i, 'LOYD'))
# Wait for the barrier to be complete.
barrier_start.wait()
for i in range(i_when_exec_shutdown//2, n):
try:
q.put((i, "YDLO"))
except self.queue.ShutDown:
results.append(False)
break
# Trigger queue shutdown.
if i == i_when_exec_shutdown:
# Only one thread should call shutdown().
if not event_shutdown.is_set():
event_shutdown.set()
results.append(True)
def _read_msg_thread(self, q, results, barrier_start):
# Get at least one item.
q.get(True)
q.task_done()
# Wait for the barrier to be complete.
barrier_start.wait()
while True:
try:
q.get(False)
q.task_done()
except self.queue.ShutDown:
results.append(True)
break
except self.queue.Empty:
pass
def _shutdown_thread(self, q, results, event_end, immediate):
event_end.wait()
q.shutdown(immediate)
results.append(q.qsize() == 0)
def _join_thread(self, q, barrier_start):
# Wait for the barrier to be complete.
barrier_start.wait()
q.join()
def _shutdown_all_methods_in_many_threads(self, immediate):
# Run a 'multi-producers/consumers queue' use case,
# with enough items into the queue.
# When shutdown, all running threads will be joined.
q = self.type2test()
ps = []
res_puts = []
res_gets = []
res_shutdown = []
write_threads = 4
read_threads = 6
join_threads = 2
nb_msgs = 1024*64
nb_msgs_w = nb_msgs // write_threads
when_exec_shutdown = nb_msgs_w // 2
# Use of a Barrier to ensure that
# - all write threads put all their items into the queue,
# - all read thread get at least one item from the queue,
# and keep on running until shutdown.
# The join thread is started only when shutdown is immediate.
nparties = write_threads + read_threads
if immediate:
nparties += join_threads
barrier_start = threading.Barrier(nparties)
ev_exec_shutdown = threading.Event()
lprocs = [
(self._write_msg_thread, write_threads, (q, nb_msgs_w, res_puts,
when_exec_shutdown, ev_exec_shutdown,
barrier_start)),
(self._read_msg_thread, read_threads, (q, res_gets, barrier_start)),
(self._shutdown_thread, 1, (q, res_shutdown, ev_exec_shutdown, immediate)),
]
if immediate:
lprocs.append((self._join_thread, join_threads, (q, barrier_start)))
# start all threads.
for func, n, args in lprocs:
for i in range(n):
ps.append(threading.Thread(target=func, args=args))
ps[-1].start()
for thread in ps:
thread.join()
self.assertTrue(True in res_puts)
self.assertEqual(res_gets.count(True), read_threads)
if immediate:
self.assertListEqual(res_shutdown, [True])
self.assertTrue(q.empty())
def test_shutdown_all_methods_in_many_threads(self):
return self._shutdown_all_methods_in_many_threads(False)
def test_shutdown_immediate_all_methods_in_many_threads(self):
return self._shutdown_all_methods_in_many_threads(True)
def _get(self, q, go, results, shutdown=False):
go.wait()
try:
msg = q.get()
results.append(not shutdown)
return not shutdown
except self.queue.ShutDown:
results.append(shutdown)
return shutdown
def _get_shutdown(self, q, go, results):
return self._get(q, go, results, True)
def _get_task_done(self, q, go, results):
go.wait()
try:
msg = q.get()
q.task_done()
results.append(True)
return msg
except self.queue.ShutDown:
results.append(False)
return False
def _put(self, q, msg, go, results, shutdown=False):
go.wait()
try:
q.put(msg)
results.append(not shutdown)
return not shutdown
except self.queue.ShutDown:
results.append(shutdown)
return shutdown
def _put_shutdown(self, q, msg, go, results):
return self._put(q, msg, go, results, True)
def _join(self, q, results, shutdown=False):
try:
q.join()
results.append(not shutdown)
return not shutdown
except self.queue.ShutDown:
results.append(shutdown)
return shutdown
def _join_shutdown(self, q, results):
return self._join(q, results, True)
def _shutdown_get(self, immediate):
q = self.type2test(2)
results = []
go = threading.Event()
q.put("Y")
q.put("D")
# queue full
if immediate:
thrds = (
(self._get_shutdown, (q, go, results)),
(self._get_shutdown, (q, go, results)),
)
else:
thrds = (
# on shutdown(immediate=False)
# one of these threads should raise Shutdown
(self._get, (q, go, results)),
(self._get, (q, go, results)),
(self._get, (q, go, results)),
)
threads = []
for func, params in thrds:
threads.append(threading.Thread(target=func, args=params))
threads[-1].start()
q.shutdown(immediate)
go.set()
for t in threads:
t.join()
if immediate:
self.assertListEqual(results, [True, True])
else:
self.assertListEqual(sorted(results), [False] + [True]*(len(thrds)-1))
def test_shutdown_get(self):
return self._shutdown_get(False)
def test_shutdown_immediate_get(self):
return self._shutdown_get(True)
def _shutdown_put(self, immediate):
q = self.type2test(2)
results = []
go = threading.Event()
q.put("Y")
q.put("D")
# queue fulled
thrds = (
(self._put_shutdown, (q, "E", go, results)),
(self._put_shutdown, (q, "W", go, results)),
)
threads = []
for func, params in thrds:
threads.append(threading.Thread(target=func, args=params))
threads[-1].start()
q.shutdown()
go.set()
for t in threads:
t.join()
self.assertEqual(results, [True]*len(thrds))
def test_shutdown_put(self):
return self._shutdown_put(False)
def test_shutdown_immediate_put(self):
return self._shutdown_put(True)
def _shutdown_join(self, immediate):
q = self.type2test()
results = []
q.put("Y")
go = threading.Event()
nb = q.qsize()
thrds = (
(self._join, (q, results)),
(self._join, (q, results)),
)
threads = []
for func, params in thrds:
threads.append(threading.Thread(target=func, args=params))
threads[-1].start()
if not immediate:
res = []
for i in range(nb):
threads.append(threading.Thread(target=self._get_task_done, args=(q, go, res)))
threads[-1].start()
q.shutdown(immediate)
go.set()
for t in threads:
t.join()
self.assertEqual(results, [True]*len(thrds))
def test_shutdown_immediate_join(self):
return self._shutdown_join(True)
def test_shutdown_join(self):
return self._shutdown_join(False)
def _shutdown_put_join(self, immediate):
q = self.type2test(2)
results = []
go = threading.Event()
q.put("Y")
# queue not fulled
thrds = (
(self._put_shutdown, (q, "E", go, results)),
(self._join, (q, results)),
)
threads = []
for func, params in thrds:
threads.append(threading.Thread(target=func, args=params))
threads[-1].start()
self.assertEqual(q.unfinished_tasks, 1)
q.shutdown(immediate)
go.set()
if immediate:
with self.assertRaises(self.queue.ShutDown):
q.get_nowait()
else:
result = q.get()
self.assertEqual(result, "Y")
q.task_done()
for t in threads:
t.join()
self.assertEqual(results, [True]*len(thrds))
def test_shutdown_immediate_put_join(self):
return self._shutdown_put_join(True)
def test_shutdown_put_join(self):
return self._shutdown_put_join(False)
def test_shutdown_get_task_done_join(self):
q = self.type2test(2)
results = []
go = threading.Event()
q.put("Y")
q.put("D")
self.assertEqual(q.unfinished_tasks, q.qsize())
thrds = (
(self._get_task_done, (q, go, results)),
(self._get_task_done, (q, go, results)),
(self._join, (q, results)),
(self._join, (q, results)),
)
threads = []
for func, params in thrds:
threads.append(threading.Thread(target=func, args=params))
threads[-1].start()
go.set()
q.shutdown(False)
for t in threads:
t.join()
self.assertEqual(results, [True]*len(thrds))
def test_shutdown_pending_get(self):
def get():
try:
results.append(q.get())
except Exception as e:
results.append(e)
q = self.type2test()
results = []
get_thread = threading.Thread(target=get)
get_thread.start()
q.shutdown(immediate=False)
get_thread.join(timeout=10.0)
self.assertFalse(get_thread.is_alive())
self.assertEqual(len(results), 1)
self.assertIsInstance(results[0], self.queue.ShutDown)
class QueueTest(BaseQueueTestMixin):
def setUp(self):
@@ -289,6 +704,7 @@ class CPriorityQueueTest(PriorityQueueTest, unittest.TestCase):
# A Queue subclass that can provoke failure at a moment's notice :)
class FailingQueueException(Exception): pass
class FailingQueueTest(BlockingTestMixin):
def setUp(self):