Skip to content

Commit

Permalink
Add fx.ShutdownError option
Browse files Browse the repository at this point in the history
  • Loading branch information
abramlab committed Feb 11, 2023
1 parent ed3eef5 commit a285dde
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 3 deletions.
43 changes: 40 additions & 3 deletions shutdown.go
Expand Up @@ -23,6 +23,8 @@ package fx
import (
"context"
"time"

"go.uber.org/multierr"
)

// Shutdowner provides a method that can manually trigger the shutdown of the
Expand All @@ -34,19 +36,19 @@ type Shutdowner interface {
}

// ShutdownOption provides a way to configure properties of the shutdown
// process. Currently, no options have been implemented.
// process.
type ShutdownOption interface {
apply(*shutdowner)
}

type exitCodeOption int

var _ ShutdownOption = exitCodeOption(0)

func (code exitCodeOption) apply(s *shutdowner) {
s.exitCode = int(code)
}

var _ ShutdownOption = exitCodeOption(0)

// ExitCode is a [ShutdownOption] that may be passed to the Shutdown method of the
// [Shutdowner] interface.
// The given integer exit code will be broadcasted to any receiver waiting
Expand All @@ -71,6 +73,41 @@ func ShutdownTimeout(timeout time.Duration) ShutdownOption {
return shutdownTimeoutOption(timeout)
}

type shutdownErrorOption []error

func (errs shutdownErrorOption) apply(s *shutdowner) {
s.app.err = multierr.Append(s.app.err, multierr.Combine(errs...))
}

var _ ShutdownOption = shutdownErrorOption([]error{})

// ShutdownError registers any number of errors with the application shutdown.
// If more than one error is given, the errors are combined into a
// single error. Similar to invocations, errors are applied in order.
//
// You can use these errors, for example, to decide what to do after the app shutdown.
//
// customErr := errors.New("something went wrong")
// app := fx.New(
// ...
// fx.Provide(func(s fx.Shutdowner, a A) B {
// s.Shutdown(fx.ShutdownError(customErr))
// }),
// ...
// )
// err := app.Start(context.Background())
// if err != nil {
// panic(err)
// }
// defer app.Stop(context.Background())
//
// if err := app.Err(); errors.Is(err, customErr) {
// // custom logic here
// }
func ShutdownError(errs ...error) ShutdownOption {
return shutdownErrorOption(errs)
}

type shutdowner struct {
app *App
exitCode int
Expand Down
23 changes: 23 additions & 0 deletions shutdown_test.go
Expand Up @@ -22,6 +22,7 @@ package fx_test

import (
"context"
"errors"
"fmt"
"sync"
"testing"
Expand Down Expand Up @@ -128,6 +129,28 @@ func TestShutdown(t *testing.T) {

assert.NoError(t, s.Shutdown(fx.ExitCode(2), fx.ShutdownTimeout(time.Second)))
})

t.Run("with shutdown error", func(t *testing.T) {
t.Parallel()

var s fx.Shutdowner
app := fxtest.New(
t,
fx.Populate(&s),
)

done := app.Done()
wait := app.Wait()
defer app.RequireStart().RequireStop()

var expectedError = errors.New("shutdown error")

assert.NoError(t, s.Shutdown(fx.ShutdownError(expectedError)), "error in app shutdown")
assert.NotNil(t, <-done, "done channel did not receive signal")
assert.NotNil(t, <-wait, "wait channel did not receive signal")
assert.ErrorIs(t, app.Err(), expectedError,
"unexpected error, expected: %q, got: %q", expectedError, app.Err())
})
}

func TestDataRace(t *testing.T) {
Expand Down

0 comments on commit a285dde

Please sign in to comment.