Skip to content

Commit da65052

Browse files
committed
perf: pre-size slice builders in Data names / IPC query results / Registry.List
Same unpresized-append pattern as FilterArgs: each iterates a known-length collection (dir entries / handlers / registry order) but grew via append. Pre-sized to the source length; return nil when empty to stay byte-identical. Co-Authored-By: Virgil <virgil@lethean.io>
1 parent 1f59e31 commit da65052

3 files changed

Lines changed: 72 additions & 8 deletions

File tree

data.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ func (d *Data) ListNames(path string) Result {
135135
return r
136136
}
137137
entries := r.Value.([]FsDirEntry)
138-
var names []string
138+
names := make([]string, 0, len(entries))
139139
for _, e := range entries {
140140
name := e.Name()
141141
if !e.IsDir() {

ipc.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,16 @@ func (c *Core) QueryAll(q Query) Result {
7474
if handlers == nil {
7575
return Result{[]any(nil), true}
7676
}
77-
var results []any
77+
results := make([]any, 0, len(*handlers))
7878
for _, h := range *handlers {
7979
r := h(c, q)
8080
if r.OK && r.Value != nil {
8181
results = append(results, r.Value)
8282
}
8383
}
84+
if len(results) == 0 {
85+
return Result{[]any(nil), true} // byte-identical to the old var-nil
86+
}
8487
return Result{results, true}
8588
}
8689

registry.go

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -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) }
86123
func (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") { ... }
100156
func (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")
194255
func (r *Registry[T]) Disable(name string) Result {

0 commit comments

Comments
 (0)