diff --git a/Makefile b/Makefile index 81da9515..469f3fb6 100644 --- a/Makefile +++ b/Makefile @@ -35,6 +35,7 @@ PROGS = helloworld \ pageflow \ signalcounter \ sideeffect \ + sleep \ TEST_ARG ?= -race -v -timeout 5m BUILD := ./build @@ -68,6 +69,7 @@ TEST_DIRS=./cmd/samples/cron \ ./cmd/samples/recipes/searchattributes \ ./cmd/samples/recipes/sideeffect \ ./cmd/samples/recipes/signalcounter \ + ./cmd/samples/recipes/sleep \ ./cmd/samples/recovery \ ./cmd/samples/pso \ @@ -81,6 +83,9 @@ helloworld: delaystart: go build -o bin/delaystart cmd/samples/recipes/delaystart/*.go +sleep: + go build -o bin/sleep cmd/samples/recipes/sleep/*.go + branch: go build -o bin/branch cmd/samples/recipes/branch/*.go @@ -207,6 +212,7 @@ bins: helloworld \ pageflow \ signalcounter \ sideeffect \ + sleep \ test: bins @rm -f test diff --git a/cmd/samples/recipes/sleep/README.md b/cmd/samples/recipes/sleep/README.md new file mode 100644 index 00000000..94a5cc3b --- /dev/null +++ b/cmd/samples/recipes/sleep/README.md @@ -0,0 +1,62 @@ +# Sleep Workflow Sample + +This sample workflow demonstrates how to use the `workflow.Sleep` function in Cadence workflows. The sleep functionality allows workflows to pause execution for a specified duration before continuing with subsequent activities. + +## Sample Description + +The sample workflow: +- Takes a sleep duration as input parameter +- Uses `workflow.Sleep` to pause workflow execution for the specified duration +- Executes a main activity after the sleep period completes +- Demonstrates proper error handling for sleep operations +- Shows how to configure activity options for post-sleep activities + +The workflow is useful for scenarios where you need to: +- Implement delays or timeouts in workflow logic +- Wait for external events or conditions +- Implement retry mechanisms with exponential backoff +- Create scheduled or periodic workflows + +## Key Components + +- **Workflow**: `sleepWorkflow` demonstrates the sleep functionality with activity execution +- **Activity**: `mainSleepActivity` is executed after the sleep period +- **Sleep Duration**: Configurable duration (default: 30 seconds) passed as workflow input +- **Test**: Includes unit tests to verify sleep and activity execution + +## Steps to Run Sample + +1. You need a cadence service running. See details in cmd/samples/README.md + +2. Run the following command to start the worker: + ``` + ./bin/sleep -m worker + ``` + +3. Run the following command to execute the workflow: + ``` + ./bin/sleep -m trigger + ``` + +You should see logs showing: +- Workflow start with sleep duration +- Sleep completion message +- Main activity execution +- Workflow completion + +## Customization + +To modify the sleep behavior: +- Change the `sleepDuration` in `main.go` to adjust the default sleep time +- Modify the activity options to configure timeouts for post-sleep activities +- Add additional activities or logic after the sleep period +- Implement conditional sleep based on workflow state + +## Use Cases + +This pattern is useful for: +- **Scheduled Tasks**: Implement workflows that need to wait before processing +- **Rate Limiting**: Add delays between API calls or external service interactions +- **Retry Logic**: Implement exponential backoff for failed operations +- **Event-Driven Workflows**: Wait for specific time periods before checking conditions +- **Batch Processing**: Add delays between batch operations to avoid overwhelming systems \ No newline at end of file diff --git a/cmd/samples/recipes/sleep/main.go b/cmd/samples/recipes/sleep/main.go new file mode 100644 index 00000000..69e6ecad --- /dev/null +++ b/cmd/samples/recipes/sleep/main.go @@ -0,0 +1,62 @@ +package main + +import ( + "flag" + "time" + + "github.com/pborman/uuid" + "go.uber.org/cadence/client" + "go.uber.org/cadence/worker" + + "github.com/uber-common/cadence-samples/cmd/samples/common" +) + +const ( + ApplicationName = "sleepTaskList" + SleepWorkflowName = "sleepWorkflow" +) + +func startWorkers(h *common.SampleHelper) { + workerOptions := worker.Options{ + MetricsScope: h.WorkerMetricScope, + Logger: h.Logger, + FeatureFlags: client.FeatureFlags{ + WorkflowExecutionAlreadyCompletedErrorEnabled: true, + }, + } + h.StartWorkers(h.Config.DomainName, ApplicationName, workerOptions) +} + +func startWorkflow(h *common.SampleHelper) { + sleepDuration := 30 * time.Second + workflowOptions := client.StartWorkflowOptions{ + ID: "sleep_" + uuid.New(), + TaskList: ApplicationName, + ExecutionStartToCloseTimeout: time.Minute, + DecisionTaskStartToCloseTimeout: time.Minute, + } + h.StartWorkflow(workflowOptions, SleepWorkflowName, sleepDuration) +} + +func registerWorkflowAndActivity(h *common.SampleHelper) { + h.RegisterWorkflowWithAlias(sleepWorkflow, SleepWorkflowName) + h.RegisterActivity(mainSleepActivity) +} + +func main() { + var mode string + flag.StringVar(&mode, "m", "trigger", "Mode is worker or trigger.") + flag.Parse() + + var h common.SampleHelper + h.SetupServiceConfig() + + switch mode { + case "worker": + registerWorkflowAndActivity(&h) + startWorkers(&h) + select {} + case "trigger": + startWorkflow(&h) + } +} diff --git a/cmd/samples/recipes/sleep/sleep_workflow.go b/cmd/samples/recipes/sleep/sleep_workflow.go new file mode 100644 index 00000000..f6ba5c1c --- /dev/null +++ b/cmd/samples/recipes/sleep/sleep_workflow.go @@ -0,0 +1,52 @@ +package main + +import ( + "context" + "time" + + "go.uber.org/cadence/activity" + "go.uber.org/cadence/workflow" + "go.uber.org/zap" +) + +const sleepWorkflowName = "sleepWorkflow" + +// sleepWorkflow demonstrates workflow.Sleep followed by a main activity call +func sleepWorkflow(ctx workflow.Context, sleepDuration time.Duration) error { + logger := workflow.GetLogger(ctx) + logger.Info("Workflow started, will sleep", zap.String("duration", sleepDuration.String())) + + // Sleep for the specified duration + err := workflow.Sleep(ctx, sleepDuration) + if err != nil { + logger.Error("Sleep failed", zap.Error(err)) + return err + } + + logger.Info("Sleep finished, executing main activity") + + // Set activity options + activityOptions := workflow.ActivityOptions{ + ScheduleToStartTimeout: time.Minute, + StartToCloseTimeout: time.Minute, + HeartbeatTimeout: time.Second * 20, + } + ctx = workflow.WithActivityOptions(ctx, activityOptions) + + var result string + err = workflow.ExecuteActivity(ctx, mainSleepActivity).Get(ctx, &result) + if err != nil { + logger.Error("Main activity failed", zap.Error(err)) + return err + } + + logger.Info("Workflow completed", zap.String("Result", result)) + return nil +} + +// mainSleepActivity is a simple activity for demonstration +func mainSleepActivity(ctx context.Context) (string, error) { + logger := activity.GetLogger(ctx) + logger.Info("mainSleepActivity executed") + return "Main sleep activity completed", nil +} diff --git a/cmd/samples/recipes/sleep/sleep_workflow_test.go b/cmd/samples/recipes/sleep/sleep_workflow_test.go new file mode 100644 index 00000000..8e3a9f8f --- /dev/null +++ b/cmd/samples/recipes/sleep/sleep_workflow_test.go @@ -0,0 +1,31 @@ +package main + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + "go.uber.org/cadence/activity" + "go.uber.org/cadence/encoded" + "go.uber.org/cadence/testsuite" +) + +func Test_Sleep(t *testing.T) { + testSuite := &testsuite.WorkflowTestSuite{} + + env := testSuite.NewTestWorkflowEnvironment() + env.RegisterWorkflow(sleepWorkflow) + env.RegisterActivity(mainSleepActivity) + + var activityMessage string + env.SetOnActivityCompletedListener(func(activityInfo *activity.Info, result encoded.Value, err error) { + result.Get(&activityMessage) + }) + + sleepDuration := 5 * time.Second + env.ExecuteWorkflow(sleepWorkflow, sleepDuration) + + require.True(t, env.IsWorkflowCompleted()) + require.NoError(t, env.GetWorkflowError()) + require.Equal(t, "Main sleep activity completed", activityMessage) +}