英文:
How to exit a go program honoring deferred calls?
问题
我需要使用defer
来手动释放使用C
库创建的内存分配,但我也需要在某个时刻使用非零状态的os.Exit
。棘手的部分是os.Exit
会跳过任何已延迟的指令:
package main
import "fmt"
import "os"
func main() {
// 使用`os.Exit`时,`defer`不会被执行,所以这个`fmt.Println`永远不会被调用。
defer fmt.Println("!")
// 有时候人们会使用`defer`来执行关键操作,比如关闭数据库、删除锁或释放内存。
// 使用指定的状态码退出程序。
os.Exit(3)
}
Playground: http://play.golang.org/p/CDiAh9SXRM,源自https://gobyexample.com/exit
那么如何在Go程序中退出并执行已声明的defer
调用呢?是否有os.Exit
的替代方案?
英文:
I need to use defer
to free allocations manually created using C
library, but I also need to os.Exit
with non 0 status at some point. The tricky part is that os.Exit
skips any deferred instruction:
package main
import "fmt"
import "os"
func main() {
// `defer`s will _not_ be run when using `os.Exit`, so
// this `fmt.Println` will never be called.
defer fmt.Println("!")
// sometimes ones might use defer to do critical operations
// like close a database, remove a lock or free memory
// Exit with status code.
os.Exit(3)
}
Playground: http://play.golang.org/p/CDiAh9SXRM stolen from https://gobyexample.com/exit
So how to exit a go program honoring declared defer
calls? Is there any alternative to os.Exit
?
答案1
得分: 57
只需将程序下移一级并返回退出代码:
package main
import "fmt"
import "os"
func doTheStuff() int {
defer fmt.Println("!")
return 3
}
func main() {
os.Exit(doTheStuff())
}
英文:
Just move your program down a level and return your exit code:
package main
import "fmt"
import "os"
func doTheStuff() int {
defer fmt.Println("!")
return 3
}
func main() {
os.Exit(doTheStuff())
}
答案2
得分: 40
runtime.Goexit()
是实现这一目标的简单方法。
> Goexit 终止调用它的 goroutine。不会影响其他 goroutine。**Goexit 在终止 goroutine 之前运行所有延迟调用。**然而,由于 Goexit 不是 panic,因此在这些延迟函数中的任何 recover 调用都将返回 nil。
然而:
> 从主 goroutine 调用 Goexit 会终止该 goroutine,而不会使 func main 返回。由于 func main 尚未返回,程序会继续执行其他 goroutine。如果所有其他 goroutine 退出,程序将崩溃。
因此,如果你从主 goroutine 调用它,在 main
函数的顶部你需要添加
defer os.Exit(0)
在此之下,你可能还想添加一些其他的 defer
语句,用于通知其他 goroutine 停止并进行清理。
英文:
runtime.Goexit()
is the easy way to accomplish that.
> Goexit terminates the goroutine that calls it. No other goroutine is affected. Goexit runs all deferred calls before terminating the goroutine. Because Goexit is not panic, however, any recover calls in those deferred functions will return nil.
However:
> Calling Goexit from the main goroutine terminates that goroutine without func main returning. Since func main has not returned, the program continues execution of other goroutines. If all other goroutines exit, the program crashes.
So if you call it from the main goroutine, at the top of main
you need to add
defer os.Exit(0)
Below that you might want to add some other defer
statements that inform the other goroutines to stop and clean up.
答案3
得分: 34
经过一些研究,参考了这个链接,我找到了一个替代方案:
- 不像https://stackoverflow.com/a/27629493/438563那样强制使用特定的架构
- 不像https://stackoverflow.com/a/24601700/438563那样需要任何全局值
我们可以利用panic
和recover
。事实证明,panic
本质上会遵守defer
调用,但总是以非0
状态码退出并转储堆栈跟踪。关键在于,我们可以通过以下方式覆盖panic
行为的最后一个方面:
package main
import "fmt"
import "os"
type Exit struct{ Code int }
// 退出码处理程序
func handleExit() {
if e := recover(); e != nil {
if exit, ok := e.(Exit); ok == true {
os.Exit(exit.Code)
}
panic(e) // 不是Exit类型,向上冒泡
}
}
现在,要在任何位置退出程序并仍保留任何声明的defer
指令,我们只需要触发一个Exit
类型:
func main() {
defer handleExit() // 插入退出处理程序
defer fmt.Println("cleaning...")
panic(Exit{3}) // 3是退出码
}
除了在func main
内插入一行代码外,它不需要任何重构:
func main() {
defer handleExit()
// 准备就绪
}
这在处理较大的代码库时非常有效,所以我将其提供给您进行审查。希望对您有所帮助。
英文:
After some research, refer to this this, I found an alternative that:
- Doesn't impose a certain architecture like in https://stackoverflow.com/a/27629493/438563
- Doesn't require any global value like in https://stackoverflow.com/a/24601700/438563
We can take advantage of panic
and recover
. It turns out that panic
, by nature, will honor defer
calls but will also always exit with non 0
status code and dump a stack trace. The trick is that we can override last aspect of panic behavior with:
package main
import "fmt"
import "os"
type Exit struct{ Code int }
// exit code handler
func handleExit() {
if e := recover(); e != nil {
if exit, ok := e.(Exit); ok == true {
os.Exit(exit.Code)
}
panic(e) // not an Exit, bubble up
}
}
Now, to exit a program at any point and still preserve any declared defer
instruction we just need to emit an Exit
type:
func main() {
defer handleExit() // plug the exit handler
defer fmt.Println("cleaning...")
panic(Exit{3}) // 3 is the exit code
}
It doesn't require any refactoring apart from plugging a line inside func main
:
func main() {
defer handleExit()
// ready to go
}
This scales pretty well with larger code bases so I'll leave it available for scrutinization. Hope it helps.
Playground: http://play.golang.org/p/4tyWwhcX0-
答案4
得分: 29
对于后代来说,对我来说,这是一个更优雅的解决方案:
func main() {
retcode := 0
defer func() { os.Exit(retcode) }()
defer defer1()
defer defer2()
[...]
if err != nil {
retcode = 1
return
}
}
英文:
For posterity, for me this was a more elegant solution:
func main() {
retcode := 0
defer func() { os.Exit(retcode) }()
defer defer1()
defer defer2()
[...]
if err != nil {
retcode = 1
return
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论