Skip to content

Tree-wide cache lock: cross-thread deadlock and per-thread dependency builds #569

Closed

Description

@lesnik512

This was generated by AI during a pre-4.0 audit of main at 9cd88e7.

Follow-up to #561 (one RLock per tree, held around create on a cache miss, registries/cache_registry.py:41-46).

1. Deadlock (reproduced, hangs forever)

A cached creator that resolves another cached type on a worker thread:

def make_a(c: Container) -> A:
    return ThreadPoolExecutor(1).submit(c.resolve, B).result()

The caller holds the tree lock inside create; the worker blocks on the same lock for B. The same lock also serialises unrelated cold creations across every request in the tree.

2. Concurrent cold misses build dependencies once per thread

resolve() of the dependencies runs outside the lock (cache_registry.py:41). 8 threads resolving a cached Svc(conn: Conn) with transient Conn: one Svc, 8 Conn, 7 discarded without finalizers. Same on 3.14t. design-decisions.md only promises one instance.

Options

  • Per-item lock (a lock per CacheItem, created with the item): fixes the deadlock and the serialisation; deadlock remains possible only for real cycles, which are already errors. Cost: one lock per cached item, not per request.
  • Resolve dependencies inside the (per-item) lock: fixes item 2 at the cost of holding the lock across dependency construction.
  • Or document item 2 as accepted behaviour.

Needs a decision and a G15 (concurrent first resolve) measurement before implementation. Failing tests first (the deadlock test needs a timeout).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingready-for-humanRequires human implementation

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions