bpo-34819: Use a monotonic clock to compute timeouts in concurrent.futures (GH-9599)
Use a monotonic clock to compute timeouts in :meth:`Executor.map` and :func:`as_completed`, in order to prevent timeouts from deviating when the system clock is adjusted.
This may not be sufficient on all systems. On POSIX for example, the actual waiting (e.g. in ``sem_timedwait``) is specified to rely on the CLOCK_REALTIME clock.
(cherry picked from commit a94ee12c26aa8dd7dce01373779df8055aff765b)
Co-authored-by: orlnub123 <orlnub123@gmail.com>
diff --git a/Lib/concurrent/futures/_base.py b/Lib/concurrent/futures/_base.py
index 4f22f7e..61c81bb 100644
--- a/Lib/concurrent/futures/_base.py
+++ b/Lib/concurrent/futures/_base.py
@@ -212,7 +212,7 @@
before the given timeout.
"""
if timeout is not None:
- end_time = timeout + time.time()
+ end_time = timeout + time.monotonic()
fs = set(fs)
total_futures = len(fs)
@@ -231,7 +231,7 @@
if timeout is None:
wait_timeout = None
else:
- wait_timeout = end_time - time.time()
+ wait_timeout = end_time - time.monotonic()
if wait_timeout < 0:
raise TimeoutError(
'%d (of %d) futures unfinished' % (
@@ -570,7 +570,7 @@
Exception: If fn(*args) raises for any values.
"""
if timeout is not None:
- end_time = timeout + time.time()
+ end_time = timeout + time.monotonic()
fs = [self.submit(fn, *args) for args in zip(*iterables)]
@@ -585,7 +585,7 @@
if timeout is None:
yield fs.pop().result()
else:
- yield fs.pop().result(end_time - time.time())
+ yield fs.pop().result(end_time - time.monotonic())
finally:
for future in fs:
future.cancel()
diff --git a/Misc/NEWS.d/next/Library/2018-09-27-09-45-00.bpo-34819.9ZaFyO.rst b/Misc/NEWS.d/next/Library/2018-09-27-09-45-00.bpo-34819.9ZaFyO.rst
new file mode 100644
index 0000000..6bbdea2
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2018-09-27-09-45-00.bpo-34819.9ZaFyO.rst
@@ -0,0 +1 @@
+Use a monotonic clock to compute timeouts in :meth:`Executor.map` and :func:`as_completed`, in order to prevent timeouts from deviating when the system clock is adjusted.