@@ -79,29 +79,85 @@ func (r *Registry[T]) Set(name string, item T) Result {
7979 return Result {OK : true }
8080}
8181
82- // Get retrieves an item by name.
82+ // GetOrSet returns the existing item for name, or atomically stores and
83+ // returns make() when the name is absent. make() runs only on a miss, so
84+ // callers racing the first lookup converge on a single stored value (the
85+ // LoadOrStore idiom). The common hit path takes only a read lock. Returns
86+ // Result{OK: false} when the registry is sealed or locked and the key is new.
87+ //
88+ // r := reg.GetOrSet("drain", func() *Lock { return &Lock{Name: "drain", Mutex: &RWMutex{}} })
89+ // lock := r.Value.(*Lock)
90+ func (r * Registry [T ]) GetOrSet (name string , make func () T ) Result {
91+ r .mu .RLock ()
92+ item , ok := r .items [name ]
93+ r .mu .RUnlock ()
94+ if ok {
95+ return Result {item , true }
96+ }
97+
98+ r .mu .Lock ()
99+ defer r .mu .Unlock ()
100+ // Re-check under the write lock — another goroutine may have created it
101+ // between the RUnlock above and acquiring the write lock.
102+ if item , ok := r .items [name ]; ok {
103+ return Result {item , true }
104+ }
105+ switch r .mode {
106+ case registryLocked :
107+ return Result {E ("registry.GetOrSet" , Concat ("registry is locked, cannot set: " , name ), nil ), false }
108+ case registrySealed :
109+ return Result {E ("registry.GetOrSet" , Concat ("registry is sealed, cannot add new key: " , name ), nil ), false }
110+ }
111+ item = make ()
112+ r .order = append (r .order , name )
113+ r .items [name ] = item
114+ return Result {item , true }
115+ }
116+
117+ // Get retrieves an item by name. A soft-disabled item resolves as absent
118+ // (Result{OK:false}) so dispatch and resolution skip it — use
119+ // GetIncludingDisabled to inspect or re-enable a disabled entry.
83120//
84121// res := r.Get("brain")
85122// if res.OK { svc := res.Value.(*Service) }
86123func (r * Registry [T ]) Get (name string ) Result {
87124 r .mu .RLock ()
88125 defer r .mu .RUnlock ()
89126
127+ item , ok := r .items [name ]
128+ if ! ok || r .disabled [name ] {
129+ return Result {}
130+ }
131+ return Result {item , true }
132+ }
133+
134+ // GetIncludingDisabled retrieves an item by name even when it is soft-disabled.
135+ // Get/Has treat a disabled entry as absent (so dispatch skips it); this variant
136+ // is for inspection, re-enable, and registration existence-checks that must see
137+ // every registered key.
138+ //
139+ // res := r.GetIncludingDisabled("broken-handler")
140+ func (r * Registry [T ]) GetIncludingDisabled (name string ) Result {
141+ r .mu .RLock ()
142+ defer r .mu .RUnlock ()
143+
90144 item , ok := r .items [name ]
91145 if ! ok {
92146 return Result {}
93147 }
94148 return Result {item , true }
95149}
96150
97- // Has returns true if the name exists in the registry.
151+ // Has returns true if the name exists and is enabled. A soft-disabled item
152+ // reports false (consistent with Get); use GetIncludingDisabled to check raw
153+ // existence regardless of disabled state.
98154//
99155// if r.Has("brain") { ... }
100156func (r * Registry [T ]) Has (name string ) bool {
101157 r .mu .RLock ()
102158 defer r .mu .RUnlock ()
103159 _ , ok := r .items [name ]
104- return ok
160+ return ok && ! r . disabled [ name ]
105161}
106162
107163// Names returns all registered names in insertion order.
@@ -124,14 +180,17 @@ func (r *Registry[T]) List(pattern string) []T {
124180 r .mu .RLock ()
125181 defer r .mu .RUnlock ()
126182
127- var result []T
183+ result := make ( []T , 0 , len ( r . order ))
128184 for _ , name := range r .order {
129185 if matched := PathMatch (pattern , name ); matched .OK && matched .Value .(bool ) {
130186 if ! r .disabled [name ] {
131187 result = append (result , r .items [name ])
132188 }
133189 }
134190 }
191+ if len (result ) == 0 {
192+ return nil // byte-identical to the old var-nil behaviour
193+ }
135194 return result
136195}
137196
@@ -187,8 +246,10 @@ func (r *Registry[T]) Delete(name string) Result {
187246 return Result {OK : true }
188247}
189248
190- // Disable soft-disables an item. It still exists but Each/List skip it.
191- // Returns Result{OK: false} if not found.
249+ // Disable soft-disables an item. It still exists but Get/Has/List/Each skip it,
250+ // so it cannot be resolved or dispatched until Enable is called. Inspect or
251+ // re-enable it via GetIncludingDisabled / Disabled. Returns Result{OK: false}
252+ // if not found.
192253//
193254// r.Disable("broken-handler")
194255func (r * Registry [T ]) Disable (name string ) Result {
0 commit comments