registry.go ×21

Frontier kind: Code frontier

unlabeled · c_48fc5c2a82a7

44 tests · 2088 LOC · 89 files · introduces 0 tests · 107 LOC · 2 files

Introduces — evidence that enters the hierarchy at this concept

Code
22 ranges107 lines · 2 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
310 ranges2088 lines · 89 files · Browse complete extent
All tests (intent)
44 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

2 files ranked by introduced lines: 107 introduced LOC across 22 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

go.temporal.io/server/common/namespace/nsregistry/registry.go 102 introduced LOC · 21 ranges

Open complete file

181 // DefaultNamespaceStateChanged is the default implementation that checks whether a namespace
182 // state change is significant enough to trigger callbacks.
183 > func DefaultNamespaceStateChanged(currentClusterName string, oldNS *namespace.Namespace, newNS *namespace.Namespace) bool { registry.go
184 > return oldNS == nil ||
185 > oldNS.State() != newNS.State() ||
186 > oldNS.Name() != newNS.Name() ||
187 > oldNS.IsGlobalNamespace() != newNS.IsGlobalNamespace() ||
188 > //nolint:forbidigo // ns-wide state diff for cache invalidation.
189 > oldNS.ActiveInCluster(currentClusterName) != newNS.ActiveInCluster(currentClusterName) ||
190 > oldNS.ReplicationState("") != newNS.ReplicationState("")
191 > }
192
193 // GetRegistrySize observes the size of the by-name and by-ID maps.
215 // arrive. If not supported, falls back to periodic polling. Start blocks until the initial namespace refresh completes.
216 // The initial refresh must succeed or the function will fatal.
217 > func (r *registry) Start() { registry.go
218 > ctx := headers.SetCallerInfo(
219 > context.Background(),
220 > headers.SystemBackgroundHighCallerInfo,
221 > )
222 >
223 > watchStarted := false
224 > r.refresher, watchStarted = r.runWatchLoop(ctx)
225 > if watchStarted {
226 // Watch started successfully
227 return
271 }
272
273 > func (r *registry) GetAllNamespaces() []*namespace.Namespace { registry.go
274 > r.nsMapsLock.RLock()
275 > defer r.nsMapsLock.RUnlock()
276 > return expmaps.Values(r.idToNamespace)
277 > }
278
279 func (r *registry) RegisterStateChangeCallback(key any, cb namespace.StateChangeCallbackFn) {
433 // On initial startup (initialWatch=true), retries are limited to avoid blocking server startup indefinitely.
434 // On reconnection after a previous success (initialWatch=false), retries continue indefinitely.
435 > func watchStartRetryPolicy(initialWatch bool) backoff.RetryPolicy { registry.go
436 > policy := backoff.NewExponentialRetryPolicy(CacheRefreshFailureRetryInterval)
437 > if initialWatch {
438 > return policy.WithMaximumAttempts(startWatchMaxAttempts)
439 > }
440 return policy.WithExpirationInterval(backoff.NoInterval)
441 }
447 // Uses ShutdownOnce to track whether the watch has ever started successfully, which affects retry behavior: limited
448 // retries on initial startup, unlimited on reconnection.
449 > func (r *registry) runWatchLoop(ctx context.Context) (*goro.Handle, bool) { registry.go
450 > // watchStartedOnce tracks whether the watch has ever started successfully.
451 > // Used to determine retry policy and signal to the caller when watch is ready.
452 > watchStartedOnce := channel.NewShutdownOnce()
453 >
454 > handle := goro.NewHandle(ctx).Go(
455 > func(ctx context.Context) error {
456 > // Outer loop handles watch restarts after connection failures.
457 > for {
458 > select {
459 case <-ctx.Done():
460 return nil
461 > default: registry.go
462 }
463
464 > result, err := r.startWatch(ctx, !watchStartedOnce.IsShutdown()) registry.go
465 > if err != nil {
466 return err
467 }
476 // Wait for either the watch to start successfully, or the goroutine to exit (due to error
477 // or because watch is not supported). Return true only if watch started successfully.
478 > select { registry.go
479 case <-watchStartedOnce.Channel():
480 return handle, true
486 // startWatch attempts to establish a namespace watch with retries.
487 // Returns the watch channel and context on success.
488 > func (r *registry) startWatch(ctx context.Context, initialWatch bool) (watchStartResult, error) { registry.go
489 > return backoff.ThrottleRetryContextWithReturn(
490 > ctx,
491 > func(ctx context.Context) (startResult watchStartResult, err error) {
492 > // Create fresh watch context for this attempt
493 > watchCtx, watchCancel := context.WithCancel(ctx)
494 > defer func() {
495 > if err != nil {
496 // Cancel attempt's watch context to clean up any partial watch state
497 watchCancel()
499 }()
500
501 > startResult.watchCtx = watchCtx registry.go
502 > startResult.watchCancel = watchCancel
503 >
504 > if startResult.eventCh, err = r.persistence.WatchNamespaces(watchCtx); err != nil {
505 if !errors.Is(err, persistence.ErrWatchNotSupported) {
506 r.logger.Error("Error starting namespace watch", tag.Error(err))
554 }
555
556 > func (r *registry) refreshNamespaces(ctx context.Context) (err error) { registry.go
557 > start := time.Now()
558 > defer func() {
559 > if err != nil {
560 metrics.NamespaceRegistryRefreshFailures.With(r.metricsHandler).Record(1)
561 }
562 > metrics.NamespaceRegistryRefreshLatency.With(r.metricsHandler).Record(time.Since(start)) registry.go
563 }()
564
565 > request := &persistence.ListNamespacesRequest{ registry.go
566 > PageSize: CacheRefreshPageSize,
567 > IncludeDeleted: true,
568 > }
569 > var namespacesDb namespace.Namespaces
570 > namespaceIDsDb := make(map[namespace.ID]struct{})
571 >
572 > for {
573 > // TODO: consider adding a timeout and/or retries here - long ListNamespaces
574 > // calls could delay watch reconnection or block shutdown
575 > response, err := r.persistence.ListNamespaces(ctx, request)
576 > if err != nil {
577 return err
578 }
579 > for _, namespaceDb := range response.Namespaces { registry.go
580 ns, err := namespace.FromPersistentState(
581 namespaceDb.Namespace,
590 namespaceIDsDb[namespace.ID(namespaceDb.Namespace.Info.Id)] = struct{}{}
591 }
592 > if len(response.NextPageToken) == 0 { registry.go
593 > break
594 }
595 request.NextPageToken = response.NextPageToken
597
598 // Make a copy of the existing namespace maps (excluding deleted), so we can calculate diff and do atomic swap.
599 > newNameToID := make(map[namespace.Name]namespace.ID) registry.go
600 > newIDToNamespace := make(map[namespace.ID]*namespace.Namespace)
601 >
602 > var deletedEntries []*namespace.Namespace
603 > for _, ns := range r.GetAllNamespaces() {
604 if _, namespaceExistsDb := namespaceIDsDb[ns.ID()]; !namespaceExistsDb {
605 deletedEntries = append(deletedEntries, ns)
610 }
611
612 > var stateChanged []*namespace.Namespace registry.go
613 > for _, aNamespace := range namespacesDb {
614 oldNS := r.updateIDToNamespace(newIDToNamespace, aNamespace.ID(), aNamespace)
615 // If namespace was renamed, remove entry for the old name
624 }
625
626 > r.nsMapsLock.Lock() registry.go
627 > totalNamespaceCount := len(newIDToNamespace) // record metric value within lock boundary
628 > r.idToNamespace = newIDToNamespace
629 > r.nameToID = newNameToID
630 > stateChanged = append(stateChanged, r.stateChangedDuringReadthrough...)
631 > r.stateChangedDuringReadthrough = nil
632 > r.nsMapsLock.Unlock()
633 >
634 > metrics.TotalNamespaces.With(r.metricsHandler).Record(float64(totalNamespaceCount))
635 >
636 > r.stateChangeCallbacks.Range(
637 > func(_, value any) bool {
638 //revive:disable-next-line:unchecked-type-assertion
639 cb := value.(namespace.StateChangeCallbackFn)
714 id namespace.ID,
715 newNS *namespace.Namespace,
716 > ) *namespace.Namespace { registry.go
717 > oldNS := iDToNamespace[id]
718 > iDToNamespace[id] = newNS
719 > return oldNS
720 > }
721
722 // getNamespace retrieves the information from the cache if it exists
894 }
895
896 > func (r *registry) namespaceStateChanged(oldNS *namespace.Namespace, newNS *namespace.Namespace) bool { registry.go
897 > return r.namespaceStateChangedFn(r.currentClusterName, oldNS, newNS)
898 > }
go.temporal.io/server/common/namespace/mutate.go 5 introduced LOC · 1 range

Open complete file

51
52 // WithNotificationVersion assigns a notification version to the Namespace.
53 > func WithNotificationVersion(v int64) Mutation { mutate.go
54 > return mutationFunc(
55 > func(ns *Namespace) {
56 > ns.notificationVersion = v
57 > })
58 }
59