// Copyright © 2023 OpenIM. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package interrupt deal with signals. package interrupt import ( "os" "os/signal" "sync" "syscall" ) // terminationSignals are signals that cause the program to exit in the // supported platforms (linux, darwin, windows). var terminationSignals = []os.Signal{syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT} // Handler guarantees execution of notifications after a critical section (the function passed // to a Run method), even in the presence of process termination. It guarantees exactly once // invocation of the provided notify functions. type Handler struct { notify []func() final func(os.Signal) once sync.Once } // Chain creates a new handler that invokes all notify functions when the critical section exits // and then invokes the optional handler's notifications. This allows critical sections to be // nested without losing exactly once invocations. Notify functions can invoke any cleanup needed // but should not exit (which is the responsibility of the parent handler). func Chain(handler *Handler, notify ...func()) *Handler { if handler == nil { return New(nil, notify...) } return New(handler.Signal, append(notify, handler.Close)...) } // New creates a new handler that guarantees all notify functions are run after the critical // section exits (or is interrupted by the OS), then invokes the final handler. If no final // handler is specified, the default final is `os.Exit(1)`. A handler can only be used for // one critical section. func New(final func(os.Signal), notify ...func()) *Handler { return &Handler{ final: final, notify: notify, } } // Close executes all the notification handlers if they have not yet been executed. func (h *Handler) Close() { h.once.Do(func() { for _, fn := range h.notify { fn() } }) } // Signal is called when an os.Signal is received, and guarantees that all notifications // are executed, then the final handler is executed. This function should only be called once // per Handler instance. func (h *Handler) Signal(s os.Signal) { h.once.Do(func() { for _, fn := range h.notify { fn() } if h.final == nil { os.Exit(1) } h.final(s) }) } // Run ensures that any notifications are invoked after the provided fn exits (even if the // process is interrupted by an OS termination signal). Notifications are only invoked once // per Handler instance, so calling Run more than once will not behave as the user expects. func (h *Handler) Run(fn func() error) error { ch := make(chan os.Signal, 1) signal.Notify(ch, terminationSignals...) defer func() { signal.Stop(ch) close(ch) }() go func() { sig, ok := <-ch if !ok { return } h.Signal(sig) }() defer h.Close() return fn() }