https://github.com/python/cpython/commit/b94b9c8886a987a324a677b5fda5bef27f15cb14
commit: b94b9c8886a987a324a677b5fda5bef27f15cb14
branch: main
author: Vyron Vasileiadis <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-17T19:20:07Z
summary:
gh-153005: Use a monotonic clock for concurrent.interpreters Queue timeouts
(GH-154156)
Queue.get() and Queue.put() computed their timeout deadline from
time.time(), the wall clock. If the system clock was stepped (NTP, a
manual change) while a call was blocked, the timeout could over- or
under-wait. queue.Queue uses time.monotonic() for the same reason.
Compute the deadline and check it against time.monotonic() instead.
files:
A Misc/NEWS.d/next/Library/2026-07-19-17-45-00.gh-issue-153005.F6WH92.rst
M Lib/concurrent/interpreters/_queues.py
M Lib/test/test_interpreters/test_queues.py
diff --git a/Lib/concurrent/interpreters/_queues.py
b/Lib/concurrent/interpreters/_queues.py
index 5f3ee0934de59d6..fc4ee595f3aa995 100644
--- a/Lib/concurrent/interpreters/_queues.py
+++ b/Lib/concurrent/interpreters/_queues.py
@@ -220,12 +220,12 @@ def put(self, obj, block=True, timeout=None, *,
timeout = int(timeout)
if timeout < 0:
raise ValueError(f'timeout value must be non-negative')
- end = time.time() + timeout
+ end = time.monotonic() + timeout
while True:
try:
_queues.put(self._id, obj, unboundop)
except QueueFull:
- if timeout is not None and time.time() >= end:
+ if timeout is not None and time.monotonic() >= end:
raise # re-raise
time.sleep(_delay)
else:
@@ -255,12 +255,12 @@ def get(self, block=True, timeout=None, *,
timeout = int(timeout)
if timeout < 0:
raise ValueError(f'timeout value must be non-negative')
- end = time.time() + timeout
+ end = time.monotonic() + timeout
while True:
try:
obj, unboundop = _queues.get(self._id)
except QueueEmpty:
- if timeout is not None and time.time() >= end:
+ if timeout is not None and time.monotonic() >= end:
raise # re-raise
time.sleep(_delay)
else:
diff --git a/Lib/test/test_interpreters/test_queues.py
b/Lib/test/test_interpreters/test_queues.py
index 77334aea3836b98..baa772b3b367952 100644
--- a/Lib/test/test_interpreters/test_queues.py
+++ b/Lib/test/test_interpreters/test_queues.py
@@ -2,7 +2,9 @@
import pickle
import threading
from textwrap import dedent
+import time
import unittest
+from unittest import mock
from test.support import import_helper, Py_DEBUG
# Raise SkipTest if subinterpreters not supported.
@@ -354,6 +356,19 @@ def test_get_timeout(self):
with self.assertRaises(queues.QueueEmpty):
queue.get(HUGE_TIMEOUT, 0.1)
+ def test_timeout_uses_monotonic_clock(self):
+ # gh-153005: the deadline must be computed from the monotonic clock,
+ # since the wall clock can be adjusted while the call is blocked.
+ queue = queues.create(1)
+ with mock.patch.object(queues, 'time', wraps=time) as fake_time:
+ with self.assertRaises(queues.QueueEmpty):
+ queue.get(timeout=0)
+ queue.put(None)
+ with self.assertRaises(queues.QueueFull):
+ queue.put(None, timeout=0)
+ fake_time.monotonic.assert_called()
+ fake_time.time.assert_not_called()
+
def test_get_nowait(self):
queue = queues.create()
with self.assertRaises(queues.QueueEmpty):
diff --git
a/Misc/NEWS.d/next/Library/2026-07-19-17-45-00.gh-issue-153005.F6WH92.rst
b/Misc/NEWS.d/next/Library/2026-07-19-17-45-00.gh-issue-153005.F6WH92.rst
new file mode 100644
index 000000000000000..7f3d10b3072ef94
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-07-19-17-45-00.gh-issue-153005.F6WH92.rst
@@ -0,0 +1,4 @@
+:meth:`!concurrent.interpreters.Queue.get` and
+:meth:`!concurrent.interpreters.Queue.put` now compute their ``timeout``
+deadline from :func:`time.monotonic` instead of the wall clock, so adjusting
+the system clock during the call no longer makes them over- or under-wait.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]