diff --git a/internal/internal_event_handlers.go b/internal/internal_event_handlers.go index 582b79778..787baf223 100644 --- a/internal/internal_event_handlers.go +++ b/internal/internal_event_handlers.go @@ -475,11 +475,17 @@ func (wc *workflowEnvironmentImpl) ExecuteChildWorkflow( } memo, err := getWorkflowMemo(params.Memo, wc.dataConverter) if err != nil { + if wc.sdkFlags.tryUse(SDKFlagChildWorkflowErrorExecution, !wc.isReplay) { + startedHandler(WorkflowExecution{}, &ChildWorkflowExecutionAlreadyStartedError{}) + } callback(nil, err) return } searchAttr, err := serializeSearchAttributes(params.SearchAttributes) if err != nil { + if wc.sdkFlags.tryUse(SDKFlagChildWorkflowErrorExecution, !wc.isReplay) { + startedHandler(WorkflowExecution{}, &ChildWorkflowExecutionAlreadyStartedError{}) + } callback(nil, err) return } @@ -506,6 +512,9 @@ func (wc *workflowEnvironmentImpl) ExecuteChildWorkflow( command, err := wc.commandsHelper.startChildWorkflowExecution(attributes) if _, ok := err.(*childWorkflowExistsWithId); ok { + if wc.sdkFlags.tryUse(SDKFlagChildWorkflowErrorExecution, !wc.isReplay) { + startedHandler(WorkflowExecution{}, &ChildWorkflowExecutionAlreadyStartedError{}) + } callback(nil, &ChildWorkflowExecutionAlreadyStartedError{}) return } diff --git a/internal/internal_flags.go b/internal/internal_flags.go index 563f3f2a7..898861b1d 100644 --- a/internal/internal_flags.go +++ b/internal/internal_flags.go @@ -37,7 +37,10 @@ const ( // LimitChangeVersionSASize will limit the search attribute size of TemporalChangeVersion to 2048 when // calling GetVersion. If the limit is exceeded the search attribute is not updated. SDKFlagLimitChangeVersionSASize = 1 - SDKFlagUnknown = math.MaxUint32 + // SDKFlagChildWorkflowErrorExecution return errors to child workflow execution future if the child workflow would + // fail in the synchronous path. + SDKFlagChildWorkflowErrorExecution = 2 + SDKFlagUnknown = math.MaxUint32 ) func sdkFlagFromUint(value uint32) sdkFlag { @@ -46,6 +49,8 @@ func sdkFlagFromUint(value uint32) sdkFlag { return SDKFlagUnset case uint32(SDKFlagLimitChangeVersionSASize): return SDKFlagLimitChangeVersionSASize + case uint32(SDKFlagChildWorkflowErrorExecution): + return SDKFlagChildWorkflowErrorExecution default: return SDKFlagUnknown } diff --git a/test/integration_test.go b/test/integration_test.go index e4c90363e..ed4ec7644 100644 --- a/test/integration_test.go +++ b/test/integration_test.go @@ -1041,6 +1041,16 @@ func (ts *IntegrationTestSuite) TestChildWorkflowDuplicatePanic_Regression() { ts.NoError(err) } +func (ts *IntegrationTestSuite) TestChildWorkflowDuplicateGetExecutionStuck_Regression() { + wfid := "test-child-workflow-duplicate-get-execution-stuck-regression" + run, err := ts.client.ExecuteWorkflow(context.Background(), + ts.startWorkflowOptions(wfid), + ts.workflows.ChildWorkflowDuplicateGetExecutionStuckRepro) + ts.NoError(err) + err = run.Get(context.Background(), nil) + ts.NoError(err) +} + func (ts *IntegrationTestSuite) TestCancelActivityImmediately() { var expected []string err := ts.executeWorkflow("test-cancel-activity-immediately", ts.workflows.CancelActivityImmediately, &expected) diff --git a/test/replaytests/duplicate-child-workflow.json b/test/replaytests/duplicate-child-workflow.json new file mode 100644 index 000000000..7f1d096e7 --- /dev/null +++ b/test/replaytests/duplicate-child-workflow.json @@ -0,0 +1,457 @@ +{ + "events": [ + { + "eventId": "1", + "eventTime": "2023-03-09T07:03:18.898892959Z", + "eventType": "WorkflowExecutionStarted", + "taskId": "1054598", + "workflowExecutionStartedEventAttributes": { + "workflowType": { + "name": "DuplicateChildWorkflow" + }, + "taskQueue": { + "name": "replay-test", + "kind": "Normal" + }, + "workflowExecutionTimeout": "0s", + "workflowRunTimeout": "0s", + "workflowTaskTimeout": "10s", + "originalExecutionRunId": "9708cdef-8846-4d04-84db-540198fff64a", + "identity": "33044@Quinn-Klassens-MacBook-Pro.local@", + "firstExecutionRunId": "9708cdef-8846-4d04-84db-540198fff64a", + "attempt": 1, + "firstWorkflowTaskBackoff": "0s", + "header": { + + } + } + }, + { + "eventId": "2", + "eventTime": "2023-03-09T07:03:18.898908001Z", + "eventType": "WorkflowTaskScheduled", + "taskId": "1054599", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "replay-test", + "kind": "Normal" + }, + "startToCloseTimeout": "10s", + "attempt": 1 + } + }, + { + "eventId": "3", + "eventTime": "2023-03-09T07:03:18.908087209Z", + "eventType": "WorkflowTaskStarted", + "taskId": "1054606", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "2", + "identity": "33044@Quinn-Klassens-MacBook-Pro.local@", + "requestId": "c382d9f2-0c43-4e04-9bf8-b553e11eb611", + "historySizeBytes": "510" + } + }, + { + "eventId": "4", + "eventTime": "2023-03-09T07:03:18.913147126Z", + "eventType": "WorkflowTaskCompleted", + "taskId": "1054610", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "2", + "startedEventId": "3", + "identity": "33044@Quinn-Klassens-MacBook-Pro.local@", + "binaryChecksum": "8ac5f756996609f24b6d575cbea47992", + "sdkMetadata": { + + }, + "meteringMetadata": { + + } + } + }, + { + "eventId": "5", + "eventTime": "2023-03-09T07:03:18.913219709Z", + "eventType": "StartChildWorkflowExecutionInitiated", + "taskId": "1054611", + "startChildWorkflowExecutionInitiatedEventAttributes": { + "namespace": "default", + "namespaceId": "512e1371-2e32-41ef-a00d-1bf138422898", + "workflowId": "ABC-SIMPLE-CHILD-WORKFLOW-ID", + "workflowType": { + "name": "ChildWorkflowWaitOnSignal" + }, + "taskQueue": { + "name": "replay-test", + "kind": "Normal" + }, + "workflowExecutionTimeout": "0s", + "workflowRunTimeout": "0s", + "workflowTaskTimeout": "10s", + "parentClosePolicy": "Terminate", + "workflowTaskCompletedEventId": "4", + "workflowIdReusePolicy": "AllowDuplicate", + "header": { + + } + } + }, + { + "eventId": "6", + "eventTime": "2023-03-09T07:03:18.921805292Z", + "eventType": "ChildWorkflowExecutionStarted", + "taskId": "1054615", + "childWorkflowExecutionStartedEventAttributes": { + "namespace": "default", + "namespaceId": "512e1371-2e32-41ef-a00d-1bf138422898", + "initiatedEventId": "5", + "workflowExecution": { + "workflowId": "ABC-SIMPLE-CHILD-WORKFLOW-ID", + "runId": "5842a3f4-26de-47f9-9e18-75414cccec1c" + }, + "workflowType": { + "name": "ChildWorkflowWaitOnSignal" + }, + "header": { + + } + } + }, + { + "eventId": "7", + "eventTime": "2023-03-09T07:03:18.921809751Z", + "eventType": "WorkflowTaskScheduled", + "taskId": "1054616", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "Quinn-Klassens-MacBook-Pro.local:e8aea3c5-4c46-492d-b120-4676be696b1a", + "kind": "Sticky" + }, + "startToCloseTimeout": "10s", + "attempt": 1 + } + }, + { + "eventId": "8", + "eventTime": "2023-03-09T07:03:18.925350209Z", + "eventType": "WorkflowTaskStarted", + "taskId": "1054620", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "7", + "identity": "33044@Quinn-Klassens-MacBook-Pro.local@", + "requestId": "ad542543-8326-4acc-b104-1200e0e42d93", + "historySizeBytes": "1184" + } + }, + { + "eventId": "9", + "eventTime": "2023-03-09T07:03:18.930087001Z", + "eventType": "WorkflowTaskCompleted", + "taskId": "1054624", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "7", + "startedEventId": "8", + "identity": "33044@Quinn-Klassens-MacBook-Pro.local@", + "binaryChecksum": "8ac5f756996609f24b6d575cbea47992", + "sdkMetadata": { + + }, + "meteringMetadata": { + + } + } + }, + { + "eventId": "10", + "eventTime": "2023-03-09T07:03:18.930093167Z", + "eventType": "TimerStarted", + "taskId": "1054625", + "timerStartedEventAttributes": { + "timerId": "10", + "startToFireTimeout": "1s", + "workflowTaskCompletedEventId": "9" + } + }, + { + "eventId": "11", + "eventTime": "2023-03-09T07:03:19.936924001Z", + "eventType": "TimerFired", + "taskId": "1054628", + "timerFiredEventAttributes": { + "timerId": "10", + "startedEventId": "10" + } + }, + { + "eventId": "12", + "eventTime": "2023-03-09T07:03:19.936953293Z", + "eventType": "WorkflowTaskScheduled", + "taskId": "1054629", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "Quinn-Klassens-MacBook-Pro.local:e8aea3c5-4c46-492d-b120-4676be696b1a", + "kind": "Sticky" + }, + "startToCloseTimeout": "10s", + "attempt": 1 + } + }, + { + "eventId": "13", + "eventTime": "2023-03-09T07:03:19.952608293Z", + "eventType": "WorkflowTaskStarted", + "taskId": "1054633", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "12", + "identity": "33044@Quinn-Klassens-MacBook-Pro.local@", + "requestId": "97208cbd-df19-4407-8cb9-89dafc1e0ced", + "historySizeBytes": "1580" + } + }, + { + "eventId": "14", + "eventTime": "2023-03-09T07:03:19.966412085Z", + "eventType": "WorkflowTaskCompleted", + "taskId": "1054637", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "12", + "startedEventId": "13", + "identity": "33044@Quinn-Klassens-MacBook-Pro.local@", + "binaryChecksum": "8ac5f756996609f24b6d575cbea47992", + "sdkMetadata": { + + }, + "meteringMetadata": { + + } + } + }, + { + "eventId": "15", + "eventTime": "2023-03-09T07:03:19.966430793Z", + "eventType": "TimerStarted", + "taskId": "1054638", + "timerStartedEventAttributes": { + "timerId": "15", + "startToFireTimeout": "1s", + "workflowTaskCompletedEventId": "14" + } + }, + { + "eventId": "16", + "eventTime": "2023-03-09T07:03:20.978645918Z", + "eventType": "TimerFired", + "taskId": "1054641", + "timerFiredEventAttributes": { + "timerId": "15", + "startedEventId": "15" + } + }, + { + "eventId": "17", + "eventTime": "2023-03-09T07:03:20.978747252Z", + "eventType": "WorkflowTaskScheduled", + "taskId": "1054642", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "Quinn-Klassens-MacBook-Pro.local:e8aea3c5-4c46-492d-b120-4676be696b1a", + "kind": "Sticky" + }, + "startToCloseTimeout": "10s", + "attempt": 1 + } + }, + { + "eventId": "18", + "eventTime": "2023-03-09T07:03:20.993582877Z", + "eventType": "WorkflowTaskStarted", + "taskId": "1054646", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "17", + "identity": "33044@Quinn-Klassens-MacBook-Pro.local@", + "requestId": "b74af48e-b017-48d8-954b-1c72c8b1ce1f", + "historySizeBytes": "1976" + } + }, + { + "eventId": "19", + "eventTime": "2023-03-09T07:03:21.006591960Z", + "eventType": "WorkflowTaskCompleted", + "taskId": "1054650", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "17", + "startedEventId": "18", + "identity": "33044@Quinn-Klassens-MacBook-Pro.local@", + "binaryChecksum": "8ac5f756996609f24b6d575cbea47992", + "sdkMetadata": { + + }, + "meteringMetadata": { + + } + } + }, + { + "eventId": "20", + "eventTime": "2023-03-09T07:03:21.006636377Z", + "eventType": "SignalExternalWorkflowExecutionInitiated", + "taskId": "1054651", + "signalExternalWorkflowExecutionInitiatedEventAttributes": { + "workflowTaskCompletedEventId": "19", + "namespace": "default", + "namespaceId": "512e1371-2e32-41ef-a00d-1bf138422898", + "workflowExecution": { + "workflowId": "ABC-SIMPLE-CHILD-WORKFLOW-ID", + "runId": "5842a3f4-26de-47f9-9e18-75414cccec1c" + }, + "signalName": "unblock", + "input": { + "payloads": [ + { + "metadata": { + "encoding": "YmluYXJ5L251bGw=" + } + } + ] + }, + "control": "20", + "header": { + + } + } + }, + { + "eventId": "21", + "eventTime": "2023-03-09T07:03:21.029621418Z", + "eventType": "ExternalWorkflowExecutionSignaled", + "taskId": "1054654", + "externalWorkflowExecutionSignaledEventAttributes": { + "initiatedEventId": "20", + "namespace": "default", + "namespaceId": "512e1371-2e32-41ef-a00d-1bf138422898", + "workflowExecution": { + "workflowId": "ABC-SIMPLE-CHILD-WORKFLOW-ID", + "runId": "5842a3f4-26de-47f9-9e18-75414cccec1c" + }, + "control": "20" + } + }, + { + "eventId": "22", + "eventTime": "2023-03-09T07:03:21.029625502Z", + "eventType": "WorkflowTaskScheduled", + "taskId": "1054655", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "Quinn-Klassens-MacBook-Pro.local:e8aea3c5-4c46-492d-b120-4676be696b1a", + "kind": "Sticky" + }, + "startToCloseTimeout": "10s", + "attempt": 1 + } + }, + { + "eventId": "23", + "eventTime": "2023-03-09T07:03:21.039785085Z", + "eventType": "WorkflowTaskStarted", + "taskId": "1054659", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "22", + "identity": "33044@Quinn-Klassens-MacBook-Pro.local@", + "requestId": "65ce90aa-96bc-4a91-9e6d-bcc9a7a59534", + "historySizeBytes": "2641" + } + }, + { + "eventId": "24", + "eventTime": "2023-03-09T07:03:21.049047168Z", + "eventType": "WorkflowTaskCompleted", + "taskId": "1054663", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "22", + "startedEventId": "23", + "identity": "33044@Quinn-Klassens-MacBook-Pro.local@", + "binaryChecksum": "8ac5f756996609f24b6d575cbea47992", + "sdkMetadata": { + + }, + "meteringMetadata": { + + } + } + }, + { + "eventId": "25", + "eventTime": "2023-03-09T07:03:21.045423210Z", + "eventType": "ChildWorkflowExecutionCompleted", + "taskId": "1054664", + "childWorkflowExecutionCompletedEventAttributes": { + "namespace": "default", + "namespaceId": "512e1371-2e32-41ef-a00d-1bf138422898", + "workflowExecution": { + "workflowId": "ABC-SIMPLE-CHILD-WORKFLOW-ID", + "runId": "5842a3f4-26de-47f9-9e18-75414cccec1c" + }, + "workflowType": { + "name": "ChildWorkflowWaitOnSignal" + }, + "initiatedEventId": "5", + "startedEventId": "6" + } + }, + { + "eventId": "26", + "eventTime": "2023-03-09T07:03:21.049054627Z", + "eventType": "WorkflowTaskScheduled", + "taskId": "1054665", + "workflowTaskScheduledEventAttributes": { + "taskQueue": { + "name": "Quinn-Klassens-MacBook-Pro.local:e8aea3c5-4c46-492d-b120-4676be696b1a", + "kind": "Sticky" + }, + "startToCloseTimeout": "10s", + "attempt": 1 + } + }, + { + "eventId": "27", + "eventTime": "2023-03-09T07:03:21.049056252Z", + "eventType": "WorkflowTaskStarted", + "taskId": "1054666", + "workflowTaskStartedEventAttributes": { + "scheduledEventId": "26", + "identity": "33044@Quinn-Klassens-MacBook-Pro.local@", + "requestId": "request-from-RespondWorkflowTaskCompleted", + "historySizeBytes": "2750" + } + }, + { + "eventId": "28", + "eventTime": "2023-03-09T07:03:21.053888918Z", + "eventType": "WorkflowTaskCompleted", + "taskId": "1054669", + "workflowTaskCompletedEventAttributes": { + "scheduledEventId": "26", + "startedEventId": "27", + "identity": "33044@Quinn-Klassens-MacBook-Pro.local@", + "binaryChecksum": "8ac5f756996609f24b6d575cbea47992", + "sdkMetadata": { + + }, + "meteringMetadata": { + + } + } + }, + { + "eventId": "29", + "eventTime": "2023-03-09T07:03:21.053895127Z", + "eventType": "WorkflowExecutionCompleted", + "taskId": "1054670", + "workflowExecutionCompletedEventAttributes": { + "workflowTaskCompletedEventId": "28" + } + } + ] +} \ No newline at end of file diff --git a/test/replaytests/replay_test.go b/test/replaytests/replay_test.go index 16ddc3833..311b6cf40 100644 --- a/test/replaytests/replay_test.go +++ b/test/replaytests/replay_test.go @@ -269,6 +269,14 @@ func (s *replayTestSuite) TestMutableSideEffectWorkflow() { require.Equal(s.T(), []int{0, 0, 0, 1, 1, 2, 3, 3, 4, 4, 5}, result) } +func (s *replayTestSuite) TestDuplciateChildWorkflow() { + replayer := worker.NewWorkflowReplayer() + replayer.RegisterWorkflow(DuplicateChildWorkflow) + + err := replayer.ReplayWorkflowHistoryFromJSONFile(ilog.NewDefaultLogger(), "duplicate-child-workflow.json") + require.NoError(s.T(), err) +} + func (s *replayTestSuite) TestVersionLoopWorkflow() { replayer := worker.NewWorkflowReplayer() replayer.RegisterWorkflow(VersionLoopWorkflow) diff --git a/test/replaytests/workflows.go b/test/replaytests/workflows.go index f21f2b4f8..ec07c7f13 100644 --- a/test/replaytests/workflows.go +++ b/test/replaytests/workflows.go @@ -31,6 +31,7 @@ import ( "time" "go.temporal.io/sdk/activity" + "go.temporal.io/sdk/temporal" "go.temporal.io/sdk/workflow" ) @@ -261,3 +262,68 @@ func VersionLoopWorkflow(ctx workflow.Context, changeID string, iterations int) } return workflow.Sleep(ctx, time.Second) } + +func ChildWorkflowWaitOnSignal(ctx workflow.Context) error { + workflow.GetSignalChannel(ctx, "unblock").Receive(ctx, nil) + return nil +} + +func DuplicateChildWorkflow(ctx workflow.Context) error { + logger := workflow.GetLogger(ctx) + + cwo := workflow.ChildWorkflowOptions{ + WorkflowID: "ABC-SIMPLE-CHILD-WORKFLOW-ID", + } + childCtx := workflow.WithChildOptions(ctx, cwo) + + child1 := workflow.ExecuteChildWorkflow(childCtx, ChildWorkflowWaitOnSignal) + var childWE workflow.Execution + err := child1.GetChildWorkflowExecution().Get(ctx, &childWE) + if err != nil { + return err + } + + duplicateChildWFFuture := workflow.ExecuteChildWorkflow(childCtx, ChildWorkflowWaitOnSignal) + selector := workflow.NewSelector(ctx) + selector.AddFuture(duplicateChildWFFuture, func(f workflow.Future) { + logger.Info("child workflow is ready") + err = f.Get(ctx, nil) + if _, ok := err.(*temporal.ChildWorkflowExecutionAlreadyStartedError); !ok { + panic("Second child must fail to start as duplicate") + } + err = workflow.Sleep(ctx, time.Second) + }).AddFuture(duplicateChildWFFuture.GetChildWorkflowExecution(), func(f workflow.Future) { + logger.Info("child workflow execution is ready") + err = f.Get(ctx, nil) + if _, ok := err.(*temporal.ChildWorkflowExecutionAlreadyStartedError); !ok { + panic("Second child must fail to start as duplicate") + } + ao := workflow.ActivityOptions{ + ScheduleToStartTimeout: time.Minute, + StartToCloseTimeout: time.Minute, + HeartbeatTimeout: time.Second * 20, + } + ctx = workflow.WithActivityOptions(ctx, ao) + field := "hello" + err = workflow.ExecuteActivity(ctx, helloworldActivity, field).Get(ctx, &field) + }).AddDefault(func() { + err = workflow.Sleep(ctx, time.Second) + }) + for i := 0; i < 2; i++ { + selector.Select(ctx) + if err != nil { + return err + } + } + + workflow.SignalExternalWorkflow(ctx, childWE.ID, childWE.RunID, "unblock", nil) + if err != nil { + return err + } + err = child1.Get(ctx, nil) + if err != nil { + return err + } + + return nil +} diff --git a/test/workflow_test.go b/test/workflow_test.go index 6670c0b39..f380c7170 100644 --- a/test/workflow_test.go +++ b/test/workflow_test.go @@ -584,6 +584,29 @@ func (w *Workflows) ChildWorkflowDuplicatePanicRepro(ctx workflow.Context) error return nil } +func (w *Workflows) ChildWorkflowDuplicateGetExecutionStuckRepro(ctx workflow.Context) error { + cwo := workflow.ChildWorkflowOptions{ + WorkflowID: "ABC-SIMPLE-CHILD-WORKFLOW-ID", + } + childCtx := workflow.WithChildOptions(ctx, cwo) + + child1 := workflow.ExecuteChildWorkflow(childCtx, w.childWorkflowWaitOnSignal) + var childWE workflow.Execution + err := child1.GetChildWorkflowExecution().Get(ctx, &childWE) + if err != nil { + return err + } + workflow.SignalExternalWorkflow(ctx, childWE.ID, childWE.RunID, "unblock", nil) + if err != nil { + return err + } + err = workflow.ExecuteChildWorkflow(childCtx, w.childWorkflowWaitOnSignal).GetChildWorkflowExecution().Get(ctx, nil) + if _, ok := err.(*temporal.ChildWorkflowExecutionAlreadyStartedError); !ok { + panic("Second child must fail to start as duplicate") + } + return nil +} + func (w *Workflows) ActivityCancelRepro(ctx workflow.Context) ([]string, error) { ctx, cancelFunc := workflow.WithCancel(ctx) @@ -2126,6 +2149,7 @@ func (w *Workflows) register(worker worker.Worker) { worker.RegisterWorkflow(w.ChildWorkflowSuccessWithParentClosePolicyAbandon) worker.RegisterWorkflow(w.ChildWorkflowCancelUnusualTransitionsRepro) worker.RegisterWorkflow(w.ChildWorkflowDuplicatePanicRepro) + worker.RegisterWorkflow(w.ChildWorkflowDuplicateGetExecutionStuckRepro) worker.RegisterWorkflow(w.ConsistentQueryWorkflow) worker.RegisterWorkflow(w.ContextPropagator) worker.RegisterWorkflow(w.ContinueAsNew)