From ebf3427615b3a3d1e6048f84a4adc47331c3803d Mon Sep 17 00:00:00 2001 From: Ken Jin Date: Thu, 11 Dec 2025 03:39:11 +0800 Subject: [PATCH 1/4] gh-141976: Protect against non-progressing specializations in tracing JIT (GH-141989) --- Include/cpython/pystats.h | 1 + ...-11-26-20-01-07.gh-issue-141976.K8NDmR.rst | 1 + Python/optimizer.c | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-11-26-20-01-07.gh-issue-141976.K8NDmR.rst diff --git a/Include/cpython/pystats.h b/Include/cpython/pystats.h index 1c94603c08b9b6..10ac98f2dfebe6 100644 --- a/Include/cpython/pystats.h +++ b/Include/cpython/pystats.h @@ -142,6 +142,7 @@ typedef struct _optimization_stats { uint64_t recursive_call; uint64_t low_confidence; uint64_t unknown_callee; + uint64_t trace_immediately_deopts; uint64_t executors_invalidated; UOpStats opcode[PYSTATS_MAX_UOP_ID + 1]; uint64_t unsupported_opcode[256]; diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-11-26-20-01-07.gh-issue-141976.K8NDmR.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-11-26-20-01-07.gh-issue-141976.K8NDmR.rst new file mode 100644 index 00000000000000..654681515e43e5 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-11-26-20-01-07.gh-issue-141976.K8NDmR.rst @@ -0,0 +1 @@ +Protect against specialization failures in the tracing JIT compiler for performance reasons. diff --git a/Python/optimizer.c b/Python/optimizer.c index 9db894f0bf054a..7fe914a7a426b9 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -610,6 +610,25 @@ _PyJit_translate_single_bytecode_to_trace( target--; } + if (_PyOpcode_Caches[_PyOpcode_Deopt[opcode]] > 0) { + uint16_t backoff = (this_instr + 1)->counter.value_and_backoff; + // adaptive_counter_cooldown is a fresh specialization. + // trigger_backoff_counter is what we set during tracing. + // All tracing backoffs should be freshly specialized or untouched. + // If not, that indicates a deopt during tracing, and + // thus the "actual" instruction executed is not the one that is + // in the instruction stream, but rather the deopt. + // It's important we check for this, as some specializations might make + // no progress (they can immediately deopt after specializing). + // We do this to improve performance, as otherwise a compiled trace + // will just deopt immediately. + if (backoff != adaptive_counter_cooldown().value_and_backoff && + backoff != trigger_backoff_counter().value_and_backoff) { + OPT_STAT_INC(trace_immediately_deopts); + opcode = _PyOpcode_Deopt[opcode]; + } + } + int old_stack_level = _tstate->jit_tracer_state.prev_state.instr_stacklevel; // Strange control-flow From 26757d135143b9e481bafd1d477634bf327910a7 Mon Sep 17 00:00:00 2001 From: Ethan Furman Date: Wed, 10 Dec 2025 11:46:10 -0800 Subject: [PATCH 2/4] gh-135559: [Enum] dir() on a Flag now shows aliases (GH-136527) --- Lib/enum.py | 6 +++++- Lib/test/test_enum.py | 6 +++++- .../Library/2025-07-10-18-40-11.gh-issue-135559.BMDtYn.rst | 2 ++ 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-07-10-18-40-11.gh-issue-135559.BMDtYn.rst diff --git a/Lib/enum.py b/Lib/enum.py index ad782b8c41e160..15dddf6de69268 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -774,12 +774,16 @@ def __delattr__(cls, attr): super().__delattr__(attr) def __dir__(cls): + if issubclass(cls, Flag): + members = list(cls._member_map_.keys()) + else: + members = cls._member_names_ interesting = set([ '__class__', '__contains__', '__doc__', '__getitem__', '__iter__', '__len__', '__members__', '__module__', '__name__', '__qualname__', ] - + cls._member_names_ + + members ) if cls._new_member_ is not object.__new__: interesting.add('__new__') diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index 66d78980c41cb6..779457119e8f0e 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -5529,12 +5529,16 @@ def test_enum_dict_standalone(self): # helpers def enum_dir(cls): + if issubclass(cls, Flag): + members = list(cls._member_map_.keys()) + else: + members = cls._member_names_ interesting = set([ '__class__', '__contains__', '__doc__', '__getitem__', '__iter__', '__len__', '__members__', '__module__', '__name__', '__qualname__', ] - + cls._member_names_ + + members ) if cls._new_member_ is not object.__new__: interesting.add('__new__') diff --git a/Misc/NEWS.d/next/Library/2025-07-10-18-40-11.gh-issue-135559.BMDtYn.rst b/Misc/NEWS.d/next/Library/2025-07-10-18-40-11.gh-issue-135559.BMDtYn.rst new file mode 100644 index 00000000000000..2ab3d4d76f38e8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-07-10-18-40-11.gh-issue-135559.BMDtYn.rst @@ -0,0 +1,2 @@ +Flag: a ``dir()`` on a ``Flag`` enumeration now shows non-canonical members. +(i.e. aliases). From 97e19014ddc652beae58e7eceb591f5d65a875e6 Mon Sep 17 00:00:00 2001 From: Ken Jin Date: Thu, 11 Dec 2025 05:09:56 +0800 Subject: [PATCH 3/4] gh-137007: Track executor before any possible deallocations (GH-137016) --- .../2025-07-22-16-20-06.gh-issue-137007.1oPvvK.rst | 1 + Python/optimizer.c | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-07-22-16-20-06.gh-issue-137007.1oPvvK.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-07-22-16-20-06.gh-issue-137007.1oPvvK.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-07-22-16-20-06.gh-issue-137007.1oPvvK.rst new file mode 100644 index 00000000000000..cb25fd10c0bd2c --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-07-22-16-20-06.gh-issue-137007.1oPvvK.rst @@ -0,0 +1 @@ +Fix a bug during JIT compilation failure which caused garbage collection debug assertions to fail. diff --git a/Python/optimizer.c b/Python/optimizer.c index 7fe914a7a426b9..b693581d7318bf 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -1310,6 +1310,10 @@ make_executor_from_uops(_PyUOpInstruction *buffer, int length, const _PyBloomFil assert(next_exit == -1); assert(dest == executor->trace); assert(dest->opcode == _START_EXECUTOR); + // Note: we MUST track it here before any Py_DECREF(executor) or + // linking of executor. Otherwise, the GC tries to untrack a + // still untracked object during dealloc. + _PyObject_GC_TRACK(executor); _Py_ExecutorInit(executor, dependencies); #ifdef Py_DEBUG char *python_lltrace = Py_GETENV("PYTHON_LLTRACE"); @@ -1338,7 +1342,6 @@ make_executor_from_uops(_PyUOpInstruction *buffer, int length, const _PyBloomFil return NULL; } #endif - _PyObject_GC_TRACK(executor); return executor; } From dc3ece2bc06d56c21ef81f86424b4598880ba1c8 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 10 Dec 2025 23:21:03 +0100 Subject: [PATCH 4/4] gh-142489: Increase ssl_handshake_timeout in asyncio tests (#142523) Replace SHORT_TIMEOUT with LONG_TIMEOUT for very slow CIs. And add the HANDSHAKE_TIMEOUT constant. --- Lib/test/test_asyncio/test_ssl.py | 40 +++++++++++++------------------ 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/Lib/test/test_asyncio/test_ssl.py b/Lib/test/test_asyncio/test_ssl.py index 06118f3a61587b..ca15fc3bdd42dd 100644 --- a/Lib/test/test_asyncio/test_ssl.py +++ b/Lib/test/test_asyncio/test_ssl.py @@ -27,6 +27,7 @@ MACOS = (sys.platform == 'darwin') BUF_MULTIPLIER = 1024 if not MACOS else 64 +HANDSHAKE_TIMEOUT = support.LONG_TIMEOUT def tearDownModule(): @@ -257,15 +258,12 @@ def prog(sock): await fut async def start_server(): - extras = {} - extras = dict(ssl_handshake_timeout=support.SHORT_TIMEOUT) - srv = await asyncio.start_server( handle_client, '127.0.0.1', 0, family=socket.AF_INET, ssl=sslctx, - **extras) + ssl_handshake_timeout=HANDSHAKE_TIMEOUT) try: srv_socks = srv.sockets @@ -322,14 +320,11 @@ def server(sock): sock.close() async def client(addr): - extras = {} - extras = dict(ssl_handshake_timeout=support.SHORT_TIMEOUT) - reader, writer = await asyncio.open_connection( *addr, ssl=client_sslctx, server_hostname='', - **extras) + ssl_handshake_timeout=HANDSHAKE_TIMEOUT) writer.write(A_DATA) self.assertEqual(await reader.readexactly(2), b'OK') @@ -349,7 +344,8 @@ async def client_sock(addr): reader, writer = await asyncio.open_connection( sock=sock, ssl=client_sslctx, - server_hostname='') + server_hostname='', + ssl_handshake_timeout=HANDSHAKE_TIMEOUT) writer.write(A_DATA) self.assertEqual(await reader.readexactly(2), b'OK') @@ -448,7 +444,7 @@ async def client(addr): *addr, ssl=client_sslctx, server_hostname='', - ssl_handshake_timeout=support.SHORT_TIMEOUT) + ssl_handshake_timeout=HANDSHAKE_TIMEOUT) writer.close() await self.wait_closed(writer) @@ -610,7 +606,7 @@ def client(): extras = {} if server_ssl: - extras = dict(ssl_handshake_timeout=support.SHORT_TIMEOUT) + extras = dict(ssl_handshake_timeout=HANDSHAKE_TIMEOUT) f = loop.create_task( loop.connect_accepted_socket( @@ -659,7 +655,8 @@ async def client(addr): reader, writer = await asyncio.open_connection( *addr, ssl=client_sslctx, - server_hostname='') + server_hostname='', + ssl_handshake_timeout=HANDSHAKE_TIMEOUT) self.assertEqual(await reader.readline(), b'A\n') writer.write(b'B') @@ -1152,14 +1149,11 @@ def do(func, *args): await fut async def start_server(): - extras = {} - srv = await self.loop.create_server( server_protocol_factory, '127.0.0.1', 0, family=socket.AF_INET, - ssl=sslctx_1, - **extras) + ssl=sslctx_1) try: srv_socks = srv.sockets @@ -1209,14 +1203,11 @@ def server(sock): sock.close() async def client(addr): - extras = {} - extras = dict(ssl_handshake_timeout=support.SHORT_TIMEOUT) - reader, writer = await asyncio.open_connection( *addr, ssl=client_sslctx, server_hostname='', - **extras) + ssl_handshake_timeout=HANDSHAKE_TIMEOUT) writer.write(A_DATA) self.assertEqual(await reader.readexactly(2), b'OK') @@ -1286,7 +1277,8 @@ async def client(addr): reader, writer = await asyncio.open_connection( *addr, ssl=client_sslctx, - server_hostname='') + server_hostname='', + ssl_handshake_timeout=HANDSHAKE_TIMEOUT) sslprotocol = writer.transport._ssl_protocol writer.write(b'ping') data = await reader.readexactly(4) @@ -1398,7 +1390,8 @@ async def client(addr): reader, writer = await asyncio.open_connection( *addr, ssl=client_sslctx, - server_hostname='') + server_hostname='', + ssl_handshake_timeout=HANDSHAKE_TIMEOUT) writer.write(b'ping') data = await reader.readexactly(4) self.assertEqual(data, b'pong') @@ -1529,7 +1522,8 @@ async def client(addr): reader, writer = await asyncio.open_connection( *addr, ssl=client_sslctx, - server_hostname='') + server_hostname='', + ssl_handshake_timeout=HANDSHAKE_TIMEOUT) writer.write(b'ping') data = await reader.readexactly(4) self.assertEqual(data, b'pong')