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 }