1// Copyright 2015 The etcd Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//go:build !windows && !plan9
16// +build !windows,!plan9
17
18package osutil
19
20import (
21	"os"
22	"os/signal"
23	"sync"
24	"syscall"
25
26	"go.uber.org/zap"
27)
28
29// InterruptHandler is a function that is called on receiving a
30// SIGTERM or SIGINT signal.
31type InterruptHandler func()
32
33var (
34	interruptRegisterMu, interruptExitMu sync.Mutex
35	// interruptHandlers holds all registered InterruptHandlers in order
36	// they will be executed.
37	interruptHandlers = []InterruptHandler{}
38)
39
40// RegisterInterruptHandler registers a new InterruptHandler. Handlers registered
41// after interrupt handing was initiated will not be executed.
42func RegisterInterruptHandler(h InterruptHandler) {
43	interruptRegisterMu.Lock()
44	defer interruptRegisterMu.Unlock()
45	interruptHandlers = append(interruptHandlers, h)
46}
47
48// HandleInterrupts calls the handler functions on receiving a SIGINT or SIGTERM.
49func HandleInterrupts(lg *zap.Logger) {
50	notifier := make(chan os.Signal, 1)
51	signal.Notify(notifier, syscall.SIGINT, syscall.SIGTERM)
52
53	go func() {
54		sig := <-notifier
55
56		interruptRegisterMu.Lock()
57		ihs := make([]InterruptHandler, len(interruptHandlers))
58		copy(ihs, interruptHandlers)
59		interruptRegisterMu.Unlock()
60
61		interruptExitMu.Lock()
62
63		if lg != nil {
64			lg.Info("received signal; shutting down", zap.String("signal", sig.String()))
65		}
66
67		for _, h := range ihs {
68			h()
69		}
70		signal.Stop(notifier)
71		pid := syscall.Getpid()
72		// exit directly if it is the "init" process, since the kernel will not help to kill pid 1.
73		if pid == 1 {
74			os.Exit(0)
75		}
76		setDflSignal(sig.(syscall.Signal))
77		syscall.Kill(pid, sig.(syscall.Signal))
78	}()
79}
80
81// Exit relays to os.Exit if no interrupt handlers are running, blocks otherwise.
82func Exit(code int) {
83	interruptExitMu.Lock()
84	os.Exit(code)
85}
86