From 0c992b74aa263b6f9dfda5b06a24be387ba7fda4 Mon Sep 17 00:00:00 2001 From: Jackie Li Date: Tue, 10 May 2022 09:37:41 +0100 Subject: [PATCH 1/2] add errgroup sample --- errgroup/README.md | 6 +++ errgroup/errgroup.go | 91 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 errgroup/README.md create mode 100644 errgroup/errgroup.go diff --git a/errgroup/README.md b/errgroup/README.md new file mode 100644 index 00000000..048fae06 --- /dev/null +++ b/errgroup/README.md @@ -0,0 +1,6 @@ +This sample demonstrates how to use `errgroup` pattern to synchronise +cancellation of workflow coroutines. + + +This sample only uses workflow code and uses `testsuite` in the `main` function +instead of using `worker` and `starter` to simplify the code. diff --git a/errgroup/errgroup.go b/errgroup/errgroup.go new file mode 100644 index 00000000..51af81a0 --- /dev/null +++ b/errgroup/errgroup.go @@ -0,0 +1,91 @@ +package main + +import ( + "errors" + "log" + "time" + + "go.temporal.io/sdk/testsuite" + "go.temporal.io/sdk/workflow" +) + +func Workflow(ctx workflow.Context) (string, error) { + g, cc := WithContext(ctx) + + for i := 0; i < 3; i++ { + g.Go(cc, func(ctx workflow.Context) error { + workflow.Sleep(ctx, 3*time.Second) + if ctx.Err() != nil { + println("ctx error", ctx.Err().Error()) + } else { + panic("shouldn't be here") + } + return nil + }) + } + g.Go(cc, func(ctx workflow.Context) error { + return errors.New("foo error") + }) + + err := g.Wait(cc) + if err == nil { + return "", errors.New("expected error") + } + return "expected error received: " + err.Error(), nil +} + +func main() { + s := &testsuite.WorkflowTestSuite{} + env := s.NewTestWorkflowEnvironment() + + env.ExecuteWorkflow(Workflow) + err := env.GetWorkflowError() + if err != nil { + log.Fatalf("[ERROR] not expecting workflow error: %v", err) + } + var result interface{} + err = env.GetWorkflowResult(&result) + if err != nil { + log.Fatalf("[ERROR] failed to get workflow result: %v", err) + } + log.Printf("result: %v", result) +} + +// adapted from https://cs.opensource.google/go/x/sync/+/036812b2:errgroup/errgroup.go +type ErrGroup struct { + wg workflow.WaitGroup + cancel func() + err error +} + +func WithContext(ctx workflow.Context) (*ErrGroup, workflow.Context) { + cc, cancel := workflow.WithCancel(ctx) + eg := &ErrGroup{ + cancel: cancel, + wg: workflow.NewWaitGroup(ctx), + } + return eg, cc +} + +func (g *ErrGroup) Wait(ctx workflow.Context) error { + g.wg.Wait(ctx) + if g.cancel != nil { + g.cancel() + } + return g.err +} + +func (g *ErrGroup) Go(ctx workflow.Context, f func(workflow.Context) error) { + g.wg.Add(1) + workflow.Go(ctx, func(ctx workflow.Context) { + defer g.wg.Done() + if err := f(ctx); err != nil { + if g.err == nil { + g.err = err + } + if g.cancel != nil { + g.cancel() + } + } + }) +} From 6b28fd4b28093a72b502f17a5ddcd7ff1d33ce72 Mon Sep 17 00:00:00 2001 From: Jackie Li Date: Tue, 10 May 2022 09:49:08 +0100 Subject: [PATCH 2/2] improve readme --- errgroup/README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/errgroup/README.md b/errgroup/README.md index 048fae06..47d26ce4 100644 --- a/errgroup/README.md +++ b/errgroup/README.md @@ -4,3 +4,21 @@ cancellation of workflow coroutines. This sample only uses workflow code and uses `testsuite` in the `main` function instead of using `worker` and `starter` to simplify the code. + +To run this sample, simply do: + +``` +go run errgroup.go +``` + +And output should be: + +``` +2022/05/10 09:44:41 DEBUG RequestCancelTimer TimerID 1 +2022/05/10 09:44:41 DEBUG RequestCancelTimer TimerID 2 +2022/05/10 09:44:41 DEBUG RequestCancelTimer TimerID 3 +ctx error canceled +ctx error canceled +ctx error canceled +2022/05/10 09:44:41 result: expected error received: foo error +```