gh-153005: Use a monotonic clock for concurrent.interpreters Queue timeouts (GH-154156)

serhiy-storchaka <[email protected]>
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
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]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.