Skip to content

The library-scan locks live in runtime.py, and the lock guard sees the or form (#749) - #904

Merged
Ninja-FSE merged 1 commit into
mainfrom
fix/749-scan-locks-live-in-runtime
Sep 23, 2026
Merged

Ninja-FSE merged 1 commit into
mainfrom
fix/749-scan-locks-live-in-runtime

Conversation

@chchatzop

Copy link
Copy Markdown
Collaborator

Fixes #749. Stacked on #903 - merge that first (both add at the top of the changelog's Unreleased).

What was wrong

#731 (#580) left two lock-like objects built at module level in dcc.py, a module !rehash reloads:

_library_scans = globals().get("_library_scans") or threading.BoundedSemaphore(MAX_CONCURRENT_LIBRARY_SCANS)
_lookup_misses_lock = globals().get("_lookup_misses_lock") or threading.Lock()

It worked - globals().get keeps the old object across a reload - but the rule (#235) is that a reloaded module never constructs its own lock: runtime.py, which nothing reloads, owns them and the module binds the name. And tests/test_no_reloaded_module_owns_a_lock.py could not see these two: it matched a factory call only as the whole right-hand side, and here the call sits inside a BoolOp.

What changed

  • runtime.py: MAX_CONCURRENT_LIBRARY_SCANS, library_scans (the semaphore) and lookup_memory_lock, beside the other locks. dcc.py binds all three by name, like queue_lock.
  • The memories themselves (_lookup_misses, _lookup_hits, _lookup_folders) stay in dcc.py. The issue left that open; runtime.py's containers are configuration state that test_runtime_state requires to be bound through defaults.py and reset between tests - moving these there failed nine of its checks, and they are dcc's own cache, not that.
  • The guard: constructs_a_lock() walks the whole value, so a factory call inside or, a conditional or an argument is found. Its synthetic control gains globals().get(..) or threading.Lock(), the BoundedSemaphore form, a conditional, and a plain or {} that must not be flagged.
  • Dev changelog only - no behaviour change.

Test

Run before the move, the extended guard names exactly the two (dcc.py:97 _library_scans, dcc.py:99 _lookup_misses_lock). TheLookupLocksAreBoundFromRuntime (2): both are runtime's objects, and the semaphore admits exactly MAX_CONCURRENT_LIBRARY_SCANS scans. Against the old code the guard and the identity check fail.

Verified

Full suite on the stacked branch: 6431 OK (19 skipped).

🤖 Generated with Claude Code

https://claude.ai/code/session_01AP6LSxkr4n9dMFNSNMogmW

…e or form (#749)

dcc.py built its scan semaphore and lookup-memory lock as
`x = globals().get("x") or threading.Lock()`, against the rule that a
reloaded module never constructs its own lock; the guard missed them
because the factory call sat inside a BoolOp. runtime.py owns both now,
bound by name in dcc.py; the memories stay dcc.py's own cache. The
guard walks the whole value, its control gains the or / conditional
forms and a plain `or {}` that must not be flagged, and two tests check
the binding and the semaphore's size. No behaviour change. Dev changelog.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AP6LSxkr4n9dMFNSNMogmW
@chchatzop
chchatzop force-pushed the fix/749-scan-locks-live-in-runtime branch from 31d7c57 to 798ebbe Compare September 23, 2026 06:17
@chchatzop
chchatzop changed the base branch from fix/891-a-rerun-keeps-every-admin-host to main September 23, 2026 06:17
@chchatzop

Copy link
Copy Markdown
Collaborator Author

Rebased onto main and retargeted to it: #903, which this was stacked on, was superseded by #909 (merged). The code is byte-for-byte the same commit; the changelog entry was re-inserted by hand at the top of Unreleased, above #909's - a plain rebase had spliced it into the middle of #909's entry without reporting a conflict. Full suite on the rebased branch: 6432 OK.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Move _library_scans and _lookup_misses_lock into runtime.py, and teach the lock guard the globals().get(...) or form

2 participants