Skip to content

Commit

Permalink
Add routine.Release
Browse files Browse the repository at this point in the history
  • Loading branch information
sergerad committed Mar 21, 2024
1 parent 6ecce1e commit ce2cc64
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 20 deletions.
11 changes: 11 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package relax

import (
"fmt"
)

var (
// PanicError is returned when a panic is recovered
// during the execution of a goroutine
PanicError = fmt.Errorf("recovered from panic")
)
11 changes: 4 additions & 7 deletions examples/routine/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,11 @@ func main() {
fmt.Println(err)
}

// If we don't want to wait for the routine, we can use the
// following pattern:
// If we don't want to wait for the routine, release it
routine = relax.Go(func() error {
panic(2)
})
go func() {
if err := routine.Wait(); err != nil {
fmt.Println(err)
}
}()
routine.Release(func(err error) {
fmt.Println(err)
})
}
11 changes: 2 additions & 9 deletions group.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,10 @@ package relax

import (
"context"
"fmt"

"golang.org/x/sync/errgroup"
)

var (
// PanicError is returned when a panic is recovered
// during the execution of a goroutine
PanicError = fmt.Errorf("recovered from panic")
)

// RoutineGroup is a wrapper around golang.org/x/sync/errgroup.Group that
// recovers from panics and returns them as errors
type RoutineGroup struct {
Expand All @@ -31,8 +24,8 @@ func NewGroup(ctx context.Context) (*RoutineGroup, context.Context) {

// Go runs a provided func in a goroutine while ensuring that
// any panic is recovered and returned as an error
func (g *RoutineGroup) Go(f func() error) {
g.Group.Go(func() (err error) {
func (rg *RoutineGroup) Go(f func() error) {
rg.Group.Go(func() (err error) {
// Handle panics
defer func() {
if r := recover(); r != nil {
Expand Down
17 changes: 14 additions & 3 deletions routine.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,28 @@ import (
"errors"
)

// Routine is a handle to a goroutine's error response
// Routine is a handle to a goroutine's error response.
// Panics that occur in this routine are converted into errors.
type Routine struct {
errChan chan error
}

// Wait blocks until the goroutine corresponding to the Routine instance returns an error
// Wait blocks until the goroutine corresponding to the Routine instance returns an error.
// Once an error is returned, all subsequent calls to Wait will return nil.
func (r *Routine) Wait() error {
return <-r.errChan
}

// Go launches a goroutine that will return an error if the provided func panics
// Release will call the handler against an error returned by this routine.
// Once a routine is released, waiting on it will return nil.
func (r *Routine) Release(handler func(error)) {
go func() {
handler(r.Wait())
}()
}

// Go launches a goroutine that will return an error if the provided func panics or
// an error is returned by the provided func.
func Go(f func() error) *Routine {
routine := &Routine{
errChan: make(chan error, 1),
Expand Down
59 changes: 58 additions & 1 deletion routine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,70 @@ import (
"github.com/stretchr/testify/require"
)

var (
errTestSentinel = errors.New("test sentinel")
)

func TestRoutine_NilError(t *testing.T) {
r := Go(func() error {
return nil
})
assert.NoError(t, r.Wait())
}

func TestRoutine_Multicall(t *testing.T) {
var tests = []struct {
name string
f func() error
expectedErr error
}{
{"nil", func() error { return nil }, nil},
{"err", func() error { return errTestSentinel }, errTestSentinel},
{"panic", func() error { panic("test panic") }, PanicError},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
r := Go(func() error {
return test.f()
})
for i := 0; i < 5; i++ {
err := r.Wait()
if i == 0 {
if !errors.Is(err, test.expectedErr) {
t.Errorf("expected %v, got %v. iteration %d", test.expectedErr, err, i)
}
} else {
if err != nil {
t.Errorf("expected nil, got %v. iteration %d", err, i)
}
}
}
})
}
}

func TestRoutine_Release(t *testing.T) {
var tests = []struct {
name string
err error
}{
{"nil", nil},
{"error", errTestSentinel},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
Go(func() error {
return test.err
}).Release(func(err error) {
if !errors.Is(err, test.err) {
t.Errorf("expected %v, got %v", test.err, err)
}
})

})
}
}

func TestRoutine_Panic_NotNilError(t *testing.T) {
var tests = []struct {
name string
Expand All @@ -23,7 +80,7 @@ func TestRoutine_Panic_NotNilError(t *testing.T) {
}{
{"empty", "", ""},
{"non-empty", "test panic", "test panic"},
{"error", errors.New("fail"), "fail"},
{"error", errTestSentinel, errTestSentinel.Error()},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
Expand Down

0 comments on commit ce2cc64

Please sign in to comment.