Add Shutdowner to allow shutdown from a module#660
Conversation
98dd0d9 to
54b5cdf
Compare
Codecov Report
@@ Coverage Diff @@
## master #660 +/- ##
==========================================
+ Coverage 95.8% 96.01% +0.21%
==========================================
Files 9 10 +1
Lines 405 427 +22
==========================================
+ Hits 388 410 +22
Misses 12 12
Partials 5 5
Continue to review full report at Codecov.
|
7eee94e to
276b70a
Compare
9f1b41b to
7e1e34e
Compare
7e1e34e to
18b208c
Compare
amckinney
left a comment
There was a problem hiding this comment.
Nice! Looks good overall. Some minor suggestions and points for discussion.
shutdown.go
Outdated
| } | ||
|
|
||
| if unsent != 0 { | ||
| err := fmt.Errorf("failed to communicate with %v out of %v channels", unsent, len(app.dones)) |
There was a problem hiding this comment.
I'm curious about this case. Should we consider this case an error with respect to a call to Shutdown? This means that all of the application's Done channels have received the os.Signal and we are about to start shutting down, so I'm not sure how we would expect our users to interpret the non-nil result from Shutdown. But, it is a special case in that we failed to send the given os.Signal to at least one of the Done channels.
This isn't necessarily a bad idea though. It makes sense in a "I failed to send the Shutdown signal since it's already sent". I guess the better question is: how do we intend users to use this information, if at all?
There was a problem hiding this comment.
To me, this is a lot like the error you get back from closing a file - usually, there's nothing you can do about it, so you log it but don't need to take any special programmatic action. We should still be returning it, though, because this probably indicates that something is deadlocked or otherwise unhealthy, and that information should end up in our exception tracking system.
More generally, we need an error return value here to make options sane later. Many options will take user input, and we'll need a way to indicate to the user that their input was invalid.
shutdown.go
Outdated
| } | ||
|
|
||
| if unsent != 0 { | ||
| err := fmt.Errorf("failed to communicate with %v out of %v channels", unsent, len(app.dones)) |
There was a problem hiding this comment.
To me, this is a lot like the error you get back from closing a file - usually, there's nothing you can do about it, so you log it but don't need to take any special programmatic action. We should still be returning it, though, because this probably indicates that something is deadlocked or otherwise unhealthy, and that information should end up in our exception tracking system.
More generally, we need an error return value here to make options sane later. Many options will take user input, and we'll need a way to indicate to the user that their input was invalid.
app_test.go
Outdated
| found = true | ||
| })) | ||
| defer app.RequireStart().RequireStop() | ||
| assert.True(t, found) |
There was a problem hiding this comment.
Do we need the extra assertion on found? The rest of the unit tests are already asserting that invoked functions are always run.
There was a problem hiding this comment.
So I patterned this test off of the "ProvidesLifecycle" line for line. I don't think that the assertion is necessary, but I figured that the tests to confirm whether or not a type is provided to the container should be the same in content. lmk if you think I should remove the found assertion from both tests or leave it.
|
Overall, I like the way this came out - nice work! I think we may want to expand the Unfortunately, this will take a bit of work - the logger (or whatever other struct we're using to track this info) needs to come from inside the container, so it's not available at the Fx layer. Instead, I think we'll want applications to be able to register callbacks that execute just before we actually shut down the app. (Something like |
523c3b6 to
13e7abd
Compare
…struct; 2) remove defer statement; 3) don't instantiate blank dones channel; 4) provide the shutdowner without anonymous function
2b273a6 to
f96250a
Compare
akshayjshah
left a comment
There was a problem hiding this comment.
Looks great - thanks for going along with the many rounds of review on this. Trust you to add punctuation to comments without further review :)
app.go
Outdated
| // development, users can send the application SIGTERM by pressing Ctrl-C in | ||
| // the same terminal as the running process. | ||
| // Alternatively, a signal can be broadcast to all done channels manually by | ||
| // using the Shutdown functionality (see the Shutdowner documentation for details) |
There was a problem hiding this comment.
nit: please add a period, exclamation point, or terminal punctuation of your choice at the end of this sentence.
Rather than noting that we're providing the Shutdowner to the container, I changed it to mention that we provided the ability to shut the application down from inside the container since providing it to the container is implicit at that point.
This adds a Shutdowner interface with a Shutdown method, providing it to all
Fx applications. This provides the ability to signal app shutdown from any Fx
module.
Note that a channel of signal receivers is used because os/signal.Notify,
and incidentally fx.App.Done() may be called multiple times, so we need
to notify all channels on shutdown.
Summary of Changes:
dones, a slice of done channels, andmu, a mutex, to the App objectin order to maintain references to all done channels created via the
Donemethod, and to be able to concurrency-safely read from and write to the
dones.Donemethod to record created channels on the app as well asreturning a read-only channel.
upon construction that has access to the app object's
donesandmu, andholding a reference to it.
App, instantiate thedonesfield andprovide the Shutdowner to the container.
Points of consideration:
fxtest?
ShutdownOptions should be added to the shutdown function andavailable to modules? Should any shutdown-related
Options be surfaced tothe end user, such as white-listing which modules are allowed to call
Shutdown()?Resolves #615.