277
}
278
279
>
func (r *registry) RegisterStateChangeCallback(key any, cb namespace.StateChangeCallbackFn) {
registry.go
280
>
// Store callback first to avoid race where watch events arrive between reading the namespace snapshot and storing the
281
>
// callback. This ensures no events are missed, but introduces a different trade-off: The callback may receive duplicate
282
>
// calls for the same namespace if a watch event arrives while we're iterating through the catch-up loop below. For
283
>
// example:
284
>
// 1. Callback is stored in stateChangeCallbacks
285
>
// 2. Watch event arrives for namespace X, callback is invoked
286
>
// 3. Catch-up loop reaches namespace X, callback is invoked again
287
>
//
288
>
// This is acceptable because callbacks are rarely added (so unlikely to trigger this) and callbacks should be idempotent anyway.
289
>
callbackWithTiming := func(ns *namespace.Namespace, deletedFromDb bool) {
290
>
// Track callback duration so we can identify slow callbacks
291
>
start := time.Now()
292
>
defer func() {
293
>
duration := time.Since(start)
294
>
if duration > slowCallbackDuration {
295
metrics.NamespaceRegistrySlowCallbacks.With(r.metricsHandler).Record(1)
296
r.logger.Warn(