I am looking for the safe way to do a clean shutdown when blocked on I/O. Here is a basic example:
package main
import (
"encoding/json"
"fmt"
"io"
"os"
"os/signal"
"syscall"
)
func main() {
rx := json.NewDecoder(os.Stdin)
chanTERM := make(chan os.Signal, 1)
signal.Notify(chanTERM, syscall.SIGTERM)
for {
select {
case <-chanTERM:
fmt.Println("Shutdown requested via signal")
os.Exit(0)
default:
}
var msg interface{}
err := rx.Decode(&msg)
if err != nil {
if err == io.EOF {
os.Exit(0)
}
fmt.Printf("Error: %v\n", err)
os.Exit(1)
}
fmt.Printf("Message: %v\n", msg)
}
}
This sort-of works, except that once the code is blocked in rx.Decode(&msg), the termination signal is not handled until another message arrives. (Actually I'm using sockets - os.Stdin is just for example here). I'd like it to shutdown immediately if it's not in the middle of doing something.
The question is then how to unblock this reader.
1. Is there a way to link an io.Reader to a context, so I can just send the ctx.Done() signal? If so, I couldn't find it.
(I found
this post, but the
library has the same issue: the context is checked before the read, but a cancel won't unblock the read)
2. I can just close the input stream from another goroutine. This seems fairly brutal. Also, to distinguish this condition from an actual error, it seems I need to parse the error message and look for the text "use of closed network connection" (github:
#4373)
3. I can move the rx.Decode(&msg) into a goroutine which passes a message over a channel, and just let it lock up if there's no data coming in. This isn't a problem here where I want to exit the entire program. If this was a network server with many connections and I wanted to disconnect just one, then I think I'd end up having to close the channel anyway to disconnect the client.
Is there another option I should be looking at?
Thanks,
Brian.