Skip to content

Commit eae6d2f

Browse files
committed
Update docs with error handling
1 parent bc29c42 commit eae6d2f

File tree

1 file changed

+79
-2
lines changed

1 file changed

+79
-2
lines changed

README.md

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ func Workflow2(ctx workflow.Context, msg string) (string, error) {
314314
From a workflow, call `workflow.ExecuteActivity` to execute an activity. The call returns a `Future[T]` you can await to get the result or any error it might return.
315315

316316
```go
317-
r1, err := workflow.ExecuteActivity[int](ctx, Activity1, 35, 12, nil, "test").Get(ctx)
317+
r1, err := workflow.ExecuteActivity[int](ctx, workflow.DefaultActivityOptions, Activity1, 35, 12, nil, "test").Get(ctx)
318318
if err != nil {
319319
panic("error getting activity 1 result")
320320
}
@@ -423,9 +423,86 @@ func SubWorkflow(ctx workflow.Context, msg string) (int, error) {
423423

424424
Similar to timer cancellation, you can pass a cancelable context to `CreateSubWorkflowInstance` and cancel the sub-workflow that way. Reacting to the cancellation is the same as canceling a workflow via the `Client`. See [Canceling workflows](#canceling-workflows) for more details.
425425

426+
### Error handling
427+
428+
#### Custom errors
429+
430+
Errors returned from activities and subworkflows need to be marshalled/unmarshalled by the library so they are wrapped in a `workflow.Error`. You can access the original type via the `err.Type` field. If a stacktrace was captured, you can access it via `err.Stack()`. Example (see also `samples/errors`):
431+
432+
```go
433+
func handleError(ctx workflow.Context, logger log.Logger, err error) {
434+
var werr *workflow.Error
435+
if errors.As(err, &werr) {
436+
switch werr.Type {
437+
case "CustomError": // This was a `type CustomError struct...` returned by an activity/subworkflow
438+
logger.Error("Custom error", "err", werr)
439+
return
440+
}
441+
442+
logger.Error("Generic workflow error", "err", werr, "stack", werr.Stack())
443+
return
444+
}
445+
446+
var perr *workflow.PanicError
447+
if errors.As(err, &perr) {
448+
// Activity/subworkflow ran into a panic
449+
logger.Error("Panic", "err", perr, "stack", perr.Stack())
450+
return
451+
}
452+
453+
logger.Error("Generic error", "err", err)
454+
}
455+
```
456+
457+
#### Panics
458+
459+
A panic in an activity will be captured by the library and made available as a `workflow.PanicError` in the calling workflow. Example:
460+
461+
462+
```go
463+
r1, err := workflow.ExecuteActivity[int](ctx, workflow.DefaultActivityOptions, Activity1, "test").Get(ctx)
464+
if err != nil {
465+
panic("error getting activity 1 result")
466+
}
467+
468+
var perr *workflow.PanicError
469+
if errors.As(err, &perr) {
470+
logger.Error("Panic", "err", perr, "stack", perr.Stack())
471+
return
472+
}
473+
```
474+
475+
#### Retries
476+
477+
With the default `DefaultActivityOptions`, Activities are retried up to three times when they return an error. If you want to keep automatic retries, but want to avoid them when hitting certain error types, you can wrap an error with `workflow.NewPermanentError`:
478+
479+
**Workflow**:
480+
481+
```go
482+
r1, err := workflow.ExecuteActivity[int](ctx, workflow.DefaultActivityOptions, Activity1, "test").Get(ctx)
483+
if err != nil {
484+
panic("error getting activity 1 result")
485+
}
486+
487+
log.Println(r1)
488+
```
489+
490+
**Activity**:
491+
492+
```go
493+
func Activity1(ctx context.Context, name string) (int, error) {
494+
if name == "test" {
495+
// No need to retry in this case, the activity will aways fail with the given inputs
496+
return 0, workflow.NewPermanentError(errors.New("test is not a valid name"))
497+
}
498+
499+
return http.Do("POST", "https://example.com", name)
500+
}
501+
```
502+
426503
### `ContinueAsNew`
427504

428-
```ContinueAsNew` allows you to restart workflow execution with different inputs. The purpose is to keep the history size small enough to avoid hitting size limits, running out of memory and impacting performance. It works by returning a special `error` from your workflow that contains the new inputs:
505+
`ContinueAsNew` allows you to restart workflow execution with different inputs. The purpose is to keep the history size small enough to avoid hitting size limits, running out of memory and impacting performance. It works by returning a special `error` from your workflow that contains the new inputs:
429506

430507
```go
431508
wf := func(ctx workflow.Context, run int) (int, error) {

0 commit comments

Comments
 (0)