I threw this together real quick. It works as expected where it does a signal with start 3 times, the first 2 failing:
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/google/uuid"
"go.temporal.io/sdk/client"
"go.temporal.io/sdk/temporal"
"go.temporal.io/sdk/worker"
"go.temporal.io/sdk/workflow"
)
func main() {
if err := run(); err != nil {
log.Fatal(err)
}
}
func run() error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Create client
c, err := client.NewClient(client.Options{})
if err != nil {
return fmt.Errorf("failed starting client: %w", err)
}
defer c.Close()
// Start worker
taskQueue := "test-task-queue-" + uuid.NewString()
w := worker.New(c, taskQueue, worker.Options{})
w.RegisterWorkflow(Workflow)
w.RegisterActivity(MaybeFail)
if err := w.Start(); err != nil {
return fmt.Errorf("failed starting workflow: %w", err)
}
defer w.Stop()
// Signal with start telling it to fail the first activity run
workflowID := "my-workflow-" + uuid.NewString()
startOpts := client.StartWorkflowOptions{TaskQueue: taskQueue}
_, err = c.SignalWithStartWorkflow(ctx, workflowID, "should-fail", true, startOpts, Workflow)
if err != nil {
return fmt.Errorf("failed starting workflow: %w", err)
}
// Signal with start again, telling it to fail again
_, err = c.SignalWithStartWorkflow(ctx, workflowID, "should-fail", true, startOpts, Workflow)
if err != nil {
return fmt.Errorf("failed resuming workflow: %w", err)
}
// Signal with start again, this time asking to succeed
run, err := c.SignalWithStartWorkflow(ctx, workflowID, "should-fail", false, startOpts, Workflow)
if err != nil {
return fmt.Errorf("failed resuming workflow: %w", err)
}
// Confirm success
if err := run.Get(ctx, nil); err != nil {
return fmt.Errorf("workflow failed: %w", err)
}
return nil
}
func Workflow(ctx workflow.Context) error {
// No retries
ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{
ScheduleToCloseTimeout: 1 * time.Minute,
RetryPolicy: &temporal.RetryPolicy{MaximumAttempts: 1},
})
// Continually run and wait for signal to tell us whether to error
shouldFailSignal := workflow.GetSignalChannel(ctx, "should-fail")
for attempt := 1; ; attempt++ {
// Wait for signal
workflow.GetLogger(ctx).Info("Waiting for signal before executing activity")
var shouldFail bool
if !shouldFailSignal.Receive(ctx, &shouldFail) {
return fmt.Errorf("channel closed")
}
// Execute activity and return on success
if err := workflow.ExecuteActivity(ctx, MaybeFail, shouldFail).Get(ctx, nil); err != nil {
workflow.GetLogger(ctx).Info("Activity failed, waiting before retry", "Attempt", attempt, "Error", err)
} else {
workflow.GetLogger(ctx).Info("Activity succeeded, exiting workflow", "Attempt", attempt)
return nil
}
}
}
func MaybeFail(ctx context.Context, shouldFail bool) error {
if shouldFail {
return fmt.Errorf("intentional error")
}
return nil
}