When an import failed with a stack overflow RecursionError, releasing the import locks on the error path could itself raise RecursionError, because the cleanup runs in a new C frame. This left the global import lock or a module lock held forever, and later imports in other threads blocked indefinitely.
_imp.acquire_lock() now raises RecursionError when there is not enough stack left to release the lock. importlib calls it before taking any import lock, so this covers the per-module locks too. The module lock weakref callback skips cleanup in that case instead of
reporting an unraisable exception.
The build-helper programs (`Programs/_freeze_module` and `_bootstrap_python`) don't need to link extension modules that they don't need.
This PR makes sure the helper programs get only the modules they need to do their job, regardless of what the build configuration sets for the final built runtime and interpreter.
This follows the same pattern as what `PCbuild/_freeze_module.vcxproj` does with `PC/config_minimal.c`, and lets "extra" builtin modules (from `Setup.local` or `MODULE_BUILDTYPE=static`) depend on frozen modules or `getpath`.
SSLProtocol.connection_lost() did not notify the application protocol
while a handshake was in progress, even when start_tls() had handed
over a protocol that had already seen connection_made(). With streams
this left StreamWriter.wait_closed() waiting forever.
ANT-2026-A6ZIAFVT
ANT-2026-4SKWM05E
AST nodes had an explicit `__dict__` slot, so every node built by
`ast.parse` allocated its own dict. With `Py_TPFLAGS_MANAGED_DICT`,
node attributes are stored as inline values with shared keys instead.
`ast.parse` is ~15.8% faster, walking trees is ~15.9% faster, and
the memory used by the AST trees is reduced by ~51.6%.
In the free-threaded build, compile() of a source string sets
suppress_co_const_immortalization so that its output is the same as in
the default build. The branch for AST objects returned before that, so
constants were replaced by the immortal ones of the interpreter's table
while a tuple of names (such as co_names) which was the same object was
not, and the marshal output depended on the code objects created before
in the process.
Keep the SSL shutdown timer running until connection_lost(), and start
it when the peer shuts down first, so that a peer which sends EOF but
does not read cannot keep a closed transport open indefinitely.
ANT-2026-81HUBBJ7
ZSTD_freeCCtx() frees a dictionary loaded with ZSTD_CCtx_loadDictionary()
before it stops the worker threads. Make libzstd wait for unfinished
jobs before freeing the compression context of a ZstdCompressor that
uses worker threads and a ZstdDict.
Works around https://github.com/facebook/zstd/issues/4862
The garbage collector could release a ZstdCompressor's dictionary, or
the digested dictionaries cached by the ZstdDict, while worker threads
of the compression context were still reading them. They are now only
released on deallocation, after the context has been freed.
ANT-2026-CT3P1CSJ
+ Visit the type in the compressor and decompressor traverse; move NEWS to Library
Since gh-83065 (3.15.0), the first import of pkg.mod kept pkg's module
lock until pkg/mod.py had finished executing. Threads that needed only
the finished pkg stayed blocked for that long, and concurrent first
imports could raise _DeadlockError or a spurious "partially initialized
module" ImportError, or hang, with no import cycle involved.
Parent packages are now imported, or waited for if another thread is
initializing them, before the submodule's module lock is taken, so only
that lock is held while the submodule executes. The lock ordering
deadlock fixed by gh-83065 stays fixed.
importlib.import_module() now returns the partially initialized module
in a circular import split across threads, as the import statement
already did, instead of raising _DeadlockError. That was long-standing
behavior rather than part of the 3.15.0 regression.
A cursor exposed its description tuple before filling in all the columns, so an allocation failure or a reader on a free-threaded build could encounter an unset tuple entry. An allocation failure could also clear lastrowid and crash the next execute. Build new metadata before replacing the old values and synchronize reads and writes. Keep a strong reference to each row or text factory while it is called so replacing the factory during a fetch cannot destroy an active callable.
A raw read can reinitialize a TextIOWrapper as write-only, and codec callbacks or finalizers can release an encoder, decoder, or decoded chunk while a read or write still needs it. This can crash the interpreter. Check that a decoder is still available after reading, keep codecs alive across calls, and read the chunk length before transferring ownership. Also recheck StringIO after converting the truncate size because __index__() can close the stream.
In the free-threaded build, dict() and dict.update() checked that a
list key/value pair had two items and then read them with borrowed
references without holding its lock, so another thread could empty
the list in between and the items were read from a freed or NULL
array.