πͺ² BUG-#28: Fix GenericProvider race between complete() and reload() - #44
Conversation
FernandoCelmer
left a comment
There was a problem hiding this comment.
The _ConnectionSnapshot pattern is a clean solution for eliminating the torn-read problem. Two issues are worth addressing before merge.
|
[Blocking] Problem β Failure scenario β A developer adds Fix β Add the field to the snapshot and populate it in @dataclass(frozen=True)
class _ConnectionSnapshot:
...
request_builder: Callable # add this
def _snapshot_locked(self) -> _ConnectionSnapshot:
return _ConnectionSnapshot(
...
request_builder=self.request_builder,
)Then replace [Suggestion] Lock is held during request body serialisation Problem β Failure scenario β A caller passes a large Fix β Take the snapshot under the lock, release, then build the body: def complete(self, system_prompt, messages, tools, ...):
with self._lock:
config = self._snapshot_locked()
# lock released β body build is now contention-free
body = config.request_builder(system_prompt, messages, tools, config.model)
... |
β¦ lock-free _ConnectionSnapshot claimed to be a complete point-in-time copy of everything reload() can mutate, but omitted request_builder β a future reader of the snapshot expecting that field would hit an AttributeError, and complete() was still reading self.request_builder directly instead of the snapshot. Added the field and switched complete() to read it off config; this also lets body construction (schema serialization, potentially non-trivial) move outside the lock now that the snapshot is fully self-contained, so it no longer blocks a concurrent reload() for the duration of the build.
Description
Fixes a race condition in
GenericProviderwhere a concurrentreload()could mutateurl,model,headers, and other config fields mid-read insidecomplete(). Introduces a_ConnectionSnapshotfrozen dataclass and athreading.Locksocomplete()takes a single consistent snapshot before doing any I/O.Motivation and Context
Closes #28.
CodeLoop.ask()documents it is safe to call whilerun()is mid-turn on another thread, butGenericProviderhad no protection against non-atomic config mutation duringreload().Types of changes
Checklist