diff --git a/shutdown.go b/shutdown.go index eb67caa87..b5fda5cbd 100644 --- a/shutdown.go +++ b/shutdown.go @@ -78,8 +78,6 @@ type shutdowner struct { // Shutdown broadcasts a signal to all of the application's Done channels // and begins the Stop process. Applications can be shut down only after they // have finished starting up. -// In practice this means Shutdowner.Shutdown should not be called from an -// fx.Invoke, but from a fx.Lifecycle.OnStart hook. func (s *shutdowner) Shutdown(opts ...ShutdownOption) error { for _, opt := range opts { opt.apply(s) diff --git a/shutdown_test.go b/shutdown_test.go index 78d23e0ea..49956196a 100644 --- a/shutdown_test.go +++ b/shutdown_test.go @@ -151,6 +151,23 @@ func TestShutdown(t *testing.T) { // success } }) + + t.Run("many times", func(t *testing.T) { + t.Parallel() + + var shutdowner fx.Shutdowner + app := fxtest.New( + t, + fx.Populate(&shutdowner), + ) + + for i := 0; i < 10; i++ { + app.RequireStart() + shutdowner.Shutdown(fx.ExitCode(i)) + assert.Equal(t, i, (<-app.Wait()).ExitCode, "run %d", i) + app.RequireStop() + } + }) } func TestDataRace(t *testing.T) { diff --git a/signal.go b/signal.go index b804abe7b..1dbfd840e 100644 --- a/signal.go +++ b/signal.go @@ -109,7 +109,6 @@ func (recv *signalReceivers) Start(ctx context.Context) { return } - recv.last = nil recv.finished = make(chan struct{}, 1) recv.shutdown = make(chan struct{}, 1) recv.notify(recv.signals, os.Interrupt, _sigINT, _sigTERM) @@ -135,6 +134,7 @@ func (recv *signalReceivers) Stop(ctx context.Context) error { close(recv.finished) recv.shutdown = nil recv.finished = nil + recv.last = nil return nil } }