You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
DependencyGraph has unsynchronized concurrent access from background threads #1255
The main thread concurrently mutates the same data:
# dependency_graph.py:89 — main threadself.children.append(current_to_child_edge)
CPython's GIL happens to serialize these today, but that's an implementation accident — the language does not guarantee thread safety for mutable containers. This also violates the design intent of PR #1199, which moved methods to module-level functions specifically to avoid background threads accessing mutable state.
Fromager's default is Python 3.12; it also declares support for 3.13 and 3.14, where the free-threaded build (no GIL) makes this a real data race.
Why it hasn't caused failures yet
Timing: Top-level edges are populated by _resolve_and_add_top_level()before any background work starts. Edges added later by Start.run() are non-top-level — missing one just means the resolver falls through to prev_graph or network, producing the same result.
The GIL: Serializes list.append and list iteration, preventing corruption when timing does overlap. Under free-threaded Python (3.13t+), this guarantee disappears.
The resolver's self._lock protects its own cache (_known_versions, _resolved_rules) but does not cover dependency_graph.
Possible fixes
Add a threading.Lock to DependencyGraph — matches the pattern already used by TrackingTopologicalSorter in the same file.
Snapshot top-level edges before entering the loop — pass a frozen copy to the resolver. Avoids locking since background reads only need data that's stable after setup.
Have _resolve_from_graph read only prev_graph — skip the ctx.dependency_graph read entirely. The top-level info is already in the resolver's session cache by then.
Activity