github.com/swiftstack/proxyfs@v0.0.0-20201223034610-5434d919416e/proxyfsd/daemon.go (about)

     1  package proxyfsd
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	_ "net/http/pprof"
     7  	"os"
     8  	"os/signal"
     9  	"strings"
    10  	"sync"
    11  
    12  	"golang.org/x/sys/unix"
    13  
    14  	"github.com/swiftstack/ProxyFS/conf"
    15  	"github.com/swiftstack/ProxyFS/logger"
    16  	"github.com/swiftstack/ProxyFS/transitions"
    17  	"github.com/swiftstack/ProxyFS/version"
    18  
    19  	// Force importing of the following "top-most" package
    20  	_ "github.com/swiftstack/ProxyFS/httpserver"
    21  )
    22  
    23  // Daemon is launched as a GoRoutine that launches ProxyFS. During startup, the parent should read errChan
    24  // to await Daemon getting to the point where it is ready to handle the specified signal set. Any errors
    25  // encountered before or after this point will be sent to errChan (and be non-nil of course).
    26  func Daemon(confFile string, confStrings []string, errChan chan error, wg *sync.WaitGroup, execArgs []string, signals ...os.Signal) {
    27  	var (
    28  		confMap        conf.ConfMap
    29  		err            error
    30  		signalReceived os.Signal
    31  	)
    32  
    33  	// Compute confMap
    34  
    35  	confMap, err = conf.MakeConfMapFromFile(confFile)
    36  	if nil != err {
    37  		errChan <- err
    38  
    39  		return
    40  	}
    41  
    42  	err = confMap.UpdateFromStrings(confStrings)
    43  	if nil != err {
    44  		errChan <- err
    45  
    46  		return
    47  	}
    48  
    49  	// Optionally launch an embedded HTTP Server for Golang runtime access;
    50  	// this should be done before transitions.Up() is called so it is
    51  	// available if transitions.Up() hangs (the embedded http server will
    52  	// return http.StatusServiceUnavailable (503) during the transition.
    53  	debugServerPortAsUint16, err := confMap.FetchOptionValueUint16("ProxyfsDebug", "DebugServerPort")
    54  	if nil != err && debugServerPortAsUint16 != 0 {
    55  
    56  		debugServerPortAsString := fmt.Sprintf("%d", debugServerPortAsUint16)
    57  		logger.Infof("proxyfsd.Daemon() starting debug HTTP Server on localhost:%s", debugServerPortAsString)
    58  		go http.ListenAndServe("localhost:"+debugServerPortAsString, nil)
    59  	}
    60  
    61  	// Arm signal handler used to catch signals
    62  	//
    63  	// Note: signalChan must be buffered to avoid race with window between
    64  	// arming handler and blocking on the chan read when signals might
    65  	// otherwise be lost.  No signals will be processed until
    66  	// transitions.Up() finishes, but an incoming SIGHUP will not cause the
    67  	// process to immediately exit.
    68  	signalChan := make(chan os.Signal, 16)
    69  
    70  	// if signals is empty it means "catch all signals" it is possible to catch
    71  	signal.Notify(signalChan, signals...)
    72  
    73  	// Start up dæmon packages
    74  
    75  	err = transitions.Up(confMap)
    76  	if nil != err {
    77  		errChan <- err
    78  		return
    79  	}
    80  	wg.Add(1)
    81  	logger.Infof("proxyfsd is starting up (version %s) (PID %d); invoked as '%s'",
    82  		version.ProxyFSVersion, os.Getpid(), strings.Join(execArgs, "' '"))
    83  	defer func() {
    84  		logger.Infof("proxyfsd logger is shutting down (PID %d)", os.Getpid())
    85  		err = transitions.Down(confMap)
    86  		if nil != err {
    87  			logger.Errorf("transitions.Down() failed: %v", err) // Oddly, if logger.Down() fails, will this work?
    88  		}
    89  		errChan <- err
    90  		wg.Done()
    91  	}()
    92  
    93  	// indicate transitions finished and signal handlers have been armed successfully
    94  	errChan <- nil
    95  
    96  	// Await a signal - reloading confFile each SIGHUP - exiting otherwise
    97  	for {
    98  		signalReceived = <-signalChan
    99  		logger.Infof("Received signal: '%v'", signalReceived)
   100  
   101  		// these signals are normally ignored, but if "signals..." above is empty
   102  		// they are delivered via the channel.  we should simply ignore them.
   103  		if signalReceived == unix.SIGCHLD || signalReceived == unix.SIGURG ||
   104  			signalReceived == unix.SIGWINCH || signalReceived == unix.SIGCONT {
   105  			logger.Infof("Ignored signal: '%v'", signalReceived)
   106  			continue
   107  		}
   108  
   109  		// we can get SIGPIPE whenever an HTTP or other client closes a
   110  		// socket on us, so ignore it
   111  		if signalReceived == unix.SIGPIPE {
   112  			logger.Infof("Ignored signal: '%v'", signalReceived)
   113  			continue
   114  		}
   115  
   116  		// SIGHUP means reconfig but any other signal means time to exit
   117  		if unix.SIGHUP != signalReceived {
   118  			logger.Infof("signal catcher is shutting down proxyfsd (PID %d)", os.Getpid())
   119  
   120  			if signalReceived != unix.SIGTERM && signalReceived != unix.SIGINT {
   121  				logger.Errorf("proxyfsd received unexpected signal: %v", signalReceived)
   122  			}
   123  
   124  			return
   125  		}
   126  
   127  		// caught SIGHUP -- recompute confMap and re-apply
   128  		confMap, err = conf.MakeConfMapFromFile(confFile)
   129  		if nil != err {
   130  			err = fmt.Errorf("failed to load updated config: %v", err)
   131  			errChan <- err
   132  			return
   133  		}
   134  
   135  		err = confMap.UpdateFromStrings(confStrings)
   136  		if nil != err {
   137  			err = fmt.Errorf("failed to reapply config overrides: %v", err)
   138  			errChan <- err
   139  			return
   140  		}
   141  
   142  		err = transitions.Signaled(confMap)
   143  		if nil != err {
   144  			err = fmt.Errorf("transitions.Signaled() failed: %v", err)
   145  			errChan <- err
   146  			return
   147  		}
   148  	}
   149  }