blob: eb8aa1ce0a5c01cac96f2d05d8f5b817f9d7c4ad [file] [log] [blame]
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package exithook provides limited support for on-exit cleanup.
//
// CAREFUL! The expectation is that Add should only be called
// from a safe context (e.g. not an error/panic path or signal
// handler, preemption enabled, allocation allowed, write barriers
// allowed, etc), and that the exit function F will be invoked under
// similar circumstances. That is the say, we are expecting that F
// uses normal / high-level Go code as opposed to one of the more
// restricted dialects used for the trickier parts of the runtime.
package exithook
import (
"internal/runtime/atomic"
_ "unsafe" // for linkname
)
// A Hook is a function to be run at program termination
// (when someone invokes os.Exit, or when main.main returns).
// Hooks are run in reverse order of registration:
// the first hook added is the last one run.
type Hook struct {
F func() // func to run
RunOnFailure bool // whether to run on non-zero exit code
}
var (
locked atomic.Int32
runGoid atomic.Uint64
hooks []Hook
running bool
// runtime sets these for us
Gosched func()
Goid func() uint64
Throw func(string)
)
// Add adds a new exit hook.
func Add(h Hook) {
for !locked.CompareAndSwap(0, 1) {
Gosched()
}
hooks = append(hooks, h)
locked.Store(0)
}
// Run runs the exit hooks.
//
// If an exit hook panics, Run will throw with the panic on the stack.
// If an exit hook invokes exit in the same goroutine, the goroutine will throw.
// If an exit hook invokes exit in another goroutine, that exit will block.
func Run(code int) {
for !locked.CompareAndSwap(0, 1) {
if Goid() == runGoid.Load() {
Throw("exit hook invoked exit")
}
Gosched()
}
defer locked.Store(0)
runGoid.Store(Goid())
defer runGoid.Store(0)
defer func() {
if e := recover(); e != nil {
Throw("exit hook invoked panic")
}
}()
for len(hooks) > 0 {
h := hooks[len(hooks)-1]
hooks = hooks[:len(hooks)-1]
if code != 0 && !h.RunOnFailure {
continue
}
h.F()
}
}
type exitError string
func (e exitError) Error() string { return string(e) }