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