diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index afb922a7..b99795f0 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -217,7 +217,14 @@ jobs: matrix: arch: ${{ fromJson(needs.init.outputs.architectures_alpine) }} version: ["3.20", "3.21", "3.22"] - python: ["3.11", "3.12", "3.13"] + python: ["3.12", "3.13", "3.14"] + exclude: + - python: "3.14" + arch: "armv7" + - python: "3.14" + arch: "armhf" + - python: "3.14" + arch: "i386" steps: - *checkout - *login-container-registry diff --git a/README.md b/README.md index 0b35894d..1724530a 100644 --- a/README.md +++ b/README.md @@ -27,13 +27,17 @@ We support on our platforms jemalloc. On the application which you want to enabl We support the latest 3 release with the latest 3 Alpine version. +> [!NOTE] +> Python 3.14 will only be available on `aarch64` and `amd64` following the deprecation of `armhf`, `armv7` and `i386`. +> See https://www.home-assistant.io/blog/2025/05/22/deprecating-core-and-supervised-installation-methods-and-32-bit-systems/ + | Image | OS | Python versions | Tags | latest | |-------|----|-----------------|------|--------| -| armhf-base-python | Alpine | 3.11, 3.12, 3.13 | 3.11-alpine3.20, 3.11-alpine3.21, 3.11-alpine3.22, 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22 | 3.13-alpine3.22 | -| armv7-base-python | Alpine | 3.11, 3.12, 3.13 | 3.11-alpine3.20, 3.11-alpine3.21, 3.11-alpine3.22, 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22 | 3.13-alpine3.22 | -| aarch64-base-python | Alpine | 3.11, 3.12, 3.13 | 3.11-alpine3.20, 3.11-alpine3.21, 3.11-alpine3.22, 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22 | 3.13-alpine3.22 | -| amd64-base-python | Alpine | 3.11, 3.12, 3.13 | 3.11-alpine3.20, 3.11-alpine3.21, 3.11-alpine3.22, 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22 | 3.13-alpine3.22 | -| i386-base-python | Alpine | 3.11, 3.12, 3.13 | 3.11-alpine3.20, 3.11-alpine3.21, 3.11-alpine3.22, 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22 | 3.13-alpine3.22 | +| armhf-base-python | Alpine | 3.12, 3.13 | 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22 | 3.13-alpine3.22 | +| armv7-base-python | Alpine | 3.12, 3.13 | 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22 | 3.13-alpine3.22 | +| aarch64-base-python | Alpine | 3.12, 3.13, 3.14 | 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22, 3.14-alpine3.20, 3.14-alpine3.21, 3.14-alpine3.22 | 3.13-alpine3.22 | +| amd64-base-python | Alpine | 3.12, 3.13, 3.14 | 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22, 3.14-alpine3.20, 3.14-alpine3.21, 3.14-alpine3.22 | 3.13-alpine3.22 | +| i386-base-python | Alpine | 3.12, 3.13 | 3.12-alpine3.20, 3.12-alpine3.21, 3.12-alpine3.22, 3.13-alpine3.20, 3.13-alpine3.21, 3.13-alpine3.22 | 3.13-alpine3.22 | ## Others diff --git a/python/3.11/arm-alignment.patch b/python/3.11/arm-alignment.patch deleted file mode 100644 index a7a4b394..00000000 --- a/python/3.11/arm-alignment.patch +++ /dev/null @@ -1,17 +0,0 @@ -Author: Dave Jones -Description: Use aligned access for _sha3 module on ARM. ---- a/Modules/_sha3/sha3module.c -+++ b/Modules/_sha3/sha3module.c -@@ -64,6 +64,12 @@ - #define PLATFORM_BYTE_ORDER IS_BIG_ENDIAN - #endif - -+/* Bus error on 32-bit ARM due to un-aligned memory accesses; 64-bit ARM -+ * doesn't complain but un-aligned memory accesses are sub-optimal */ -+#if defined(__arm__) || defined(__aarch64__) -+#define NO_MISALIGNED_ACCESSES -+#endif -+ - /* mangle names */ - #define KeccakF1600_FastLoop_Absorb _PySHA3_KeccakF1600_FastLoop_Absorb - #define Keccak_HashFinal _PySHA3_Keccak_HashFinal diff --git a/python/3.11/asynctio_unix_events.patch b/python/3.11/asynctio_unix_events.patch deleted file mode 100644 index 626198dd..00000000 --- a/python/3.11/asynctio_unix_events.patch +++ /dev/null @@ -1,16 +0,0 @@ -diff --git a/Lib/asyncio/unix_events.py b/Lib/asyncio/unix_events.py -index f34a5b4b44..b1d0f1e61e 100644 ---- a/Lib/asyncio/unix_events.py -+++ b/Lib/asyncio/unix_events.py -@@ -369,6 +369,11 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop): - fut.set_result(total_sent) - return - -+ # On 32-bit architectures truncate to 1GiB to avoid OverflowError, -+ # see bpo-38319. -+ if sys.maxsize < 2 ** 32: -+ blocksize = min(blocksize, 2 ** 30) -+ - try: - sent = os.sendfile(fd, fileno, offset, blocksize) - except (BlockingIOError, InterruptedError): \ No newline at end of file diff --git a/python/3.11/gh-106527-asyncio-optimize-to-add-remove-readers-and.patch b/python/3.11/gh-106527-asyncio-optimize-to-add-remove-readers-and.patch deleted file mode 100644 index 29f94f64..00000000 --- a/python/3.11/gh-106527-asyncio-optimize-to-add-remove-readers-and.patch +++ /dev/null @@ -1,113 +0,0 @@ -From b7dc795dfd175c0d25a479cfaf94a13c368a5a7b Mon Sep 17 00:00:00 2001 -From: "J. Nick Koston" -Date: Sat, 22 Jul 2023 16:07:40 -0500 -Subject: [PATCH] gh-106527: asyncio: optimize to add/remove readers and - writers (#106528) - ---- - Lib/asyncio/selector_events.py | 64 +++++++++---------- - Lib/test/test_asyncio/test_selector_events.py | 36 +++++------ - 2 files changed, 47 insertions(+), 53 deletions(-) - -diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py -index f895750e3c..d521b4e2e2 100644 ---- a/Lib/asyncio/selector_events.py -+++ b/Lib/asyncio/selector_events.py -@@ -274,9 +274,8 @@ def _ensure_fd_no_transport(self, fd): - def _add_reader(self, fd, callback, *args): - self._check_closed() - handle = events.Handle(callback, args, self, None) -- try: -- key = self._selector.get_key(fd) -- except KeyError: -+ key = self._selector.get_map().get(fd) -+ if key is None: - self._selector.register(fd, selectors.EVENT_READ, - (handle, None)) - else: -@@ -290,30 +289,27 @@ def _add_reader(self, fd, callback, *args): - def _remove_reader(self, fd): - if self.is_closed(): - return False -- try: -- key = self._selector.get_key(fd) -- except KeyError: -+ key = self._selector.get_map().get(fd) -+ if key is None: - return False -+ mask, (reader, writer) = key.events, key.data -+ mask &= ~selectors.EVENT_READ -+ if not mask: -+ self._selector.unregister(fd) - else: -- mask, (reader, writer) = key.events, key.data -- mask &= ~selectors.EVENT_READ -- if not mask: -- self._selector.unregister(fd) -- else: -- self._selector.modify(fd, mask, (None, writer)) -+ self._selector.modify(fd, mask, (None, writer)) - -- if reader is not None: -- reader.cancel() -- return True -- else: -- return False -+ if reader is not None: -+ reader.cancel() -+ return True -+ else: -+ return False - - def _add_writer(self, fd, callback, *args): - self._check_closed() - handle = events.Handle(callback, args, self, None) -- try: -- key = self._selector.get_key(fd) -- except KeyError: -+ key = self._selector.get_map().get(fd) -+ if key is None: - self._selector.register(fd, selectors.EVENT_WRITE, - (None, handle)) - else: -@@ -328,24 +324,22 @@ def _remove_writer(self, fd): - """Remove a writer callback.""" - if self.is_closed(): - return False -- try: -- key = self._selector.get_key(fd) -- except KeyError: -+ key = self._selector.get_map().get(fd) -+ if key is None: - return False -+ mask, (reader, writer) = key.events, key.data -+ # Remove both writer and connector. -+ mask &= ~selectors.EVENT_WRITE -+ if not mask: -+ self._selector.unregister(fd) - else: -- mask, (reader, writer) = key.events, key.data -- # Remove both writer and connector. -- mask &= ~selectors.EVENT_WRITE -- if not mask: -- self._selector.unregister(fd) -- else: -- self._selector.modify(fd, mask, (reader, None)) -+ self._selector.modify(fd, mask, (reader, None)) - -- if writer is not None: -- writer.cancel() -- return True -- else: -- return False -+ if writer is not None: -+ writer.cancel() -+ return True -+ else: -+ return False - - def add_reader(self, fd, callback, *args): - """Add a reader callback.""" --- -2.39.2 (Apple Git-143) - diff --git a/python/3.11/gh-106554-replace-_BaseSelectorImpl._key_from_fd-wit.patch b/python/3.11/gh-106554-replace-_BaseSelectorImpl._key_from_fd-wit.patch deleted file mode 100644 index e742d485..00000000 --- a/python/3.11/gh-106554-replace-_BaseSelectorImpl._key_from_fd-wit.patch +++ /dev/null @@ -1,73 +0,0 @@ -From aeef8591e41b68341af308e56a744396c66879cc Mon Sep 17 00:00:00 2001 -From: "J. Nick Koston" -Date: Fri, 14 Jul 2023 08:46:30 -1000 -Subject: [PATCH] gh-106554: replace `_BaseSelectorImpl._key_from_fd` with - `dict.get` (#106555) - ---- - Lib/selectors.py | 21 ++++----------------- - 1 file changed, 4 insertions(+), 17 deletions(-) - -diff --git a/Lib/selectors.py b/Lib/selectors.py -index dfcc125dcd..6d82935445 100644 ---- a/Lib/selectors.py -+++ b/Lib/selectors.py -@@ -276,19 +276,6 @@ def close(self): - def get_map(self): - return self._map - -- def _key_from_fd(self, fd): -- """Return the key associated to a given file descriptor. -- -- Parameters: -- fd -- file descriptor -- -- Returns: -- corresponding key, or None if not found -- """ -- try: -- return self._fd_to_key[fd] -- except KeyError: -- return None - - - class SelectSelector(_BaseSelectorImpl): -@@ -336,7 +323,7 @@ def select(self, timeout=None): - if fd in w: - events |= EVENT_WRITE - -- key = self._key_from_fd(fd) -+ key = self._fd_to_key.get(fd) - if key: - ready.append((key, events & key.events)) - return ready -@@ -426,7 +413,7 @@ def select(self, timeout=None): - if event & ~self._EVENT_WRITE: - events |= EVENT_READ - -- key = self._key_from_fd(fd) -+ key = self._fd_to_key.get(fd) - if key: - ready.append((key, events & key.events)) - return ready -@@ -479,7 +466,7 @@ def select(self, timeout=None): - if event & ~select.EPOLLOUT: - events |= EVENT_READ - -- key = self._key_from_fd(fd) -+ key = self._fd_to_key.get(fd) - if key: - ready.append((key, events & key.events)) - return ready -@@ -574,7 +561,7 @@ def select(self, timeout=None): - if flag == select.KQ_FILTER_WRITE: - events |= EVENT_WRITE - -- key = self._key_from_fd(fd) -+ key = self._fd_to_key.get(fd) - if key: - ready.append((key, events & key.events)) - return ready --- -2.39.2 (Apple Git-143) - diff --git a/python/3.11/gh-106664-selectors-add-get-method-to-_SelectorMappi.patch b/python/3.11/gh-106664-selectors-add-get-method-to-_SelectorMappi.patch deleted file mode 100644 index b1ec461e..00000000 --- a/python/3.11/gh-106664-selectors-add-get-method-to-_SelectorMappi.patch +++ /dev/null @@ -1,43 +0,0 @@ -From 8d2f3c36caf9ecdee1176314b18388aef6e7f2c2 Mon Sep 17 00:00:00 2001 -From: "J. Nick Koston" -Date: Thu, 13 Jul 2023 09:18:53 -1000 -Subject: [PATCH] gh-106664: selectors: add get() method to _SelectorMapping - (#106665) - -It can be used to avoid raising and catching KeyError twice via __getitem__. - -Co-authored-by: Inada Naoki ---- - Lib/selectors.py | 14 +++++++++----- - Lib/test/test_selectors.py | 6 ++++++ - 2 files changed, 15 insertions(+), 5 deletions(-) - -diff --git a/Lib/selectors.py b/Lib/selectors.py -index af6a4f94b5..dfcc125dcd 100644 ---- a/Lib/selectors.py -+++ b/Lib/selectors.py -@@ -66,12 +66,16 @@ def __init__(self, selector): - def __len__(self): - return len(self._selector._fd_to_key) - -+ def get(self, fileobj, default=None): -+ fd = self._selector._fileobj_lookup(fileobj) -+ return self._selector._fd_to_key.get(fd, default) -+ - def __getitem__(self, fileobj): -- try: -- fd = self._selector._fileobj_lookup(fileobj) -- return self._selector._fd_to_key[fd] -- except KeyError: -- raise KeyError("{!r} is not registered".format(fileobj)) from None -+ fd = self._selector._fileobj_lookup(fileobj) -+ key = self._selector._fd_to_key.get(fd) -+ if key is None: -+ raise KeyError("{!r} is not registered".format(fileobj)) -+ return key - - def __iter__(self): - return iter(self._selector._fd_to_key) --- -2.39.2 (Apple Git-143) - diff --git a/python/3.11/gh-106751-selectors-optimize-EpollSelector.select-10.patch b/python/3.11/gh-106751-selectors-optimize-EpollSelector.select-10.patch deleted file mode 100644 index 79cfcc6e..00000000 --- a/python/3.11/gh-106751-selectors-optimize-EpollSelector.select-10.patch +++ /dev/null @@ -1,57 +0,0 @@ -From aecf6aca515a203a823a87c711f15cbb82097c8b Mon Sep 17 00:00:00 2001 -From: "J. Nick Koston" -Date: Tue, 18 Jul 2023 00:16:32 -1000 -Subject: [PATCH] gh-106751: selectors: optimize EpollSelector.select() - (#106754) - -Co-authored-by: Pieter Eendebak ---- - Lib/selectors.py | 17 +++++++++-------- - 1 file changed, 9 insertions(+), 8 deletions(-) - -diff --git a/Lib/selectors.py b/Lib/selectors.py -index 6d82935445..a42d156340 100644 ---- a/Lib/selectors.py -+++ b/Lib/selectors.py -@@ -430,6 +430,9 @@ class PollSelector(_PollLikeSelector): - - if hasattr(select, 'epoll'): - -+ _NOT_EPOLLIN = ~select.EPOLLIN -+ _NOT_EPOLLOUT = ~select.EPOLLOUT -+ - class EpollSelector(_PollLikeSelector): - """Epoll-based selector.""" - _selector_cls = select.epoll -@@ -452,22 +455,20 @@ def select(self, timeout=None): - # epoll_wait() expects `maxevents` to be greater than zero; - # we want to make sure that `select()` can be called when no - # FD is registered. -- max_ev = max(len(self._fd_to_key), 1) -+ max_ev = len(self._fd_to_key) or 1 - - ready = [] - try: - fd_event_list = self._selector.poll(timeout, max_ev) - except InterruptedError: - return ready -- for fd, event in fd_event_list: -- events = 0 -- if event & ~select.EPOLLIN: -- events |= EVENT_WRITE -- if event & ~select.EPOLLOUT: -- events |= EVENT_READ - -- key = self._fd_to_key.get(fd) -+ fd_to_key = self._fd_to_key -+ for fd, event in fd_event_list: -+ key = fd_to_key.get(fd) - if key: -+ events = ((event & _NOT_EPOLLIN and EVENT_WRITE) -+ | (event & _NOT_EPOLLOUT and EVENT_READ)) - ready.append((key, events & key.events)) - return ready - --- -2.39.2 (Apple Git-143) - diff --git a/python/3.11/gh-110733-Optimize-_run_once-for-many-iterations-of-.patch b/python/3.11/gh-110733-Optimize-_run_once-for-many-iterations-of-.patch deleted file mode 100644 index a139ab22..00000000 --- a/python/3.11/gh-110733-Optimize-_run_once-for-many-iterations-of-.patch +++ /dev/null @@ -1,31 +0,0 @@ -From 86a112f61954dede0a777544e59068c0b01c8439 Mon Sep 17 00:00:00 2001 -From: "J. Nick Koston" -Date: Wed, 11 Oct 2023 08:34:01 -1000 -Subject: [PATCH] gh-110733: Optimize _run_once for many iterations of the - event loop - ---- - Lib/asyncio/base_events.py | 7 +++++-- - 1 file changed, 5 insertions(+), 2 deletions(-) - -diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py -index b092c93436..956864e424 100644 ---- a/Lib/asyncio/base_events.py -+++ b/Lib/asyncio/base_events.py -@@ -1907,8 +1907,11 @@ def _run_once(self): - timeout = 0 - elif self._scheduled: - # Compute the desired timeout. -- when = self._scheduled[0]._when -- timeout = min(max(0, when - self.time()), MAXIMUM_SELECT_TIMEOUT) -+ timeout = self._scheduled[0]._when - self.time() -+ if timeout > MAXIMUM_SELECT_TIMEOUT: -+ timeout = MAXIMUM_SELECT_TIMEOUT -+ elif timeout < 0: -+ timeout = 0 - - event_list = self._selector.select(timeout) - self._process_events(event_list) --- -2.39.3 (Apple Git-145) - diff --git a/python/3.11/gh-112989-asyncio-Reduce-overhead-to-connect-sockets.patch b/python/3.11/gh-112989-asyncio-Reduce-overhead-to-connect-sockets.patch deleted file mode 100644 index aa1c4d32..00000000 --- a/python/3.11/gh-112989-asyncio-Reduce-overhead-to-connect-sockets.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 7e2d93f30b157e414924c32232bb748c8f66c828 Mon Sep 17 00:00:00 2001 -From: "J. Nick Koston" -Date: Tue, 12 Dec 2023 14:29:21 -1000 -Subject: [PATCH] gh-112989: asyncio: Reduce overhead to connect sockets with - SelectorEventLoop (#112991) - -_ensure_fd_no_transport had a KeyError in the success path ---- - Lib/asyncio/selector_events.py | 14 +++++--------- - .../2023-12-12-05-48-17.gh-issue-112989.ZAa_eq.rst | 1 + - 2 files changed, 6 insertions(+), 9 deletions(-) - create mode 100644 Misc/NEWS.d/next/Library/2023-12-12-05-48-17.gh-issue-112989.ZAa_eq.rst - -diff --git a/Lib/asyncio/selector_events.py b/Lib/asyncio/selector_events.py -index d521b4e2e2..dcd5e0aa34 100644 ---- a/Lib/asyncio/selector_events.py -+++ b/Lib/asyncio/selector_events.py -@@ -261,15 +261,11 @@ def _ensure_fd_no_transport(self, fd): - except (AttributeError, TypeError, ValueError): - # This code matches selectors._fileobj_to_fd function. - raise ValueError(f"Invalid file object: {fd!r}") from None -- try: -- transport = self._transports[fileno] -- except KeyError: -- pass -- else: -- if not transport.is_closing(): -- raise RuntimeError( -- f'File descriptor {fd!r} is used by transport ' -- f'{transport!r}') -+ transport = self._transports.get(fileno) -+ if transport and not transport.is_closing(): -+ raise RuntimeError( -+ f'File descriptor {fd!r} is used by transport ' -+ f'{transport!r}') - - def _add_reader(self, fd, callback, *args): - self._check_closed() - diff --git a/python/3.13/build.yaml b/python/3.13/build.yaml index fcb181d2..88260515 100644 --- a/python/3.13/build.yaml +++ b/python/3.13/build.yaml @@ -9,7 +9,7 @@ cosign: base_identity: https://github.com/home-assistant/docker-base/.* identity: https://github.com/home-assistant/docker-base/.* args: - PYTHON_VERSION: "3.13.7" + PYTHON_VERSION: "3.13.8" PIP_VERSION: "25.2" CERT_IDENTITY: thomas@python.org CERT_OIDC_ISSUER: https://accounts.google.com diff --git a/python/3.11/Dockerfile b/python/3.14/Dockerfile similarity index 95% rename from python/3.11/Dockerfile rename to python/3.14/Dockerfile index e7be73ab..61da9c63 100644 --- a/python/3.11/Dockerfile +++ b/python/3.14/Dockerfile @@ -80,15 +80,15 @@ RUN set -ex \ --with-lto \ --with-system-libmpdec \ --with-system-expat \ - --with-system-ffi \ --without-ensurepip \ --without-static-libpython \ && make -j "$(nproc)" \ LDFLAGS="-Wl,--strip-all" \ CFLAGS="-fno-semantic-interposition -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-free" \ -# set thread stack size to 1MB so we don't segfault before we hit sys.getrecursionlimit() +# set thread stack size to 2MB so we don't segfault before we hit sys.getrecursionlimit() # https://github.com/alpinelinux/aports/commit/2026e1259422d4e0cf92391ca2d3844356c649d0 - EXTRA_CFLAGS="-DTHREAD_STACK_SIZE=0x100000" \ +# https://github.com/alpinelinux/aports/commit/675f1babb7ac89068d32b8f4b4e8bbfbb04c96ac + EXTRA_CFLAGS="-DTHREAD_STACK_SIZE=0x200000" \ && make install \ \ && find /usr/local -type f -executable -not \( -name '*tkinter*' \) -exec scanelf --needed --nobanner --format '%n#p' '{}' ';' \ diff --git a/python/3.11/build.yaml b/python/3.14/build.yaml similarity index 83% rename from python/3.11/build.yaml rename to python/3.14/build.yaml index 8cdea411..cfd611ac 100644 --- a/python/3.11/build.yaml +++ b/python/3.14/build.yaml @@ -9,10 +9,10 @@ cosign: base_identity: https://github.com/home-assistant/docker-base/.* identity: https://github.com/home-assistant/docker-base/.* args: - PYTHON_VERSION: "3.11.13" + PYTHON_VERSION: "3.14.0" PIP_VERSION: "25.2" - CERT_IDENTITY: pablogsal@python.org - CERT_OIDC_ISSUER: https://accounts.google.com + CERT_IDENTITY: hugo@python.org + CERT_OIDC_ISSUER: https://github.com/login/oauth labels: io.hass.base.name: python org.opencontainers.image.source: https://github.com/home-assistant/docker-base diff --git a/python/3.14/gh-134173-optimize-state-transfer-between-concurrent.patch b/python/3.14/gh-134173-optimize-state-transfer-between-concurrent.patch new file mode 100644 index 00000000..9e4a6c6b --- /dev/null +++ b/python/3.14/gh-134173-optimize-state-transfer-between-concurrent.patch @@ -0,0 +1,89 @@ +From 53da1e8c8ccbe3161ebc42e8b8b7ebd1ab70e05b Mon Sep 17 00:00:00 2001 +From: "J. Nick Koston" +Date: Sun, 18 May 2025 11:56:20 -0400 +Subject: [PATCH] gh-134173: optimize state transfer between + `concurrent.futures.Future` and `asyncio.Future` (#134174) + +Co-authored-by: Kumar Aditya +--- + Lib/asyncio/futures.py | 17 +++--- + Lib/concurrent/futures/_base.py | 27 +++++++++ + Lib/test/test_asyncio/test_futures.py | 58 +++++++++++++++++-- + .../test_concurrent_futures/test_future.py | 57 ++++++++++++++++++ + ...-05-18-07-25-15.gh-issue-134173.53oOoF.rst | 3 + + 5 files changed, 148 insertions(+), 14 deletions(-) + create mode 100644 Misc/NEWS.d/next/Library/2025-05-18-07-25-15.gh-issue-134173.53oOoF.rst + +diff --git a/Lib/asyncio/futures.py b/Lib/asyncio/futures.py +index d1df6707302..6bd00a64478 100644 +--- a/Lib/asyncio/futures.py ++++ b/Lib/asyncio/futures.py +@@ -351,22 +351,19 @@ def _set_concurrent_future_state(concurrent, source): + def _copy_future_state(source, dest): + """Internal helper to copy state from another Future. + +- The other Future may be a concurrent.futures.Future. ++ The other Future must be a concurrent.futures.Future. + """ +- assert source.done() + if dest.cancelled(): + return + assert not dest.done() +- if source.cancelled(): ++ done, cancelled, result, exception = source._get_snapshot() ++ assert done ++ if cancelled: + dest.cancel() ++ elif exception is not None: ++ dest.set_exception(_convert_future_exc(exception)) + else: +- exception = source.exception() +- if exception is not None: +- dest.set_exception(_convert_future_exc(exception)) +- else: +- result = source.result() +- dest.set_result(result) +- ++ dest.set_result(result) + + def _chain_future(source, destination): + """Chain two futures so that when one completes, so does the other. +diff --git a/Lib/concurrent/futures/_base.py b/Lib/concurrent/futures/_base.py +index d98b1ebdd58..f506ce68aea 100644 +--- a/Lib/concurrent/futures/_base.py ++++ b/Lib/concurrent/futures/_base.py +@@ -558,6 +558,33 @@ def set_exception(self, exception): + self._condition.notify_all() + self._invoke_callbacks() + ++ def _get_snapshot(self): ++ """Get a snapshot of the future's current state. ++ ++ This method atomically retrieves the state in one lock acquisition, ++ which is significantly faster than multiple method calls. ++ ++ Returns: ++ Tuple of (done, cancelled, result, exception) ++ - done: True if the future is done (cancelled or finished) ++ - cancelled: True if the future was cancelled ++ - result: The result if available and not cancelled ++ - exception: The exception if available and not cancelled ++ """ ++ # Fast path: check if already finished without lock ++ if self._state == FINISHED: ++ return True, False, self._result, self._exception ++ ++ # Need lock for other states since they can change ++ with self._condition: ++ # We have to check the state again after acquiring the lock ++ # because it may have changed in the meantime. ++ if self._state == FINISHED: ++ return True, False, self._result, self._exception ++ if self._state in {CANCELLED, CANCELLED_AND_NOTIFIED}: ++ return True, True, None, None ++ return False, False, None, None ++ + __class_getitem__ = classmethod(types.GenericAlias) + + class Executor(object): + diff --git a/python/3.11/musl-find_library.patch b/python/3.14/musl-find_library.patch similarity index 100% rename from python/3.11/musl-find_library.patch rename to python/3.14/musl-find_library.patch