vitess.io/vitess@v0.16.2/go/vt/servenv/servenv.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // Package servenv contains functionality that is common for all
    18  // Vitess server programs.  It defines and initializes command line
    19  // flags that control the runtime environment.
    20  //
    21  // After a server program has called flag.Parse, it needs to call
    22  // env.Init to make env use the command line variables to initialize
    23  // the environment. It also needs to call env.Close before exiting.
    24  //
    25  // Note: If you need to plug in any custom initialization/cleanup for
    26  // a vitess distribution, register them using OnInit and onClose. A
    27  // clean way of achieving that is adding to this package a file with
    28  // an init() function that registers the hooks.
    29  package servenv
    30  
    31  import (
    32  	// register the HTTP handlers for profiling
    33  	_ "net/http/pprof"
    34  	"net/url"
    35  	"os"
    36  	"os/signal"
    37  	"runtime/debug"
    38  	"strings"
    39  	"sync"
    40  	"syscall"
    41  	"time"
    42  
    43  	"github.com/spf13/pflag"
    44  
    45  	"vitess.io/vitess/go/event"
    46  	"vitess.io/vitess/go/netutil"
    47  	"vitess.io/vitess/go/stats"
    48  	"vitess.io/vitess/go/trace"
    49  	"vitess.io/vitess/go/vt/grpccommon"
    50  	"vitess.io/vitess/go/vt/log"
    51  	"vitess.io/vitess/go/vt/logutil"
    52  	"vitess.io/vitess/go/vt/vterrors"
    53  
    54  	// register the proper init and shutdown hooks for logging
    55  	_ "vitess.io/vitess/go/vt/logutil"
    56  
    57  	// Include deprecation warnings for soon-to-be-unsupported flag invocations.
    58  	_flag "vitess.io/vitess/go/internal/flag"
    59  )
    60  
    61  var (
    62  	// port is part of the flags used when calling RegisterDefaultFlags.
    63  	port int
    64  
    65  	// mutex used to protect the Init function
    66  	mu sync.Mutex
    67  
    68  	onInitHooks     event.Hooks
    69  	onTermHooks     event.Hooks
    70  	onTermSyncHooks event.Hooks
    71  	onRunHooks      event.Hooks
    72  	inited          bool
    73  
    74  	// ListeningURL is filled in when calling Run, contains the server URL.
    75  	ListeningURL url.URL
    76  )
    77  
    78  // Flags specific to Init, Run, and RunDefault functions.
    79  var (
    80  	lameduckPeriod = 50 * time.Millisecond
    81  	onTermTimeout  = 10 * time.Second
    82  	onCloseTimeout = 10 * time.Second
    83  	catchSigpipe   bool
    84  	maxStackSize   = 64 * 1024 * 1024
    85  	initStartTime  time.Time // time when tablet init started: for debug purposes to time how long a tablet init takes
    86  )
    87  
    88  // RegisterFlags installs the flags used by Init, Run, and RunDefault.
    89  //
    90  // This must be called before servenv.ParseFlags if using any of those
    91  // functions.
    92  func RegisterFlags() {
    93  	OnParse(func(fs *pflag.FlagSet) {
    94  		fs.DurationVar(&lameduckPeriod, "lameduck-period", lameduckPeriod, "keep running at least this long after SIGTERM before stopping")
    95  		fs.DurationVar(&onTermTimeout, "onterm_timeout", onTermTimeout, "wait no more than this for OnTermSync handlers before stopping")
    96  		fs.DurationVar(&onCloseTimeout, "onclose_timeout", onCloseTimeout, "wait no more than this for OnClose handlers before stopping")
    97  		fs.BoolVar(&catchSigpipe, "catch-sigpipe", catchSigpipe, "catch and ignore SIGPIPE on stdout and stderr if specified")
    98  		fs.IntVar(&maxStackSize, "max-stack-size", maxStackSize, "configure the maximum stack size in bytes")
    99  
   100  		// pid_file.go
   101  		fs.StringVar(&pidFile, "pid_file", pidFile, "If set, the process will write its pid to the named file, and delete it on graceful shutdown.")
   102  	})
   103  }
   104  
   105  func GetInitStartTime() time.Time {
   106  	mu.Lock()
   107  	defer mu.Unlock()
   108  	return initStartTime
   109  }
   110  
   111  // Init is the first phase of the server startup.
   112  func Init() {
   113  	mu.Lock()
   114  	defer mu.Unlock()
   115  	initStartTime = time.Now()
   116  
   117  	// Ignore SIGPIPE if specified
   118  	// The Go runtime catches SIGPIPE for us on all fds except stdout/stderr
   119  	// See https://golang.org/pkg/os/signal/#hdr-SIGPIPE
   120  	if catchSigpipe {
   121  		sigChan := make(chan os.Signal, 1)
   122  		signal.Notify(sigChan, syscall.SIGPIPE)
   123  		go func() {
   124  			<-sigChan
   125  			log.Warning("Caught SIGPIPE (ignoring all future SIGPIPEs)")
   126  			signal.Ignore(syscall.SIGPIPE)
   127  		}()
   128  	}
   129  
   130  	// Add version tag to every info log
   131  	log.Infof(AppVersion.String())
   132  	if inited {
   133  		log.Fatal("servenv.Init called second time")
   134  	}
   135  	inited = true
   136  
   137  	// Once you run as root, you pretty much destroy the chances of a
   138  	// non-privileged user starting the program correctly.
   139  	if uid := os.Getuid(); uid == 0 {
   140  		log.Exitf("servenv.Init: running this as root makes no sense")
   141  	}
   142  
   143  	// We used to set this limit directly, but you pretty much have to
   144  	// use a root account to allow increasing a limit reliably. Dropping
   145  	// privileges is also tricky. The best strategy is to make a shell
   146  	// script set up the limits as root and switch users before starting
   147  	// the server.
   148  	fdLimit := &syscall.Rlimit{}
   149  	if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, fdLimit); err != nil {
   150  		log.Errorf("max-open-fds failed: %v", err)
   151  	}
   152  	fdl := stats.NewGauge("MaxFds", "File descriptor limit")
   153  	fdl.Set(int64(fdLimit.Cur))
   154  
   155  	// Limit the stack size. We don't need huge stacks and smaller limits mean
   156  	// any infinite recursion fires earlier and on low memory systems avoids
   157  	// out of memory issues in favor of a stack overflow error.
   158  	debug.SetMaxStack(maxStackSize)
   159  
   160  	onInitHooks.Fire()
   161  }
   162  
   163  func populateListeningURL(port int32) {
   164  	host, err := netutil.FullyQualifiedHostname()
   165  	if err != nil {
   166  		host, err = os.Hostname()
   167  		if err != nil {
   168  			log.Exitf("os.Hostname() failed: %v", err)
   169  		}
   170  	}
   171  	ListeningURL = url.URL{
   172  		Scheme: "http",
   173  		Host:   netutil.JoinHostPort(host, port),
   174  		Path:   "/",
   175  	}
   176  }
   177  
   178  // OnInit registers f to be run at the beginning of the app
   179  // lifecycle. It should be called in an init() function.
   180  func OnInit(f func()) {
   181  	onInitHooks.Add(f)
   182  }
   183  
   184  // OnTerm registers a function to be run when the process receives a SIGTERM.
   185  // This allows the program to change its behavior during the lameduck period.
   186  //
   187  // All hooks are run in parallel, and there is no guarantee that the process
   188  // will wait for them to finish before dying when the lameduck period expires.
   189  //
   190  // See also: OnTermSync
   191  func OnTerm(f func()) {
   192  	onTermHooks.Add(f)
   193  }
   194  
   195  // OnTermSync registers a function to be run when the process receives SIGTERM.
   196  // This allows the program to change its behavior during the lameduck period.
   197  //
   198  // All hooks are run in parallel, and the process will do its best to wait
   199  // (up to -onterm_timeout) for all of them to finish before dying.
   200  //
   201  // See also: OnTerm
   202  func OnTermSync(f func()) {
   203  	onTermSyncHooks.Add(f)
   204  }
   205  
   206  // fireOnTermSyncHooks returns true iff all the hooks finish before the timeout.
   207  func fireOnTermSyncHooks(timeout time.Duration) bool {
   208  	return fireHooksWithTimeout(timeout, "OnTermSync", onTermSyncHooks.Fire)
   209  }
   210  
   211  // fireOnCloseHooks returns true iff all the hooks finish before the timeout.
   212  func fireOnCloseHooks(timeout time.Duration) bool {
   213  	return fireHooksWithTimeout(timeout, "OnClose", func() {
   214  		onCloseHooks.Fire()
   215  		ListeningURL = url.URL{}
   216  	})
   217  }
   218  
   219  // fireHooksWithTimeout returns true iff all the hooks finish before the timeout.
   220  func fireHooksWithTimeout(timeout time.Duration, name string, hookFn func()) bool {
   221  	defer log.Flush()
   222  	log.Infof("Firing %s hooks and waiting up to %v for them", name, timeout)
   223  
   224  	timer := time.NewTimer(timeout)
   225  	defer timer.Stop()
   226  
   227  	done := make(chan struct{})
   228  	go func() {
   229  		hookFn()
   230  		close(done)
   231  	}()
   232  
   233  	select {
   234  	case <-done:
   235  		log.Infof("%s hooks finished", name)
   236  		return true
   237  	case <-timer.C:
   238  		log.Infof("%s hooks timed out", name)
   239  		return false
   240  	}
   241  }
   242  
   243  // OnRun registers f to be run right at the beginning of Run. All
   244  // hooks are run in parallel.
   245  func OnRun(f func()) {
   246  	onRunHooks.Add(f)
   247  }
   248  
   249  // FireRunHooks fires the hooks registered by OnHook.
   250  // Use this in a non-server to run the hooks registered
   251  // by servenv.OnRun().
   252  func FireRunHooks() {
   253  	onRunHooks.Fire()
   254  }
   255  
   256  // RegisterDefaultFlags registers the default flags for
   257  // listening to a given port for standard connections.
   258  // If calling this, then call RunDefault()
   259  func RegisterDefaultFlags() {
   260  	OnParse(func(fs *pflag.FlagSet) {
   261  		fs.IntVar(&port, "port", port, "port for the server")
   262  	})
   263  }
   264  
   265  // Port returns the value of the `--port` flag.
   266  func Port() int {
   267  	return port
   268  }
   269  
   270  // RunDefault calls Run() with the parameters from the flags.
   271  func RunDefault() {
   272  	Run(port)
   273  }
   274  
   275  var (
   276  	flagHooksM      sync.Mutex
   277  	globalFlagHooks = []func(*pflag.FlagSet){
   278  		vterrors.RegisterFlags,
   279  	}
   280  	commandFlagHooks = map[string][]func(*pflag.FlagSet){}
   281  )
   282  
   283  // OnParse registers a callback function to register flags on the flagset that are
   284  // used by any caller of servenv.Parse or servenv.ParseWithArgs.
   285  func OnParse(f func(fs *pflag.FlagSet)) {
   286  	flagHooksM.Lock()
   287  	defer flagHooksM.Unlock()
   288  
   289  	globalFlagHooks = append(globalFlagHooks, f)
   290  }
   291  
   292  // OnParseFor registers a callback function to register flags on the flagset
   293  // used by servenv.Parse or servenv.ParseWithArgs. The provided callback will
   294  // only be called if the `cmd` argument passed to either Parse or ParseWithArgs
   295  // exactly matches the `cmd` argument passed to OnParseFor.
   296  //
   297  // To register for flags for multiple commands, for example if a package's flags
   298  // should be used for only vtgate and vttablet but no other binaries, call this
   299  // multiple times with the same callback function. To register flags for all
   300  // commands globally, use OnParse instead.
   301  func OnParseFor(cmd string, f func(fs *pflag.FlagSet)) {
   302  	flagHooksM.Lock()
   303  	defer flagHooksM.Unlock()
   304  
   305  	commandFlagHooks[cmd] = append(commandFlagHooks[cmd], f)
   306  }
   307  
   308  func getFlagHooksFor(cmd string) (hooks []func(fs *pflag.FlagSet)) {
   309  	flagHooksM.Lock()
   310  	defer flagHooksM.Unlock()
   311  
   312  	hooks = append(hooks, globalFlagHooks...) // done deliberately to copy the slice
   313  
   314  	if commandHooks, ok := commandFlagHooks[cmd]; ok {
   315  		hooks = append(hooks, commandHooks...)
   316  	}
   317  
   318  	return hooks
   319  }
   320  
   321  // ParseFlags initializes flags and handles the common case when no positional
   322  // arguments are expected.
   323  func ParseFlags(cmd string) {
   324  	fs := GetFlagSetFor(cmd)
   325  
   326  	_flag.Parse(fs)
   327  
   328  	if version {
   329  		AppVersion.Print()
   330  		os.Exit(0)
   331  	}
   332  
   333  	args := fs.Args()
   334  	if len(args) > 0 {
   335  		_flag.Usage()
   336  		log.Exitf("%s doesn't take any positional arguments, got '%s'", cmd, strings.Join(args, " "))
   337  	}
   338  
   339  	logutil.PurgeLogs()
   340  }
   341  
   342  // GetFlagSetFor returns the flag set for a given command.
   343  // This has to exported for the Vitess-operator to use
   344  func GetFlagSetFor(cmd string) *pflag.FlagSet {
   345  	fs := pflag.NewFlagSet(cmd, pflag.ExitOnError)
   346  	for _, hook := range getFlagHooksFor(cmd) {
   347  		hook(fs)
   348  	}
   349  
   350  	return fs
   351  }
   352  
   353  // ParseFlagsWithArgs initializes flags and returns the positional arguments
   354  func ParseFlagsWithArgs(cmd string) []string {
   355  	fs := GetFlagSetFor(cmd)
   356  
   357  	_flag.Parse(fs)
   358  
   359  	if version {
   360  		AppVersion.Print()
   361  		os.Exit(0)
   362  	}
   363  
   364  	args := fs.Args()
   365  	if len(args) == 0 {
   366  		log.Exitf("%s expected at least one positional argument", cmd)
   367  	}
   368  
   369  	logutil.PurgeLogs()
   370  
   371  	return args
   372  }
   373  
   374  // Flag installations for packages that servenv imports. We need to register
   375  // here rather than in those packages (which is what we would normally do)
   376  // because that would create a dependency cycle.
   377  func init() {
   378  	// These are the binaries that call trace.StartTracing.
   379  	for _, cmd := range []string{
   380  		"vtadmin",
   381  		"vtclient",
   382  		"vtcombo",
   383  		"vtctl",
   384  		"vtctlclient",
   385  		"vtctld",
   386  		"vtgate",
   387  		"vttablet",
   388  	} {
   389  		OnParseFor(cmd, trace.RegisterFlags)
   390  	}
   391  
   392  	// These are the binaries that make gRPC calls.
   393  	for _, cmd := range []string{
   394  		"vtbackup",
   395  		"vtcombo",
   396  		"vtctl",
   397  		"vtctlclient",
   398  		"vtctld",
   399  		"vtgate",
   400  		"vtgateclienttest",
   401  		"vtgr",
   402  		"vtorc",
   403  		"vttablet",
   404  		"vttestserver",
   405  	} {
   406  		OnParseFor(cmd, grpccommon.RegisterFlags)
   407  	}
   408  
   409  	// These are the binaries that export stats
   410  	for _, cmd := range []string{
   411  		"vtbackup",
   412  		"vtcombo",
   413  		"vtctld",
   414  		"vtgate",
   415  		"vtgr",
   416  		"vttablet",
   417  		"vtorc",
   418  	} {
   419  		OnParseFor(cmd, stats.RegisterFlags)
   420  	}
   421  
   422  	// Flags in package log are installed for all binaries.
   423  	OnParse(log.RegisterFlags)
   424  	// Flags in package logutil are installed for all binaries.
   425  	OnParse(logutil.RegisterFlags)
   426  }
   427  
   428  func RegisterFlagsForTopoBinaries(registerFlags func(fs *pflag.FlagSet)) {
   429  	topoBinaries := []string{
   430  		"vtbackup",
   431  		"vtcombo",
   432  		"vtctl",
   433  		"vtctld",
   434  		"vtgate",
   435  		"vtgr",
   436  		"vttablet",
   437  		"vttestserver",
   438  		"zk",
   439  		"vtorc",
   440  	}
   441  	for _, cmd := range topoBinaries {
   442  		OnParseFor(cmd, registerFlags)
   443  	}
   444  }