Reputation: 919
Is there any way to exit a Go program, but execute all the pending defer statements?
I've been clearing up temporary files by using defer, but the deferred statements aren't executed when the program is interrupted with Ctrl+C or even os.Exit.
After exiting this program with Ctrl+C, both foo.txt and bar.txt are left over:
package main
import (
"fmt"
"io/ioutil"
"os"
"os/signal"
"syscall"
)
func main() {
ioutil.WriteFile("./foo.txt", []byte("foo"), 0644)
defer os.RemoveAll("./foo.txt")
go func() {
ioutil.WriteFile("./bar.txt", []byte("bar"), 0644)
defer os.RemoveAll("./bar.txt")
for {
// various long running things
}
}()
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
signal.Notify(c, syscall.SIGTERM)
go func() {
<-c
fmt.Println("Received OS interrupt - exiting.")
os.Exit(0)
}()
for {
// various long running things
}
}
Upvotes: 0
Views: 5051
Reputation: 549
From golang reference:
A "defer" statement invokes a function whose execution is deferred to the moment the surrounding function returns
When you call os.Exit(0) you bypass the normal return procedure and your deferred functions are not executed.
Also, even if the deferred worked inside the main goroutine, the defers in other goroutines would not work since they would die before returning.
A better code architecture would allow you to get something similar. You need to think about your long running processes as workers. Export every long running process in workers and defer any clean up right after calling the worker. Use a select in the main goroutine to capture signals and synchronise work
package main
import (
"fmt"
"io/ioutil"
"os"
"os/signal"
"syscall"
"time"
)
func check(e error) {
if e != nil {
panic(e)
}
}
func main() {
ioutil.WriteFile("./foo.txt", []byte("foo"), 0644)
defer os.RemoveAll("./foo.txt")
// Worker 1
done := make(chan bool, 1)
go func(done chan bool) {
fmt.Println("worker 1 with bar ...")
ioutil.WriteFile("./bar.txt", []byte("bar"), 0644)
// various long running things
time.Sleep(3 * time.Second)
done <- true
}(done)
defer os.RemoveAll("./bar.txt")
// End worker1
s := make(chan os.Signal, 1)
signal.Notify(s, os.Interrupt)
signal.Notify(s, syscall.SIGTERM)
// Worker 2
done2 := make(chan bool, 1)
go func(done chan bool) {
fmt.Println("worker 2 ...")
time.Sleep(6 * time.Second)
done <- true
}(done2)
// End worker 2
select {
case <-s:
fmt.Println("Quiting with signal - exit")
case <-done:
<-done2
case <-done2:
<-done
}
return
}
This select is a quick and dirty way to handle two workers, a better way would be to use sync.WaitGroup
Upvotes: 1
Reputation: 23138
I would recommend not relying on defer, but defining a reusable function that can be used in a defer or in the signal block. Something like this:
package main
import (
"fmt"
"io/ioutil"
"os"
"os/signal"
"syscall"
)
func main() {
ioutil.WriteFile("./foo.txt", []byte("foo"), 0644)
cleanup := func(){
os.RemoveAll("./foo.txt")
os.RemoveAll("./bar.txt")
}
defer cleanup() //for normal return
go func() {
ioutil.WriteFile("./bar.txt", []byte("bar"), 0644)
for {
// various long running things
}
}()
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
signal.Notify(c, syscall.SIGTERM)
go func() {
<-c
fmt.Println("Received OS interrupt - exiting.")
cleanup()
os.Exit(0)
}()
for {
// various long running things
}
}
Upvotes: 0