go.temporal.io/server/common/contextutil/metadata.go

190 LOC · 102 covered · 88 uncovered · 36 ranges · 1100 concepts · 33 introducers · 460 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 contextutil
2
3 import (
4 "context"
5 "maps"
6 "strconv"
7 "strings"
8 "sync"
9 )
10
11 type (
12 metadataContextKey struct{}
13
14 // metadataContext is used to store workflow and activity metadata
15 metadataContext struct {
16 sync.Mutex
17 Metadata map[string]any
18 MarkedActivityIDs map[string]struct{}
19 }
20 )
21
22 var metadataCtxKey = metadataContextKey{}
23
24 const (
25 // MetadataKeyWorkflowType is the context metadata key for workflow type.
26 // These keys are serialized into a protobuf message (ContextMetadata) and sent
27 // in the "contextmetadata-bin" gRPC trailer. The "-bin" suffix causes gRPC to
28 // base64-encode the value on the wire, so metadata values may contain arbitrary
29 // bytes including HTTP/2-unsafe control characters.
30 MetadataKeyWorkflowType = "workflow-type"
31 // MetadataKeyWorkflowTaskQueue is the context metadata key for workflow task queue
32 MetadataKeyWorkflowTaskQueue = "workflow-task-queue"
33 // MetadataKeyStandaloneActivityType is the context metadata key for standalone activity type.
34 MetadataKeyStandaloneActivityType = "standalone-activity-type"
35 // MetadataKeyStandaloneActivityTaskQueue is the context metadata key for standalone activity task queue.
36 MetadataKeyStandaloneActivityTaskQueue = "standalone-activity-task-queue"
37
38 activityTypePrefix = "activity-type-"
39 activityTaskQueuePrefix = "activity-task-queue-"
40 )
41
42 // ActivityTypeKey returns the metadata key for an activity's type, keyed by scheduled event ID.
43 > func ActivityTypeKey(scheduledEventID int64) string { metadata.go ×1
44 > return activityTypePrefix + strconv.FormatInt(scheduledEventID, 10)
45 > }
46
47 // ActivityTaskQueueKey returns the metadata key for an activity's task queue, keyed by scheduled event ID.
48 > func ActivityTaskQueueKey(scheduledEventID int64) string { metadata.go ×1
49 > return activityTaskQueuePrefix + strconv.FormatInt(scheduledEventID, 10)
50 > }
51
52 // ContextMetadataGetActivityTypeAndTaskQueue scans the context metadata for a single
53 // activity's type and task queue. Returns false if no activity metadata is found
54 // or if multiple activities are present.
55 > func ContextMetadataGetActivityTypeAndTaskQueue(ctx context.Context) (activityType string, taskQueue string, ok bool) { metadata.go ×1
56 > metadataCtx := getMetadataContext(ctx)
57 > if metadataCtx == nil {
58 > return "", "", false metadata.go ×1
59 > }
60
61 > metadataCtx.Lock() metadata.go ×1
62 > defer metadataCtx.Unlock()
63 >
64 > var foundType, foundTaskQueue bool
65 > for key, value := range metadataCtx.Metadata {
66 > if strings.HasPrefix(key, activityTypePrefix) { metadata.go ×1
67 > if foundType { metadata.go ×2
68 > return "", "", false metadata.go ×1
69 > }
70 > activityType, foundType = value.(string) metadata.go ×2
71 > } else if strings.HasPrefix(key, activityTaskQueuePrefix) { metadata.go ×2
72 > if foundTaskQueue {
73 return "", "", false
74 }
75 > taskQueue, foundTaskQueue = value.(string) metadata.go ×2
76 }
77 }
78
79 > return activityType, taskQueue, foundType && foundTaskQueue metadata.go ×1
80 }
81
82 // ContextMetadataMarkActivityID marks an activity ID on the context for metadata resolution.
83 // The handler knows which activity (from the task token) but not its type or task queue.
84 // Mutable state knows the activity details but not which activity the request targets.
85 // This bridges the two: the handler marks the ID, and SetContextMetadata (during
86 // closeTransaction) resolves it to type and task queue from mutable state.
87 // Cannot be used for transactions that remove the activity from mutable state
88 // (e.g., activity completion), since it won't be available for resolution.
89 > func ContextMetadataMarkActivityID(ctx context.Context, activityID string) bool { metadata.go ×1
90 > metadataCtx := getMetadataContext(ctx)
91 > if metadataCtx == nil {
92 > return false metadata.go ×1
93 > }
94 > metadataCtx.Lock() metadata.go ×2
95 > defer metadataCtx.Unlock()
96 > metadataCtx.MarkedActivityIDs[activityID] = struct{}{}
97 > return true
98 }
99
100 // ContextMetadataGetMarkedActivityIDs returns the marked activity IDs from the context.
101 > func ContextMetadataGetMarkedActivityIDs(ctx context.Context) []string { metadata.go ×1
102 > metadataCtx := getMetadataContext(ctx)
103 > if metadataCtx == nil {
104 > return nil metadata.go ×1
105 > }
106
107 > metadataCtx.Lock() metadata.go ×1
108 > defer metadataCtx.Unlock()
109 >
110 > if len(metadataCtx.MarkedActivityIDs) == 0 {
111 > return nil metadata.go ×1
112 > }
113 > ids := make([]string, 0, len(metadataCtx.MarkedActivityIDs)) metadata.go ×2
114 > for id := range metadataCtx.MarkedActivityIDs {
115 > ids = append(ids, id)
116 > }
117 > return ids
118 }
119
120 // getMetadataContext extracts metadata context from golang context.
121 > func getMetadataContext(ctx context.Context) *metadataContext { metadata.go ×1
122 > metadataCtx := ctx.Value(metadataCtxKey)
123 > if metadataCtx == nil {
124 > return nil metadata.go ×1
125 > }
126 > mc, ok := metadataCtx.(*metadataContext) metadata.go ×1
127 > if !ok {
128 > return nil metadata.go ×1
129 > }
130 > return mc metadata.go ×1
131 }
132
133 // WithMetadataContext adds a metadata context to the given context.
134 > func WithMetadataContext(ctx context.Context) context.Context { metadata.go ×1
135 > metadataCtx := &metadataContext{
136 > Metadata: make(map[string]any),
137 > MarkedActivityIDs: make(map[string]struct{}),
138 > }
139 > return context.WithValue(ctx, metadataCtxKey, metadataCtx)
140 > }
141
142 // ContextHasMetadata returns true if the context has metadata support.
143 // This can be used to debug whether a context has been properly initialized with metadata.
144 > func ContextHasMetadata(ctx context.Context) bool { metadata.go ×1
145 > return getMetadataContext(ctx) != nil
146 > }
147
148 // ContextMetadataSet sets a metadata key-value pair in the context, overwriting any existing value.
149 > func ContextMetadataSet(ctx context.Context, key string, value any) bool { metadata.go ×1
150 > metadataCtx := getMetadataContext(ctx)
151 > if metadataCtx == nil {
152 > return false metadata.go ×1
153 > }
154
155 > metadataCtx.Lock() metadata.go ×1
156 > defer metadataCtx.Unlock()
157 >
158 > metadataCtx.Metadata[key] = value
159 > return true
160 }
161
162 // ContextMetadataGet retrieves a metadata value from the context.
163 > func ContextMetadataGet(ctx context.Context, key string) (any, bool) { metadata.go ×1
164 > metadataCtx := getMetadataContext(ctx)
165 > if metadataCtx == nil {
166 > return nil, false metadata.go ×1
167 > }
168
169 > metadataCtx.Lock() metadata.go ×1
170 > defer metadataCtx.Unlock()
171 >
172 > value, ok := metadataCtx.Metadata[key]
173 > return value, ok
174 }
175
176 // ContextMetadataGetAll retrieves all metadata from the context as a map copy.
177 > func ContextMetadataGetAll(ctx context.Context) map[string]any { metadata.go ×1
178 > metadataCtx := getMetadataContext(ctx)
179 > if metadataCtx == nil {
180 > return nil metadata.go ×1
181 > }
182
183 > metadataCtx.Lock() metadata.go ×1
184 > defer metadataCtx.Unlock()
185 >
186 > // Return a copy to prevent external modifications
187 > result := make(map[string]any, len(metadataCtx.Metadata))
188 > maps.Copy(result, metadataCtx.Metadata)
189 > return result
190 }