Skip to content

Commit 02a2658

Browse files
committed
Add fx.ShutdownError option
1 parent 5eb0e76 commit 02a2658

File tree

2 files changed

+65
-7
lines changed

2 files changed

+65
-7
lines changed

shutdown.go

Lines changed: 42 additions & 7 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,17 +36,11 @@ 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

42-
type exitCodeOption int
43-
44-
func (code exitCodeOption) apply(s *shutdowner) {
45-
s.exitCode = int(code)
46-
}
47-
4844
var _ ShutdownOption = exitCodeOption(0)
4945

5046
// ExitCode is a [ShutdownOption] that may be passed to the Shutdown method of the
@@ -71,6 +67,45 @@ func ShutdownTimeout(timeout time.Duration) ShutdownOption {
7167
return shutdownTimeoutOption(timeout)
7268
}
7369

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