Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions errgroup/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
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.

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
```
91 changes: 91 additions & 0 deletions errgroup/errgroup.go
Original file line number Diff line number Diff line change
@@ -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()
}
}
})
}