get.pme.sh/pnats@v0.0.0-20240304004023-26bb5a137ed0/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  }