Skip to content

Commit 397aabe

Browse files
committed
Backport gh-134173: optimize state transfer between concurrent.futures.Future and asyncio.Future
This is a backport of python/cpython#134174
1 parent 597ee65 commit 397aabe

File tree

1 file changed

+89
-0
lines changed

1 file changed

+89
-0
lines changed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
From 53da1e8c8ccbe3161ebc42e8b8b7ebd1ab70e05b Mon Sep 17 00:00:00 2001
2+
From: "J. Nick Koston" <[email protected]>
3+
Date: Sun, 18 May 2025 11:56:20 -0400
4+
Subject: [PATCH] gh-134173: optimize state transfer between
5+
`concurrent.futures.Future` and `asyncio.Future` (#134174)
6+
7+
Co-authored-by: Kumar Aditya <[email protected]>
8+
---
9+
Lib/asyncio/futures.py | 17 +++---
10+
Lib/concurrent/futures/_base.py | 27 +++++++++
11+
Lib/test/test_asyncio/test_futures.py | 58 +++++++++++++++++--
12+
.../test_concurrent_futures/test_future.py | 57 ++++++++++++++++++
13+
...-05-18-07-25-15.gh-issue-134173.53oOoF.rst | 3 +
14+
5 files changed, 148 insertions(+), 14 deletions(-)
15+
create mode 100644 Misc/NEWS.d/next/Library/2025-05-18-07-25-15.gh-issue-134173.53oOoF.rst
16+
17+
diff --git a/Lib/asyncio/futures.py b/Lib/asyncio/futures.py
18+
index d1df6707302..6bd00a64478 100644
19+
--- a/Lib/asyncio/futures.py
20+
+++ b/Lib/asyncio/futures.py
21+
@@ -351,22 +351,19 @@ def _set_concurrent_future_state(concurrent, source):
22+
def _copy_future_state(source, dest):
23+
"""Internal helper to copy state from another Future.
24+
25+
- The other Future may be a concurrent.futures.Future.
26+
+ The other Future must be a concurrent.futures.Future.
27+
"""
28+
- assert source.done()
29+
if dest.cancelled():
30+
return
31+
assert not dest.done()
32+
- if source.cancelled():
33+
+ done, cancelled, result, exception = source._get_snapshot()
34+
+ assert done
35+
+ if cancelled:
36+
dest.cancel()
37+
+ elif exception is not None:
38+
+ dest.set_exception(_convert_future_exc(exception))
39+
else:
40+
- exception = source.exception()
41+
- if exception is not None:
42+
- dest.set_exception(_convert_future_exc(exception))
43+
- else:
44+
- result = source.result()
45+
- dest.set_result(result)
46+
-
47+
+ dest.set_result(result)
48+
49+
def _chain_future(source, destination):
50+
"""Chain two futures so that when one completes, so does the other.
51+
diff --git a/Lib/concurrent/futures/_base.py b/Lib/concurrent/futures/_base.py
52+
index d98b1ebdd58..f506ce68aea 100644
53+
--- a/Lib/concurrent/futures/_base.py
54+
+++ b/Lib/concurrent/futures/_base.py
55+
@@ -558,6 +558,33 @@ def set_exception(self, exception):
56+
self._condition.notify_all()
57+
self._invoke_callbacks()
58+
59+
+ def _get_snapshot(self):
60+
+ """Get a snapshot of the future's current state.
61+
+
62+
+ This method atomically retrieves the state in one lock acquisition,
63+
+ which is significantly faster than multiple method calls.
64+
+
65+
+ Returns:
66+
+ Tuple of (done, cancelled, result, exception)
67+
+ - done: True if the future is done (cancelled or finished)
68+
+ - cancelled: True if the future was cancelled
69+
+ - result: The result if available and not cancelled
70+
+ - exception: The exception if available and not cancelled
71+
+ """
72+
+ # Fast path: check if already finished without lock
73+
+ if self._state == FINISHED:
74+
+ return True, False, self._result, self._exception
75+
+
76+
+ # Need lock for other states since they can change
77+
+ with self._condition:
78+
+ # We have to check the state again after acquiring the lock
79+
+ # because it may have changed in the meantime.
80+
+ if self._state == FINISHED:
81+
+ return True, False, self._result, self._exception
82+
+ if self._state in {CANCELLED, CANCELLED_AND_NOTIFIED}:
83+
+ return True, True, None, None
84+
+ return False, False, None, None
85+
+
86+
__class_getitem__ = classmethod(types.GenericAlias)
87+
88+
class Executor(object):
89+

0 commit comments

Comments
 (0)