feat(failover): package-level default observer for transparently-built chains
CI / Build, Test & Lint (push) Successful in 10m43s
CI / Build, Test & Lint (push) Successful in 10m43s
The transparent comma-Parse path builds failover chains via NewFailoverModel with no options, so defaultFailoverConfig() left the observer nil and observers only fired when a caller passed WithFailoverObserver explicitly. Add a package-level default observer (SetFailoverObserver / DefaultFailoverObserver), guarded by the existing defaultsMu, and seed it in defaultFailoverConfig() so chains built transparently still notify it. An explicit WithFailoverObserver still overrides the default per-chain. mort sets this at boot to persist failover events. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,12 @@ var (
|
||||
// DefaultFailoverBackoff is the default exponential-with-jitter backoff.
|
||||
DefaultFailoverBackoff = defaultBackoff
|
||||
|
||||
// defaultFailoverObserver is the package-level observer applied to chains
|
||||
// built without an explicit WithFailoverObserver (e.g. the transparent
|
||||
// comma-Parse path). Kept unexported and behind defaultsMu so reads/writes
|
||||
// are race-safe under -race. mort sets this at boot to persist failover events.
|
||||
defaultFailoverObserver FailoverObserver
|
||||
|
||||
defaultsMu sync.Mutex
|
||||
)
|
||||
|
||||
@@ -62,6 +68,31 @@ func SetFailoverDefaults(maxRetries int, cooldown time.Duration) {
|
||||
DefaultFailoverCooldown = cooldown
|
||||
}
|
||||
|
||||
// SetFailoverObserver sets the package-level default observer notified on
|
||||
// failover decisions for chains built without an explicit WithFailoverObserver.
|
||||
//
|
||||
// Why: the transparent comma-Parse path builds chains via NewFailoverModel with
|
||||
// no options, so without a package default no observer ever fires; mort sets
|
||||
// this once at boot to persist failover events from every chain.
|
||||
// What: stores the observer under defaultsMu; pass nil to disable.
|
||||
// Test: set an observer, build a no-option chain, assert it fires on failover.
|
||||
func SetFailoverObserver(obs FailoverObserver) {
|
||||
defaultsMu.Lock()
|
||||
defer defaultsMu.Unlock()
|
||||
defaultFailoverObserver = obs
|
||||
}
|
||||
|
||||
// DefaultFailoverObserver returns the current package-level default observer.
|
||||
//
|
||||
// Why: lets tests assert/restore the default without reaching into the unexported var.
|
||||
// What: reads defaultFailoverObserver under defaultsMu.
|
||||
// Test: set via SetFailoverObserver, assert this returns a non-nil func.
|
||||
func DefaultFailoverObserver() FailoverObserver {
|
||||
defaultsMu.Lock()
|
||||
defer defaultsMu.Unlock()
|
||||
return defaultFailoverObserver
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Global model health (process-wide bench registry)
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -285,6 +316,11 @@ func defaultFailoverConfig() failoverConfig {
|
||||
maxRetries: DefaultFailoverMaxRetries,
|
||||
cooldown: DefaultFailoverCooldown,
|
||||
backoff: DefaultFailoverBackoff,
|
||||
// Seed the package-level default observer. An explicit
|
||||
// WithFailoverObserver applied after this in NewFailoverModel/ParseChain
|
||||
// overrides it for that chain. Read under the same defaultsMu we already
|
||||
// hold (a single Lock above), so no re-lock / deadlock.
|
||||
observer: defaultFailoverObserver,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user