Skip to content

Concurrent imports of a package and its submodule raise _DeadlockError in 3.14 and since 3.13.16 #160045

Open

Description

@Kyle-Neale

Bug description:

Since the fix for gh-130094 (GH-130101, backported to 3.13 in GH-155915 and first released in 3.13.16), _find_and_load_unlocked() re-imports a parent package whose spec is still _initializing, and it does so while holding the child module's lock. That creates a lock-order inversion between two common imports that worked before:

  • Thread A runs import pkg. It holds pkg's module lock while pkg/__init__.py runs, and that file does from .sub import X, so A then needs the lock for pkg.sub.
  • Thread B runs import pkg.sub while pkg is still initializing. It takes the pkg.sub lock, sees pkg.__spec__._initializing, and now waits for pkg's lock.

importlib detects the cycle and raises _DeadlockError in thread A. Up to 3.13.15, thread B loaded pkg.sub without waiting for the parent, so both imports succeeded. There is no import cycle in user code, and the same imports work in either order on a single thread.

Reproducer (fails every time on 3.13.16 and 3.14.4; passes on 3.13.13):

import os, sys, tempfile, threading, time

root = tempfile.mkdtemp()
os.makedirs(os.path.join(root, "pkg"))
with open(os.path.join(root, "pkg", "__init__.py"), "w") as f:
    f.write("import time\ntime.sleep(0.5)\nfrom .sub import X\n")
with open(os.path.join(root, "pkg", "sub.py"), "w") as f:
    f.write("X = 1\n")
sys.path.insert(0, root)

errors = []

def run(name):
    try:
        __import__(name)
    except BaseException as e:
        errors.append(f"{name}: {type(e).__name__}: {e}")

a = threading.Thread(target=run, args=("pkg",))
b = threading.Thread(target=run, args=("pkg.sub",))
a.start()
time.sleep(0.1)  # pkg is now initializing; thread a holds its module lock
b.start()
a.join(); b.join()
print(sys.version.split()[0], "->", errors or "OK")
3.13.13 -> OK
3.13.16 -> ["pkg: _DeadlockError: deadlock detected by _ModuleLock('pkg.sub') at 0x..."]
3.14.4  -> ["pkg: _DeadlockError: deadlock detected by _ModuleLock('pkg.sub') at 0x..."]

Removing only the new block below from 3.13.16's _find_and_load_unlocked() makes the reproducer pass. The meta_path = list(meta_path) part of the same change has no effect on it:

        if getattr(parent_spec, '_initializing', False):
            _call_with_frames_removed(import_, parent)
        # Crazy side-effects (again)!
        module = sys.modules.get(name)
        if module is not None:
            return module

Real-world impact: any package whose __init__ imports its own submodules is affected (requests, prometheus_client, and so on) as soon as two threads import it and one of its submodules for the first time at once. The Datadog Agent loads Python checks from several threads and hit this right after moving to 3.13.16. Checks failed at startup with _DeadlockError: deadlock detected by _ModuleLock('requests.exceptions'), so we went back to 3.13.15 (DataDog/datadog-agent#57902). Other projects report the same error after upgrading to 3.13.16 or 3.14, for example gke-labs/kube-agents#2446, dgunning/edgartools#1325 and marimo-team/marimo#11095.

Because this is a regression in a bugfix release, could the 3.13 backport be reverted, or this part changed so the thread doesn't wait for the parent's lock while holding the child's? The same code is on 3.14 and main.

gh-159963 is related but separate: it covers a 3.15-only change from gh-83065. This report is about the gh-130094 change that is already in 3.13.16 and 3.14.

CPython versions tested on:

3.13, 3.14

Operating systems tested on:

macOS

Activity

  1. added
    type-bugAn unexpected behavior, bug, or error
    stdlibStandard Library Python modules in the Lib/ directory
    on Oct 21, 2026
  2. ZeroIntensity commented on Oct 21, 2026

    @ZeroIntensity
    Member

    cc @Yhg1s @hugovk (RMs) @colesbury (author of fix)

  3. gpshead commented on Oct 25, 2026

    @gpshead
    Member

    FWIW I think the regression in 3.13.16 wound up making it match the behavior all 3.14.x releases have had. This regression is what I was referring to in my #130094 (comment) comment.

    The stacked combination of the gh-83065 + gh-159963 fixes is probably required to get to the actually happy place for multi-threaded import deadlock cycles.

    We could also just revert the 3.13.16 change to restore 3.13's other behavior - but it matches what 3.14 has so it wasn't clear to me that restoring 3.13's behavior actually "fixes" anything - the branch is in security fix only mode at this point but if someone maintaining their own build wants the prior 3.13.0-3.13.15 behavior, reverting gh-155915 as a patch in your own tree would do it.

    Anyone upgrading to 3.14 would need to deal with the behavior anyways.

  4. changed the title [-]Concurrent imports of a package and its submodule raise _DeadlockError since 3.13.16 (gh-130094 backport)[/-] [+]Concurrent imports of a package and its submodule raise _DeadlockError in 3.14 and since 3.13.16[/+] on Oct 25, 2026
  5. self-assigned this
    on Oct 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

3.14bugs and security fixesstdlibStandard Library Python modules in the Lib/ directorytopic-importlibtype-bugAn unexpected behavior, bug, or error

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions