Skip to content

Commit 1c55c52

Browse files
committed
Implements ShutdownCode option and ShutdownSignal os.Signal wrapper
1 parent 1124297 commit 1c55c52

3 files changed

Lines changed: 147 additions & 2 deletions

File tree

shutdown.go

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,48 @@ type ShutdownOption interface {
3939
apply(*shutdowner)
4040
}
4141

42+
type shutdownCode int
43+
44+
func (c shutdownCode) apply(s *shutdowner) {
45+
s.exitCode = int(c)
46+
}
47+
48+
// ShutdownCode implements a shutdown option that allows a user specify the
49+
// os.Exit code that an application should exit with.
50+
func ShutdownCode(code int) ShutdownOption {
51+
return shutdownCode(code)
52+
}
53+
4254
type shutdowner struct {
43-
app *App
55+
exitCode int
56+
app *App
57+
}
58+
59+
// ShutdownerSignal defines an os.Signal interface with an extension that allows
60+
// the querying of a exit code defined for the signal. ExitCode defaults to 0.
61+
type ShutdownerSignal interface {
62+
os.Signal
63+
ExitCode() int
64+
}
65+
66+
type shutdownSignal struct {
67+
signal os.Signal
68+
exitCode int
69+
}
70+
71+
func (s shutdownSignal) String() string { return s.signal.String() }
72+
func (s shutdownSignal) Signal() { s.signal.Signal() }
73+
func (s shutdownSignal) ExitCode() int { return s.exitCode }
74+
75+
// ShutdownSignal will return a ShutdownerSignal for a given os.Signal. If
76+
// the signal type was not originally a ShutdownerSignal, a new ShutdownerSignal
77+
// will be created from the os.Signal, and exit code will be set to default.
78+
func ShutdownSignal(signal os.Signal) ShutdownerSignal {
79+
if s, ok := signal.(ShutdownerSignal); ok {
80+
return s
81+
}
82+
83+
return &shutdownSignal{signal: signal}
4484
}
4585

4686
// Shutdown broadcasts a signal to all of the application's Done channels
@@ -49,7 +89,14 @@ type shutdowner struct {
4989
// In practice this means Shutdowner.Shutdown should not be called from an
5090
// fx.Invoke, but from a fx.Lifecycle.OnStart hook.
5191
func (s *shutdowner) Shutdown(opts ...ShutdownOption) error {
52-
return s.app.broadcastSignal(_sigTERM)
92+
for _, opt := range opts {
93+
opt.apply(s)
94+
}
95+
96+
return s.app.broadcastSignal(&shutdownSignal{
97+
exitCode: s.exitCode,
98+
signal: _sigTERM,
99+
})
53100
}
54101

55102
func (app *App) shutdowner() Shutdowner {

shutdown_code_example_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright (c) 2019 Uber Technologies, Inc.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy
4+
// of this software and associated documentation files (the "Software"), to deal
5+
// in the Software without restriction, including without limitation the rights
6+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
// copies of the Software, and to permit persons to whom the Software is
8+
// furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in
11+
// all copies or substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
// THE SOFTWARE.
20+
21+
package fx_test
22+
23+
import (
24+
"fmt"
25+
26+
"go.uber.org/fx"
27+
)
28+
29+
func ExampleShutdownCode() {
30+
app := fx.New(
31+
fx.Invoke(func(shutdowner fx.Shutdowner) {
32+
// Call the shutdowner Shutdown method with a shutdown code
33+
// option
34+
shutdowner.Shutdown(fx.ShutdownCode(1))
35+
}),
36+
)
37+
38+
app.Run()
39+
40+
// Extract the shutdown signal from the os.Signal returned by app.Done
41+
signal := fx.ShutdownSignal(<-app.Done())
42+
43+
// Retrieve the exit code
44+
fmt.Printf("os.Exit(%v)\n", signal.ExitCode())
45+
46+
// Output:
47+
// os.Exit(1)
48+
}

shutdown_test.go

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

2323
import (
2424
"context"
25+
"fmt"
2526
"sync"
2627
"testing"
2728

@@ -87,6 +88,55 @@ func TestShutdown(t *testing.T) {
8788
assert.NotNil(t, <-done1, "done channel 1 did not receive signal")
8889
assert.NotNil(t, <-done2, "done channel 2 did not receive signal")
8990
})
91+
92+
t.Run("shutdown app with exit code(s)", func(t *testing.T) {
93+
t.Parallel()
94+
95+
t.Run("default", func(t *testing.T) {
96+
t.Parallel()
97+
var s fx.Shutdowner
98+
app := fxtest.New(
99+
t,
100+
fx.Populate(&s),
101+
)
102+
103+
done := app.Done()
104+
defer app.RequireStart().RequireStop()
105+
106+
assert.NoError(t, s.Shutdown(), "error in app shutdown")
107+
signal := <-done
108+
109+
assert.NotNil(t, signal, "done channel did not receive signal")
110+
shutdownSignal := fx.ShutdownSignal(signal)
111+
assert.NotNil(t, shutdownSignal, "done channel did not send shutdown signal")
112+
assert.NotPanics(t, shutdownSignal.Signal)
113+
assert.Zero(t, shutdownSignal.ExitCode())
114+
assert.NotNil(t, fx.ShutdownSignal(nil))
115+
})
116+
117+
for expected := 0; expected < 3; expected++ {
118+
expected := expected
119+
t.Run(fmt.Sprintf("with exit code %v", expected), func(t *testing.T) {
120+
t.Parallel()
121+
var s fx.Shutdowner
122+
app := fxtest.New(
123+
t,
124+
fx.Populate(&s),
125+
)
126+
127+
done := app.Done()
128+
defer app.RequireStart().RequireStop()
129+
130+
assert.NoError(t, s.Shutdown(fx.ShutdownCode(expected)), "error in app shutdown")
131+
signal := <-done
132+
133+
assert.NotNil(t, signal, "done channel did not receive signal")
134+
shutdownSignal := fx.ShutdownSignal(signal)
135+
assert.NotNil(t, shutdownSignal, "done channel did not send shutdown signal")
136+
assert.Equal(t, expected, shutdownSignal.ExitCode())
137+
})
138+
}
139+
})
90140
}
91141

92142
func TestDataRace(t *testing.T) {

0 commit comments

Comments
 (0)