go.temporal.io/server/chasm/registry.go

389 LOC · 213 covered · 176 uncovered · 94 ranges · 4478 concepts · 65 introducers · 1704 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

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 related-file, concept, and source links on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 package chasm
2
3 import (
4 "errors"
5 "fmt"
6 "maps"
7 "reflect"
8 "regexp"
9 "strings"
10
11 "github.com/nexus-rpc/sdk-go/nexus"
12 "go.temporal.io/server/common/log"
13 "google.golang.org/grpc"
14 )
15
16 var (
17 // This is golang type identifier regex.
18 nameValidator = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`)
19 )
20
21 type (
22 Registry struct {
23 libraries map[string]Library // library name -> library
24
25 // rc stands for RegistrableComponent.
26 rcByFqn map[string]*RegistrableComponent // fully qualified type name -> component
27 rcByID map[uint32]*RegistrableComponent // component type ID -> component
28 rcByGoType map[reflect.Type]*RegistrableComponent // component go type -> component
29 // rcContextValues is aggregated context values from all components,
30 // used for easy lookup when Context.Value(key) is called.
31 // Registration process will check for key conflicts and return error if same key is registered by multiple components.
32 rcContextValues map[any]valueWithFqn
33
34 // rt stands for RegistrableTask.
35 rtByFqn map[string]*RegistrableTask // fully qualified type name -> task
36 rtByID map[uint32]*RegistrableTask // task type ID -> task
37 rtByGoType map[reflect.Type]*RegistrableTask // task go type -> task
38
39 nexusServices map[string]*nexus.Service // service name -> nexus service
40 NexusEndpointProcessor *NexusEndpointProcessor
41
42 logger log.Logger
43 }
44 )
45
46 // valueWithFqn is a wrapper struct that associates a value with
47 // the fully qualified name (FQN) of the component that registered it.
48 type valueWithFqn struct {
49 v any
50 fqn string
51 }
52
53 > func NewRegistry(logger log.Logger) *Registry { registry.go ×1
54 > return &Registry{
55 > libraries: make(map[string]Library),
56 > rcByFqn: make(map[string]*RegistrableComponent),
57 > rcByID: make(map[uint32]*RegistrableComponent),
58 > rcByGoType: make(map[reflect.Type]*RegistrableComponent),
59 > rtByFqn: make(map[string]*RegistrableTask),
60 > rtByID: make(map[uint32]*RegistrableTask),
61 > rtByGoType: make(map[reflect.Type]*RegistrableTask),
62 > rcContextValues: make(map[any]valueWithFqn),
63 > nexusServices: make(map[string]*nexus.Service),
64 > NexusEndpointProcessor: NewNexusEndpointProcessor(),
65 > logger: logger,
66 > }
67 > }
68
69 > func (r *Registry) Register(lib Library) error { registry.go ×2
70 > if err := r.validateName(lib.Name()); err != nil {
71 > return err registry.go ×1
72 > }
73 > if _, ok := r.libraries[lib.Name()]; ok { registry.go ×3
74 return fmt.Errorf("library %s is already registered", lib.Name())
75 }
76 > r.libraries[lib.Name()] = lib registry.go ×3
77 >
78 > for _, c := range lib.Components() {
79 > if err := r.registerComponent(lib, c); err != nil { registry.go ×3
80 > return err registry.go ×1
81 > }
82 }
83 > for _, t := range lib.Tasks() { registry.go ×1
84 > if err := r.registerTask(lib, t); err != nil { registrable_task.go ×2
85 > return err registry.go ×1
86 > }
87 }
88
89 > for _, svc := range lib.NexusServices() { registry.go ×1
90 > if err := r.registerNexusService(svc); err != nil { registry.go ×3
91 > return err registry.go ×2
92 > }
93 }
94
95 > for _, svc := range lib.NexusServiceProcessors() { registry.go ×2
96 > if err := r.NexusEndpointProcessor.RegisterServiceProcessor(svc); err != nil { registry.go ×1
97 return err
98 }
99 }
100
101 > return nil registry.go ×2
102 }
103
104 // RegisterServices registers all gRPC services from all registered libraries.
105 > func (r *Registry) RegisterServices(server *grpc.Server) { fx.go ×44
106 > for _, lib := range r.libraries {
107 > lib.RegisterServices(server)
108 > }
109 }
110
111 // ComponentFqnByID converts component type ID to fully qualified component type name.
112 // This method should only be used by CHASM framework internal code,
113 // NOT CHASM library developers.
114 > func (r *Registry) ComponentFqnByID(id uint32) (string, bool) { registry.go ×1
115 > rc, ok := r.rcByID[id]
116 > if !ok {
117 > return "", false ndc_standby_task_util.go ×2
118 > }
119 > return rc.fqType(), true registry.go ×1
120 }
121
122 // ComponentIDByFqn converts fully qualified component type name to component type ID.
123 // This method should only be used by CHASM framework internal code,
124 // NOT CHASM library developers.
125 func (r *Registry) ComponentIDByFqn(fqn string) (uint32, bool) {
126 rc, ok := r.rcByFqn[fqn]
127 if !ok {
128 return 0, false
129 }
130 return rc.componentID, true
131 }
132
133 // ComponentByID returns the registrable component for a given archetype ID.
134 // This method should only be used by CHASM framework internal code,
135 // NOT CHASM library developers.
136 > func (r *Registry) ComponentByID(id uint32) (*RegistrableComponent, bool) { registry.go ×1
137 > rc, ok := r.rcByID[id]
138 > return rc, ok
139 > }
140
141 // ComponentIDFor converts registered component instance to component type ID.
142 // This method should only be used by CHASM framework internal code,
143 // NOT CHASM library developers.
144 > func (r *Registry) ComponentIDFor(componentInstance any) (uint32, bool) { registry.go ×2
145 > rc, ok := r.componentFor(componentInstance)
146 > if !ok {
147 return 0, false
148 }
149 > return rc.componentID, true registry.go ×2
150 }
151
152 // TaskByID returns the registrable task for a given task type ID.
153 // This method should only be used by CHASM framework internal code,
154 // NOT CHASM library developers.
155 > func (r *Registry) TaskByID(id uint32) (*RegistrableTask, bool) { registry.go ×1
156 > rt, ok := r.rtByID[id]
157 > return rt, ok
158 > }
159
160 // TaskFqnByID converts task type ID to fully qualified task type name.
161 // This method should only be used by CHASM framework internal code,
162 // NOT CHASM library developers.
163 > func (r *Registry) TaskFqnByID(id uint32) (string, bool) { metrics.go ×2
164 > rt, ok := r.rtByID[id]
165 > if !ok {
166 > return "", false registry.go ×1
167 > }
168 > return rt.fqType(), true registry.go ×1
169 }
170
171 // TaskIDFor converts registered task instance to task type ID.
172 // This method should only be used by CHASM framework internal code,
173 // NOT CHASM library developers.
174 > func (r *Registry) TaskIDFor(taskInstance any) (uint32, bool) { visibility_queue_task_executor.go ×6
175 > rt, ok := r.taskFor(taskInstance)
176 > if !ok {
177 return 0, false
178 }
179 > return rt.taskTypeID, true visibility_queue_task_executor.go ×6
180 }
181
182 // ArchetypeDisplayName returns the human-readable name for a given archetype ID.
183 // This method should only be used by CHASM framework internal code,
184 // NOT CHASM library developers.
185 > func (r *Registry) ArchetypeDisplayName(id ArchetypeID) (string, bool) { registry.go ×1
186 > rc, ok := r.ComponentByID(id)
187 > if !ok {
188 > return "", false registry.go ×1
189 > }
190 > return rc.componentType, true registry.go ×1
191 }
192
193 // ArchetypeIDOf returns the ArchetypeID for the given component Go type.
194 // This method should only be used by CHASM framework internal code,
195 // NOT CHASM library developers.
196 > func (r *Registry) ArchetypeIDOf(componentGoType reflect.Type) (ArchetypeID, bool) { registry.go ×1
197 > rc, ok := r.rcByGoType[componentGoType]
198 > if !ok {
199 > return UnspecifiedArchetypeID, false registry.go ×1
200 > }
201 > return rc.componentID, true registry.go ×1
202 }
203
204 > func (r *Registry) component(fqn string) (*RegistrableComponent, bool) { registry.go ×1
205 > rc, ok := r.rcByFqn[fqn]
206 > return rc, ok
207 > }
208
209 > func (r *Registry) task(fqn string) (*RegistrableTask, bool) { registry.go ×2
210 > rt, ok := r.rtByFqn[fqn]
211 > return rt, ok
212 > }
213
214 > func (r *Registry) componentFor(componentInstance any) (*RegistrableComponent, bool) { registry.go ×1
215 > rc, ok := r.rcByGoType[reflect.TypeOf(componentInstance)]
216 > return rc, ok
217 > }
218
219 > func (r *Registry) taskFor(taskInstance any) (*RegistrableTask, bool) { registry.go ×1
220 > rt, ok := r.rtByGoType[reflect.TypeOf(taskInstance)]
221 > return rt, ok
222 > }
223
224 > func (r *Registry) componentOf(componentGoType reflect.Type) (*RegistrableComponent, bool) { registry.go ×1
225 > rc, ok := r.rcByGoType[componentGoType]
226 > return rc, ok
227 > }
228
229 > func (r *Registry) taskOf(taskGoType reflect.Type) (*RegistrableTask, bool) { registry.go ×2
230 > rt, ok := r.rtByGoType[taskGoType]
231 > return rt, ok
232 > }
233
234 func (r *Registry) registerComponent(
235 lib namer,
236 rc *RegistrableComponent,
237 > ) error { registry.go ×3
238 > if err := r.validate(rc); err != nil {
239 > return err registry.go ×1
240 > }
241
242 > fqn, id, err := rc.registerToLibrary(lib) registry.go ×6
243 > if err != nil {
244 > return err registrable_component.go ×1
245 > }
246
247 > if _, ok := r.rcByFqn[fqn]; ok { registry.go ×6
248 > return fmt.Errorf("component %s is already registered", fqn) registry.go ×1
249 > }
250
251 > if id == UnspecifiedArchetypeID { registry.go ×6
252 return fmt.Errorf("component %s maps to a reserved archetype id %d, please use a different name", fqn, UnspecifiedArchetypeID)
253 }
254
255 > if existingComponent, ok := r.rcByID[id]; ok { registry.go ×6
256 return fmt.Errorf("component ID %d collision between %s and %s", id, fqn, existingComponent.fqType())
257 }
258
259 > for key, value := range rc.contextValues { registry.go ×6
260 > if existingValue, ok := r.rcContextValues[key]; ok { registry.go ×2
261 return fmt.Errorf("context value key %v registered by component %s conflicts with component %s", key, fqn, existingValue.fqn)
262 }
263 > r.rcContextValues[key] = valueWithFqn{ registry.go ×2
264 > v: value,
265 > fqn: fqn,
266 > }
267 }
268
269 // rc.goType implements Component interface; therefore, it must be a struct.
270 // This check to protect against the interface itself being registered.
271 > if !(rc.goType.Kind() == reflect.Struct || registry.go ×6
272 > (rc.goType.Kind() == reflect.Pointer && rc.goType.Elem().Kind() == reflect.Struct)) {
273 > return fmt.Errorf("component type %s must be struct or pointer to struct", rc.goType.String()) registry.go ×1
274 > }
275 > if _, ok := r.rcByGoType[rc.goType]; ok { registry.go ×4
276 > return fmt.Errorf("component type %s is already registered", rc.goType.String()) registry.go ×1
277 > }
278 > r.warnUnmanagedFields(fqn, rc) registry.go ×4
279 >
280 > r.rcByFqn[fqn] = rc
281 > r.rcByID[id] = rc
282 > r.rcByGoType[rc.goType] = rc
283 > return nil
284 }
285
286 > func (r *Registry) validate(rc *RegistrableComponent) error { registry.go ×3
287 > if err := r.validateName(rc.componentType); err != nil {
288 > return err registry.go ×1
289 > }
290 > return r.validateVisibilityBusinessIDAlias(rc) registry.go ×2
291 }
292
293 func (r *Registry) registerTask(
294 lib namer,
295 rt *RegistrableTask,
296 > ) error { registrable_task.go ×2
297 > if err := r.validateName(rt.taskType); err != nil {
298 > return err registry.go ×1
299 > }
300
301 > fqn, id, err := rt.registerToLibrary(lib) registrable_task.go ×5
302 > if err != nil {
303 > return err registrable_task.go ×1
304 > }
305
306 > if _, ok := r.rtByFqn[fqn]; ok { registrable_task.go ×5
307 > return fmt.Errorf("task %s is already registered", fqn) registry.go ×1
308 > }
309
310 > if existingTask, ok := r.rtByID[id]; ok { registrable_task.go ×5
311 return fmt.Errorf("task type ID %d collision between %s and %s", id, fqn, existingTask.fqType())
312 }
313
314 > if !(rt.goType.Kind() == reflect.Struct || registrable_task.go ×5
315 > (rt.goType.Kind() == reflect.Pointer && rt.goType.Elem().Kind() == reflect.Struct)) {
316 > return fmt.Errorf("task type %s must be struct or pointer to struct", rt.goType.String()) registry.go ×1
317 > }
318 > if _, ok := r.rtByGoType[rt.goType]; ok { registry.go ×3
319 > return fmt.Errorf("task type %s is already registered", rt.goType.String()) registry.go ×1
320 > }
321 > if !(rt.componentGoType.Kind() == reflect.Interface || registry.go ×3
322 > (rt.componentGoType.Kind() == reflect.Struct ||
323 > (rt.componentGoType.Kind() == reflect.Pointer && rt.componentGoType.Elem().Kind() == reflect.Struct)) &&
324 > rt.componentGoType.AssignableTo(reflect.TypeFor[Component]())) {
325 return fmt.Errorf("component type %s must be and interface or struct that implements Component interface", rt.componentGoType.String())
326 }
327
328 > r.rtByFqn[fqn] = rt registry.go ×3
329 > r.rtByID[id] = rt
330 > r.rtByGoType[rt.goType] = rt
331 > return nil
332 }
333
334 > func (r *Registry) validateName(n string) error { registry.go ×2
335 > if n == "" {
336 > return errors.New("name must not be empty") registry.go ×1
337 > }
338 > if !nameValidator.MatchString(n) { registry.go ×1
339 > return fmt.Errorf("name %s is invalid. name must follow golang identifier rules: %s", n, nameValidator.String()) registry.go ×1
340 > }
341 > return nil registry.go ×3
342 }
343
344 > func (r *Registry) validateVisibilityBusinessIDAlias(rc *RegistrableComponent) error { registry.go ×2
345 > if !hasVisibilityField(rc.goType) {
346 > return nil registry.go ×1
347 > }
348 // Archetypes that contain a Field[*Visibility] must specify WithBusinessIDAlias.
349 > if !rc.hasBusinessIDAlias() { registrable_component.go ×1
350 > return fmt.Errorf("component %s has Field[*Visibility] but no businessID alias; use WithBusinessIDAlias option", rc.componentType) registrable_component.go ×1
351 > }
352 > return nil registrable_component.go ×5
353 }
354
355 > func (r *Registry) warnUnmanagedFields(fqn string, rc *RegistrableComponent) { registry.go ×4
356 > var unmanagedFields []string
357 > for f := range unmanagedFieldsOf(rc.goType) {
358 > unmanagedFields = append(unmanagedFields, fmt.Sprintf("%s %s", f.name, f.typ)) registry.go ×2
359 > }
360 > if len(unmanagedFields) > 0 { registry.go ×4
361 > r.logger.Info(fmt.Sprintf( registry.go ×2
362 > "Warning: CHASM component %s declares state fields that won't be managed by CHASM:\n\t%s",
363 > fqn,
364 > strings.Join(unmanagedFields, "\n\t")))
365 > }
366 }
367
368 > func (r *Registry) registerNexusService(svc *nexus.Service) error { registry.go ×3
369 > if _, ok := r.nexusServices[svc.Name]; ok {
370 > return fmt.Errorf("nexus service %s is already registered", svc.Name) registry.go ×2
371 > }
372 > r.nexusServices[svc.Name] = svc registry.go ×3
373 > return nil
374 }
375
376 // NexusServices returns all registered Nexus services.
377 > func (r *Registry) NexusServices() map[string]*nexus.Service { registry.go ×1
378 > // Return a copy to prevent external modification
379 > services := make(map[string]*nexus.Service, len(r.nexusServices))
380 > maps.Copy(services, r.nexusServices)
381 > return services
382 > }
383
384 > func (r *Registry) componentContextValue(key any) any { context.go ×1
385 > if v, ok := r.rcContextValues[key]; ok {
386 > return v.v context.go ×1
387 > }
388 > return nil registry.go ×1
389 }