Caching¶
Cached singleton-instance machinery for DotEnvConfig subclasses.
caching ¶
Cached singleton-instance machinery for DotEnvConfig subclasses.
Provides the internal implementation behind DotEnvConfig.cached(),
DotEnvConfig.reset_cached(), and DotEnvConfig.cached_override().
The public API lives on DotEnvConfig itself as thin classmethod wrappers
that preserve concrete-subclass typing (Self); this module operates on
plain type[DotEnvConfig] / DotEnvConfig and is not part of the
package's public API.
The cached instance is stored as a private class attribute
(_cached_instance) on each config subclass's own __dict__ rather than
in a module-level registry. This ties the cache lifetime to the class object:
when nothing else references the class, both the class and its cached instance
become collectible together.
Thread safety
A module-level threading.RLock guards the double-checked-locking
initialization path and the save/restore operations in
begin_override / end_override. A reentrant lock (not a plain
Lock) is required because the lock is held across cls.load():
post_load() and field validator hooks may legitimately touch
the cache for other classes, and a non-reentrant lock would
self-deadlock that thread. With a single module-level lock no
cross-thread circular wait is possible — only one thread holds the
lock and the holder always proceeds — so same-thread nesting is the
only reentrancy case, and the RLock permits it.
Same-class operations from within that class's own in-flight load
remain invalid: cached() cannot return an instance that does not
exist yet (the nested call would see a cold cache and recurse into
load() without bound), and reset_cached() /
cached_override() would be silently overwritten when the
in-flight load installs its instance. A threading.local set
tracks classes currently loading on each thread; all three entry
points raise RuntimeError in that case. A circular cross-class
chain (A's hook loads B, B's hook loads A) collapses back onto the
first class and is likewise reported as a same-class
RuntimeError.
One residual hazard sits outside this design: the lock is held for
the duration of a load, so a hook must not block on another thread
(e.g. thread.join()) that touches the cache — the joining thread
holds the lock while the joined thread waits for it, deadlocking
both.