For the snippet below, the deferred call is not made when ^C is received. Is it possible that the cleanup introduces a race condition? If yes, what could be a better pattern of cleanup on receiving an interrupt?
func fn() {
// some code
defer cleanup()
go func() {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
// Block until a signal is received.
_ = <-c
cleanup()
}
for {
// Infinite loop. Returns iff an error is encountered in the
// body
}
}
Note that if you "install" your signal channel with
signal.Notify()
, the default behavior will be disabled. This means if you do this, thefor
loop in yourfn()
function will not be interrupted, it will continue to run.So when you receive a value on your registered channel, you have to make that
for
loop terminate so you can do a "clean" cleanup. Else the resourcescleanup()
ought to free might still be used infor
, most likely resulting in error or panic.Once you do this, you don't even have to call
cleanup()
manually, because returning fromfn()
will run the deferred function properly.Here's an example:
Of course the above example does not guarantee app termination. You should have some code that listens to the
shutdownCh
and terminates the app. This code should also wait for all goroutines to gracefully finish. For that you may usesync.WaitGroup
: add 1 to it when you launch a goroutine that should be waited for on exit, and callWaitGroup.Done()
when such a goroutine finishes.Also since in a real app there might be lots of these, the signal handling should be moved to a "central" place and not done in each place.
Here's a complete example how to do that:
Here's an example output of the above app, when pressing CTRL+C 3 seconds after its start: