When running Temporal’s golang SDK (v1.35.0) to mock a parent and child workflow, only the first signal sent by the parent is successfully delivered to the child workflow; all subsequent signals are not received.
A minimal example is shown below. (Note that it’s not possible to use env.OnSignalExternalWorkflow here, because “TestWorkflowEnvironment handles sending signals between the workflows that are started from the root workflow. For example, sending signals between parent and child workflows. Or sending signals between 2 child workflows”, per the docs.)
func TestParentChildSignalExchange(t *testing.T) {
var ts testsuite.WorkflowTestSuite
env := ts.NewTestWorkflowEnvironment()
var childReceivedC bool
mockChildWorkflow := func(ctx workflow.Context, parentWfId string) error {
s := workflow.NewSelector(ctx)
s.AddReceive(workflow.GetSignalChannel(ctx, SignalA), func(c workflow.ReceiveChannel, _ bool) {
var val string
c.Receive(ctx, &val)
fmt.Println("Child received signal A")
_ = workflow.SignalExternalWorkflow(ctx, parentWfId, "", SignalB, "payload-B").Get(ctx, nil)
fmt.Println("Child sent signal B")
})
s.AddReceive(workflow.GetSignalChannel(ctx, SignalC), func(c workflow.ReceiveChannel, _ bool) {
var val string
c.Receive(ctx, &val)
fmt.Println("Child received signal C")
childReceivedC = true
})
// Selects for signal A and signal C
s.Select(ctx)
s.Select(ctx)
return nil
}
parentWorkflow := func(ctx workflow.Context) error {
cwo := workflow.ChildWorkflowOptions{
WorkflowID: "child-workflow-id",
}
var childWE workflow.Execution
workflowId := workflow.GetInfo(ctx).WorkflowExecution.ID
ctx = workflow.WithChildOptions(ctx, cwo)
err := workflow.ExecuteChildWorkflow(ctx, "MockChildWorkflow", workflowId).GetChildWorkflowExecution().Get(ctx, &childWE)
if err != nil {
return err
}
workflow.SignalExternalWorkflow(ctx, childWE.ID, childWE.RunID, SignalA, "payload-A")
fmt.Println("Parent sent signal A")
var bVal string
s := workflow.NewSelector(ctx)
s.AddReceive(workflow.GetSignalChannel(ctx, SignalB), func(c workflow.ReceiveChannel, _ bool) {
c.Receive(ctx, &bVal)
fmt.Println("Parent received signal B")
_ = workflow.SignalExternalWorkflow(ctx, childWE.ID, childWE.RunID, SignalC, "payload-C").Get(ctx, nil)
fmt.Println("Parent sent signal C")
})
s.Select(ctx)
return nil
}
env.RegisterWorkflow(parentWorkflow)
env.RegisterWorkflowWithOptions(mockChildWorkflow, workflow.RegisterOptions{Name: "MockChildWorkflow"})
env.ExecuteWorkflow(parentWorkflow)
require.True(t, env.IsWorkflowCompleted())
require.True(t, childReceivedC, "child workflow should have received signal C")
}
This gives the following output, showing that the child workflow successfully receives the first signal but not the second.
=== RUN TestParentChildSignalExchange
2025/08/27 17:34:10 INFO ExecuteChildWorkflow WorkflowType MockChildWorkflow
Parent sent signal A
Child received signal A
Child sent signal B
Parent received signal B
Parent sent signal C
/home/patrick/go-scheduler/workflow/temporal_executor_test.go:158:
Error Trace: /home/patrick/go-scheduler/workflow/temporal_executor_test.go:158
Error: Should be true
Test: TestParentChildSignalExchange
Messages: child workflow should have received signal C
--- FAIL: TestParentChildSignalExchange (0.00s)
FAIL
What is the cause of this behavior? Many thanks.