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

serhiy-storchaka <[email protected]>
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
https://github.com/python/cpython/commit/b205580c37b765f212b961c3f1119ba74f5a7099
commit: b205580c37b765f212b961c3f1119ba74f5a7099
branch: 3.13
author: Bhuvansh <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-20T19:46:38+03:00
summary:

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

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.

(cherry picked from commit b94b9c8886a987a324a677b5fda5bef27f15cb14)

Co-authored-by: Vyron Vasileiadis <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-07-19-17-45-00.gh-issue-153005.F6WH92.rst
M Lib/test/support/interpreters/queues.py
M Lib/test/test_interpreters/test_queues.py

diff --git a/Lib/test/support/interpreters/queues.py b/Lib/test/support/interpreters/queues.py
index deb8e8613af731..c5b1fc9dd31bbf 100644
--- a/Lib/test/support/interpreters/queues.py
+++ b/Lib/test/support/interpreters/queues.py
@@ -230,14 +230,14 @@ def put(self, obj, 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
         if fmt is _PICKLED:
             obj = pickle.dumps(obj)
         while True:
             try:
                 _queues.put(self._id, obj, fmt, unboundop)
             except QueueFull as exc:
-                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:
@@ -271,12 +271,12 @@ def get(self, 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, fmt, unboundop = _queues.get(self._id)
             except QueueEmpty as exc:
-                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 7858c4e602ce2b..f05ef46f761b57 100644
--- a/Lib/test/test_interpreters/test_queues.py
+++ b/Lib/test/test_interpreters/test_queues.py
@@ -2,8 +2,9 @@
 import pickle
 import threading
 from textwrap import dedent
-import unittest
 import time
+import unittest
+from unittest import mock
 
 from test.support import import_helper, Py_DEBUG
 # Raise SkipTest if subinterpreters not supported.
@@ -383,6 +384,19 @@ def test_get_timeout(self):
         with self.assertRaises(queues.QueueEmpty):
             queue.get(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 00000000000000..7f3d10b3072ef9
--- /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.