github.com/nats-io/nats-server/v2@v2.11.0-preview.2/server/signal.go (about) 1 // Copyright 2012-2019 The NATS Authors 2 // Licensed under the Apache License, Version 2.0 (the "License"); 3 // you may not use this file except in compliance with the License. 4 // You may obtain a copy of the License at 5 // 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 //go:build !windows && !wasm 15 // +build !windows,!wasm 16 17 package server 18 19 import ( 20 "errors" 21 "fmt" 22 "os" 23 "os/exec" 24 "os/signal" 25 "strconv" 26 "strings" 27 "syscall" 28 ) 29 30 var processName = "nats-server" 31 32 // SetProcessName allows to change the expected name of the process. 33 func SetProcessName(name string) { 34 processName = name 35 } 36 37 // Signal Handling 38 func (s *Server) handleSignals() { 39 if s.getOpts().NoSigs { 40 return 41 } 42 c := make(chan os.Signal, 1) 43 44 signal.Notify(c, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGHUP) 45 46 go func() { 47 for { 48 select { 49 case sig := <-c: 50 s.Debugf("Trapped %q signal", sig) 51 switch sig { 52 case syscall.SIGINT: 53 s.Shutdown() 54 os.Exit(0) 55 case syscall.SIGTERM: 56 // Shutdown unless graceful shutdown already in progress. 57 s.mu.Lock() 58 ldm := s.ldm 59 s.mu.Unlock() 60 61 if !ldm { 62 s.Shutdown() 63 os.Exit(1) 64 } 65 case syscall.SIGUSR1: 66 // File log re-open for rotating file logs. 67 s.ReOpenLogFile() 68 case syscall.SIGUSR2: 69 go s.lameDuckMode() 70 case syscall.SIGHUP: 71 // Config reload. 72 if err := s.Reload(); err != nil { 73 s.Errorf("Failed to reload server configuration: %s", err) 74 } 75 } 76 case <-s.quitCh: 77 return 78 } 79 } 80 }() 81 } 82 83 // ProcessSignal sends the given signal command to the given process. If pidStr 84 // is empty, this will send the signal to the single running instance of 85 // nats-server. If multiple instances are running, pidStr can be a globular 86 // expression ending with '*'. This returns an error if the given process is 87 // not running or the command is invalid. 88 func ProcessSignal(command Command, pidExpr string) error { 89 var ( 90 err error 91 errStr string 92 pids = make([]int, 1) 93 pidStr = strings.TrimSuffix(pidExpr, "*") 94 isGlob = strings.HasSuffix(pidExpr, "*") 95 ) 96 97 // Validate input if given 98 if pidStr != "" { 99 if pids[0], err = strconv.Atoi(pidStr); err != nil { 100 return fmt.Errorf("invalid pid: %s", pidStr) 101 } 102 } 103 // Gather all PIDs unless the input is specific 104 if pidStr == "" || isGlob { 105 if pids, err = resolvePids(); err != nil { 106 return err 107 } 108 } 109 // Multiple instances are running and the input is not an expression 110 if len(pids) > 1 && !isGlob { 111 errStr = fmt.Sprintf("multiple %s processes running:", processName) 112 for _, p := range pids { 113 errStr += fmt.Sprintf("\n%d", p) 114 } 115 return errors.New(errStr) 116 } 117 // No instances are running 118 if len(pids) == 0 { 119 return fmt.Errorf("no %s processes running", processName) 120 } 121 122 var signum syscall.Signal 123 if signum, err = CommandToSignal(command); err != nil { 124 return err 125 } 126 127 for _, pid := range pids { 128 if _pidStr := strconv.Itoa(pid); _pidStr != pidStr && pidStr != "" { 129 if !isGlob || !strings.HasPrefix(_pidStr, pidStr) { 130 continue 131 } 132 } 133 if err = kill(pid, signum); err != nil { 134 errStr += fmt.Sprintf("\nsignal %q %d: %s", command, pid, err) 135 } 136 } 137 if errStr != "" { 138 return errors.New(errStr) 139 } 140 return nil 141 } 142 143 // Translates a command to a signal number 144 func CommandToSignal(command Command) (syscall.Signal, error) { 145 switch command { 146 case CommandStop: 147 return syscall.SIGKILL, nil 148 case CommandQuit: 149 return syscall.SIGINT, nil 150 case CommandReopen: 151 return syscall.SIGUSR1, nil 152 case CommandReload: 153 return syscall.SIGHUP, nil 154 case commandLDMode: 155 return syscall.SIGUSR2, nil 156 case commandTerm: 157 return syscall.SIGTERM, nil 158 default: 159 return 0, fmt.Errorf("unknown signal %q", command) 160 } 161 } 162 163 // resolvePids returns the pids for all running nats-server processes. 164 func resolvePids() ([]int, error) { 165 // If pgrep isn't available, this will just bail out and the user will be 166 // required to specify a pid. 167 output, err := pgrep() 168 if err != nil { 169 switch err.(type) { 170 case *exec.ExitError: 171 // ExitError indicates non-zero exit code, meaning no processes 172 // found. 173 break 174 default: 175 return nil, errors.New("unable to resolve pid, try providing one") 176 } 177 } 178 var ( 179 myPid = os.Getpid() 180 pidStrs = strings.Split(string(output), "\n") 181 pids = make([]int, 0, len(pidStrs)) 182 ) 183 for _, pidStr := range pidStrs { 184 if pidStr == "" { 185 continue 186 } 187 pid, err := strconv.Atoi(pidStr) 188 if err != nil { 189 return nil, errors.New("unable to resolve pid, try providing one") 190 } 191 // Ignore the current process. 192 if pid == myPid { 193 continue 194 } 195 pids = append(pids, pid) 196 } 197 return pids, nil 198 } 199 200 var kill = func(pid int, signal syscall.Signal) error { 201 return syscall.Kill(pid, signal) 202 } 203 204 var pgrep = func() ([]byte, error) { 205 return exec.Command("pgrep", processName).Output() 206 }