Skip to content

Commit 7497389

Browse files
committed
History: Expose and call SetContextMetadata in readonly operations
1 parent 183c98c commit 7497389

File tree

8 files changed

+140
-4
lines changed

8 files changed

+140
-4
lines changed

common/contextutil/metadata.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ type (
1717

1818
var metadataCtxKey = metadataContextKey{}
1919

20+
const (
21+
// MetadataKeyWorkflowType is the context metadata key for workflow type
22+
MetadataKeyWorkflowType = "workflow-type"
23+
// MetadataKeyWorkflowTaskQueue is the context metadata key for workflow task queue
24+
MetadataKeyWorkflowTaskQueue = "workflow-task-queue"
25+
)
26+
2027
// getMetadataContext extracts metadata context from golang context.
2128
func getMetadataContext(ctx context.Context) *metadataContext {
2229
metadataCtx := ctx.Value(metadataCtxKey)
@@ -38,6 +45,12 @@ func WithMetadataContext(ctx context.Context) context.Context {
3845
return context.WithValue(ctx, metadataCtxKey, metadataCtx)
3946
}
4047

48+
// ContextHasMetadata returns true if the context has metadata support.
49+
// This can be used to debug whether a context has been properly initialized with metadata.
50+
func ContextHasMetadata(ctx context.Context) bool {
51+
return getMetadataContext(ctx) != nil
52+
}
53+
4154
// ContextMetadataSet sets a metadata key-value pair in the context.
4255
func ContextMetadataSet(ctx context.Context, key string, value any) bool {
4356
metadataCtx := getMetadataContext(ctx)

common/contextutil/metadata_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,3 +301,63 @@ func TestMetadataContextIsolation(t *testing.T) {
301301
assert.Equal(t, "value2", value2)
302302
})
303303
}
304+
305+
func TestContextHasMetadata(t *testing.T) {
306+
t.Run("returns true when context has metadata", func(t *testing.T) {
307+
ctx := WithMetadataContext(context.Background())
308+
assert.True(t, ContextHasMetadata(ctx))
309+
})
310+
311+
t.Run("returns false for context without metadata", func(t *testing.T) {
312+
ctx := context.Background()
313+
assert.False(t, ContextHasMetadata(ctx))
314+
})
315+
316+
t.Run("returns true after setting metadata values", func(t *testing.T) {
317+
ctx := WithMetadataContext(context.Background())
318+
ContextMetadataSet(ctx, "key1", "value1")
319+
ContextMetadataSet(ctx, "key2", "value2")
320+
321+
assert.True(t, ContextHasMetadata(ctx))
322+
})
323+
324+
t.Run("returns true for empty metadata context", func(t *testing.T) {
325+
ctx := WithMetadataContext(context.Background())
326+
// No values set, but metadata context exists
327+
assert.True(t, ContextHasMetadata(ctx))
328+
})
329+
330+
t.Run("child context inherits metadata from parent", func(t *testing.T) {
331+
parentCtx := WithMetadataContext(context.Background())
332+
ContextMetadataSet(parentCtx, "key", "value")
333+
334+
type testContextKey string
335+
childCtx := context.WithValue(parentCtx, testContextKey("other-key"), "other-value")
336+
337+
assert.True(t, ContextHasMetadata(parentCtx))
338+
assert.True(t, ContextHasMetadata(childCtx))
339+
})
340+
341+
t.Run("returns false for wrong type in context", func(t *testing.T) {
342+
ctx := context.WithValue(context.Background(), metadataCtxKey, "wrong type")
343+
assert.False(t, ContextHasMetadata(ctx))
344+
})
345+
346+
t.Run("returns true for cancelled context with metadata", func(t *testing.T) {
347+
ctx, cancel := context.WithCancel(context.Background())
348+
ctx = WithMetadataContext(ctx)
349+
cancel()
350+
351+
assert.True(t, ContextHasMetadata(ctx))
352+
})
353+
354+
t.Run("multiple contexts with metadata are independent", func(t *testing.T) {
355+
ctx1 := WithMetadataContext(context.Background())
356+
ctx2 := WithMetadataContext(context.Background())
357+
ctx3 := context.Background()
358+
359+
assert.True(t, ContextHasMetadata(ctx1))
360+
assert.True(t, ContextHasMetadata(ctx2))
361+
assert.False(t, ContextHasMetadata(ctx3))
362+
})
363+
}

service/history/api/queryworkflow/api.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ func Invoke(
8080
workflowLease.GetReleaseFn()(nil)
8181
}()
8282

83+
// Context metadata is automatically set during mutable state transaction close for operations that mutate state.
84+
// Since QueryWorkflow is readonly and never closes the transaction, we explicitly call SetContextMetadata
85+
// here to ensure successful requests have workflow metadata populated in the context.
86+
workflowLease.GetMutableState().SetContextMetadata(ctx)
87+
8388
req := request.GetRequest()
8489
_, mutableStateStatus := workflowLease.GetMutableState().GetWorkflowStateStatus()
8590
scope = scope.WithTags(metrics.StringTag("workflow_status", mutableStateStatus.String()))

service/history/api/respondworkflowtaskcompleted/api.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,13 @@ func (handler *WorkflowTaskCompletedHandler) Invoke(
180180
}
181181
}
182182

183+
if len(request.Commands) == 0 && len(request.Messages) > 0 {
184+
// Context metadata is automatically set during mutable state transaction close. For RespondWorkflowTaskCompleted
185+
// with only `update.Rejection` messages, the transaction is never closed. We explicitly call SetContextMetadata
186+
// here to ensure readonly messages have workflow metadata populated in the context.
187+
ms.SetContextMetadata(ctx)
188+
}
189+
183190
workflowLease.GetReleaseFn()(errForRelease)
184191
}()
185192

service/history/interfaces/mutable_state.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ type (
289289
UpdateBuildIdAssignment(buildId string) error
290290
ApplyBuildIdRedirect(startingTaskScheduledEventId int64, buildId string, redirectCounter int64) error
291291
RefreshExpirationTimeoutTask(ctx context.Context) error
292+
SetContextMetadata(context.Context)
292293

293294
GetHistorySize() int64
294295
AddHistorySize(size int64)

service/history/interfaces/mutable_state_mock.go

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

service/history/workflow/mutable_state_impl.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6993,19 +6993,19 @@ type closeTransactionResult struct {
69936993
chasmNodesMutation chasm.NodesMutation
69946994
}
69956995

6996-
func (ms *MutableStateImpl) setMetaDataMap(
6996+
func (ms *MutableStateImpl) SetContextMetadata(
69976997
ctx context.Context,
69986998
) {
69996999
switch ms.chasmTree.ArchetypeID() {
70007000
case chasm.WorkflowArchetypeID, chasm.UnspecifiedArchetypeID:
70017001
// Set workflow type
70027002
if wfType := ms.GetWorkflowType(); wfType != nil && wfType.GetName() != "" {
7003-
contextutil.ContextMetadataSet(ctx, "workflow-type", wfType.GetName())
7003+
contextutil.ContextMetadataSet(ctx, contextutil.MetadataKeyWorkflowType, wfType.GetName())
70047004
}
70057005

70067006
// Set workflow task queue
70077007
if ms.executionInfo != nil && ms.executionInfo.TaskQueue != "" {
7008-
contextutil.ContextMetadataSet(ctx, "workflow-task-queue", ms.executionInfo.TaskQueue)
7008+
contextutil.ContextMetadataSet(ctx, contextutil.MetadataKeyWorkflowTaskQueue, ms.executionInfo.TaskQueue)
70097009
}
70107010

70117011
// TODO: To set activity_type/activity_task_queue metadata, the history gRPC handler should
@@ -7019,7 +7019,7 @@ func (ms *MutableStateImpl) closeTransaction(
70197019
ctx context.Context,
70207020
transactionPolicy historyi.TransactionPolicy,
70217021
) (closeTransactionResult, error) {
7022-
ms.setMetaDataMap(ctx)
7022+
ms.SetContextMetadata(ctx)
70237023

70247024
if err := ms.closeTransactionWithPolicyCheck(
70257025
transactionPolicy,

service/history/workflow/mutable_state_impl_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
"go.temporal.io/server/chasm"
3535
"go.temporal.io/server/common"
3636
"go.temporal.io/server/common/cluster"
37+
"go.temporal.io/server/common/contextutil"
3738
"go.temporal.io/server/common/definition"
3839
"go.temporal.io/server/common/dynamicconfig"
3940
"go.temporal.io/server/common/failure"
@@ -6110,3 +6111,40 @@ func (s *mutableStateSuite) TestCHASMNodeSize() {
61106111
expectedTotalSize += len(newNodeKey) + newNode.Size()
61116112
s.Equal(expectedTotalSize, mutableState.GetApproximatePersistedSize())
61126113
}
6114+
6115+
func (s *mutableStateSuite) TestSetContextMetadata() {
6116+
s.mockEventsCache.EXPECT().PutEvent(gomock.Any(), gomock.Any()).Times(1)
6117+
6118+
ctx := contextutil.WithMetadataContext(context.Background())
6119+
workflowType := "test-workflow-type"
6120+
taskQueue := "test-task-queue"
6121+
6122+
execution := &commonpb.WorkflowExecution{
6123+
WorkflowId: tests.WorkflowID,
6124+
RunId: tests.RunID,
6125+
}
6126+
6127+
_, err := s.mutableState.AddWorkflowExecutionStartedEvent(
6128+
execution,
6129+
&historyservice.StartWorkflowExecutionRequest{
6130+
NamespaceId: tests.NamespaceID.String(),
6131+
StartRequest: &workflowservice.StartWorkflowExecutionRequest{
6132+
WorkflowId: tests.WorkflowID,
6133+
WorkflowType: &commonpb.WorkflowType{Name: workflowType},
6134+
TaskQueue: &taskqueuepb.TaskQueue{Name: taskQueue},
6135+
},
6136+
},
6137+
)
6138+
s.NoError(err)
6139+
6140+
s.mutableState.SetContextMetadata(ctx)
6141+
6142+
// Verify metadata was set correctly
6143+
wfType, ok := contextutil.ContextMetadataGet(ctx, contextutil.MetadataKeyWorkflowType)
6144+
s.True(ok)
6145+
s.Equal(workflowType, wfType)
6146+
6147+
tq, ok := contextutil.ContextMetadataGet(ctx, contextutil.MetadataKeyWorkflowTaskQueue)
6148+
s.True(ok)
6149+
s.Equal(taskQueue, tq)
6150+
}

0 commit comments

Comments
 (0)