Skip to content

Commit a285dde

Browse files
committed
Add fx.ShutdownError option
1 parent ed3eef5 commit a285dde

File tree

2 files changed

+63
-3
lines changed

2 files changed

+63
-3
lines changed

shutdown.go

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ package fx
2323
import (
2424
"context"
2525
"time"
26+
27+
"go.uber.org/multierr"
2628
)
2729

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

3638
// ShutdownOption provides a way to configure properties of the shutdown
37-
// process. Currently, no options have been implemented.
39+
// process.
3840
type ShutdownOption interface {
3941
apply(*shutdowner)
4042
}
4143

4244
type exitCodeOption int
4345

46+
var _ ShutdownOption = exitCodeOption(0)
47+
4448
func (code exitCodeOption) apply(s *shutdowner) {
4549
s.exitCode = int(code)
4650
}
4751

48-
var _ ShutdownOption = exitCodeOption(0)
49-
5052
// ExitCode is a [ShutdownOption] that may be passed to the Shutdown method of the
5153
// [Shutdowner] interface.
5254
// The given integer exit code will be broadcasted to any receiver waiting
@@ -71,6 +73,41 @@ func ShutdownTimeout(timeout time.Duration) ShutdownOption {
7173
return shutdownTimeoutOption(timeout)
7274
}
7375

76+
type shutdownErrorOption []error
77+
78+
func (errs shutdownErrorOption) apply(s *shutdowner) {
79+
s.app.err = multierr.Append(s.app.err, multierr.Combine(errs...))
80+
}
81+
82+
var _ ShutdownOption = shutdownErrorOption([]error{})
83+
84+
// ShutdownError registers any number of errors with the application shutdown.
85+
// If more than one error is given, the errors are combined into a
86+
// single error. Similar to invocations, errors are applied in order.
87+
//
88+
// You can use these errors, for example, to decide what to do after the app shutdown.
89+
//
90+
// customErr := errors.New("something went wrong")
91+
// app := fx.New(
92+
// ...
93+
// fx.Provide(func(s fx.Shutdowner, a A) B {
94+
// s.Shutdown(fx.ShutdownError(customErr))
95+
// }),
96+
// ...
97+
// )
98+
// err := app.Start(context.Background())
99+
// if err != nil {
100+
// panic(err)
101+
// }
102+
// defer app.Stop(context.Background())
103+
//
104+
// if err := app.Err(); errors.Is(err, customErr) {
105+
// // custom logic here
106+
// }
107+
func ShutdownError(errs ...error) ShutdownOption {
108+
return shutdownErrorOption(errs)
109+
}
110+
74111
type shutdowner struct {
75112
app *App
76113
exitCode int

shutdown_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ package fx_test
2222

2323
import (
2424
"context"
25+
"errors"
2526
"fmt"
2627
"sync"
2728
"testing"
@@ -128,6 +129,28 @@ func TestShutdown(t *testing.T) {
128129

129130
assert.NoError(t, s.Shutdown(fx.ExitCode(2), fx.ShutdownTimeout(time.Second)))
130131
})
132+
133+
t.Run("with shutdown error", func(t *testing.T) {
134+
t.Parallel()
135+
136+
var s fx.Shutdowner
137+
app := fxtest.New(
138+
t,
139+
fx.Populate(&s),
140+
)
141+
142+
done := app.Done()
143+
wait := app.Wait()
144+
defer app.RequireStart().RequireStop()
145+
146+
var expectedError = errors.New("shutdown error")
147+
148+
assert.NoError(t, s.Shutdown(fx.ShutdownError(expectedError)), "error in app shutdown")
149+
assert.NotNil(t, <-done, "done channel did not receive signal")
150+
assert.NotNil(t, <-wait, "wait channel did not receive signal")
151+
assert.ErrorIs(t, app.Err(), expectedError,
152+
"unexpected error, expected: %q, got: %q", expectedError, app.Err())
153+
})
131154
}
132155

133156
func TestDataRace(t *testing.T) {

0 commit comments

Comments
 (0)