// 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) }