func main() { // Get the listener and ppid from the environment. If this is successful, // this process is a child that's inheriting and open listener from ppid. l, ppid, err := goagain.GetEnvs() if nil != err { // Listen on a TCP socket and accept connections in a new goroutine. laddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:48879") if nil != err { log.Println(err) os.Exit(1) } log.Printf("listening on %v", laddr) l, err = net.ListenTCP("tcp", laddr) if nil != err { log.Println(err) os.Exit(1) } go serve(l) } else { // Resume listening and accepting connections in a new goroutine. log.Printf("resuming listening on %v", l.Addr()) go serve(l) // Kill the parent, now that the child has started successfully. if err := goagain.KillParent(ppid); nil != err { log.Println(err) os.Exit(1) } } // Block the main goroutine awaiting signals. if err := goagain.AwaitSignals(l); nil != err { log.Println(err) os.Exit(1) } // Do whatever's necessary to ensure a graceful exit like waiting for // goroutines to terminate or a channel to become closed. // In this case, we'll simply stop listening and wait one second. if err := l.Close(); nil != err { log.Fatalln(err) } time.Sleep(1e9) }
func main() { xlog.SetOutput(os.Stdout) options := struct { Addr string `goptions:"-L, --listen, description='Listen address'"` HashKey string `goptions:"--hashkey, description='Hash key for cookie store and XSRF', obligatory"` BlockKey string `goptions:"--blockkey, description='Crypto key for cookie store and XSRF', obligatory"` GplusClientID string `goptions:"--gplusclientid, description='Google+ Client ID', obligatory"` GplusClientSecret string `goptions:"--gplusclientsecret, description='Google+ Client Secret', obligatory"` GPlusAuthURL string `goptions:"--gplusauthurl, description='Google+ Authentication URL', obligatory"` TwitterClientKey string `goptions:"--twitterclientkey, description='Twitter Client Key', obligatory"` TwitterClientSecret string `goptions:"--twitterclientsecret, description='Twitter Client Secret', obligatory"` TwitterAuthURL string `goptions:"--twitterauthurl, description='Twitter Authentication URL', obligatory"` DSN string `goptions:"--dsn, description='MySQL DSN string', obligatory"` HtdocsDir string `goptions:"--htdocs, description='htdocs directory', obligatory"` UploadDir string `goptions:"--uploaddir, description='Upload directory', obligatory"` TmpDir string `goptions:"--tmpdir, description='directory for temporary files', obligatory"` RedisAddr string `goptions:"--redis, description='redis address', obligatory"` AccessLog bool `goptions:"--accesslog, description='log HTTP requests'"` StatHat string `goptions:"--stathat, description='Enable StatHat tracking and set user key'"` Topic string `goptions:"--topic, description='Topic to which uploads shall be published for conversions'"` NSQAddr string `goptions:"--nsqd, description='address:port of nsqd to publish messages to'"` PersonaAudience string `goptions:"--persona-audience, description='Persona audience, e.g. http://localhost:8080'"` }{ Addr: "[::]:8080", RedisAddr: ":6379", } goptions.ParseAndFail(&options) if options.StatHat != "" { enableStatHat = true stathatUserKey = options.StatHat } xlog.Debug("Creating cookie store...") sessionStore := sessions.NewCookieStore([]byte(options.HashKey), []byte(options.BlockKey)) secureCookie := securecookie.New([]byte(options.HashKey), []byte(options.BlockKey)) auth.Config.CookieSecret = []byte(options.HashKey) auth.Config.LoginSuccessRedirect = "/api/connect" auth.Config.CookieSecure = false xlog.Debugf("Connecting to database %s...", options.DSN) var dbStore *Store if sqldb, err := sql.Open("mysql", options.DSN); err != nil { xlog.Fatalf("sql.Open failed: %v", err) } else { dbStore = NewStore(sqldb) } fileStore := &FileUploadStore{UploadDir: options.UploadDir, TmpDir: options.TmpDir, Topic: options.Topic, NSQ: nsq.NewWriter(options.NSQAddr)} xlog.Debugf("Creating upload directory %s...", options.UploadDir) os.Mkdir(options.UploadDir, 0755) os.Mkdir(options.TmpDir, 0755) xlog.Debugf("Setting up HTTP server...") mux := http.NewServeMux() // auth calls mux.Handle("/auth/gplus", auth.Google(options.GplusClientID, options.GplusClientSecret, options.GPlusAuthURL)) mux.Handle("/auth/twitter", auth.Twitter(options.TwitterClientKey, options.TwitterClientSecret, options.TwitterAuthURL)) mux.Handle("/auth/persona", &PersonaAuthHandler{Audience: options.PersonaAudience, SessionStore: sessionStore, DBStore: dbStore, SecureCookie: secureCookie}) // API calls. apiRouter := pat.New() apiRouter.Get("/api/loggedin", &LoggedInHandler{SessionStore: sessionStore}) apiRouter.Get("/api/connect", http.HandlerFunc(auth.SecureUser(func(w http.ResponseWriter, r *http.Request, u auth.User) { Connect(w, r, u, sessionStore, secureCookie, dbStore) }))) apiRouter.Get("/api/connected", &ConnectedHandler{SessionStore: sessionStore, DBStore: dbStore}) apiRouter.Post("/api/disconnect", &DisconnectHandler{SessionStore: sessionStore, SecureCookie: secureCookie}) apiRouter.Post("/api/upload", &UploadHandler{SessionStore: sessionStore, DBStore: dbStore, UploadStore: fileStore, SecureCookie: secureCookie}) apiRouter.Get("/api/getuploads", &GetUploadsHandler{SessionStore: sessionStore, DBStore: dbStore}) apiRouter.Post("/api/renameupload", &RenameUploadHandler{SessionStore: sessionStore, DBStore: dbStore, SecureCookie: secureCookie}) apiRouter.Post("/api/delupload", &DeleteUploadHandler{SessionStore: sessionStore, DBStore: dbStore, SecureCookie: secureCookie, UploadStore: fileStore}) apiRouter.Post("/api/startsession", &StartSessionHandler{SessionStore: sessionStore, DBStore: dbStore, SecureCookie: secureCookie}) apiRouter.Post("/api/stopsession", &StopSessionHandler{SessionStore: sessionStore, DBStore: dbStore, SecureCookie: secureCookie, RedisAddr: options.RedisAddr}) apiRouter.Post("/api/delsession", &DeleteSessionHandler{SessionStore: sessionStore, DBStore: dbStore, SecureCookie: secureCookie}) apiRouter.Get("/api/getsessions", &GetSessionsHandler{SessionStore: sessionStore, DBStore: dbStore}) apiRouter.Get("/api/sessioninfo/:id", &GetSessionInfoHandler{SessionStore: sessionStore, DBStore: dbStore}) mux.Handle("/api/ws", websocket.Handler(func(c *websocket.Conn) { WebsocketHandler(c, dbStore, sessionStore, options.RedisAddr) })) // let all API things go through autogzip. mux.Handle("/api/", autogzip.Handle(apiRouter)) // deliver index.html through autogzip. deliverIndex := autogzip.HandleFunc(func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, path.Join(options.HtdocsDir, "index.html")) }) // deliver index.html for AngularJS routes. mux.HandleFunc("/v/", deliverIndex) mux.HandleFunc("/s/", deliverIndex) // XXX make sure that files from /userdata/ don't go through autogzip. That messes up // the load progress of pdf.js. mux.Handle("/userdata/", http.StripPrefix("/userdata/", fileStore)) mux.HandleFunc("/contact", deliverIndex) mux.HandleFunc("/tos", deliverIndex) mux.HandleFunc("/settings", deliverIndex) // deliver static files from htdocs, autogzip'd. mux.Handle("/", autogzip.Handle(http.FileServer(http.Dir(options.HtdocsDir)))) handler := http.Handler(mux) if options.AccessLog { handler = Logger(handler) } l, ppid, err := goagain.GetEnvs() if err != nil { StatCount("satsuma start", 1) xlog.Debugf("Starting HTTP server on %s", options.Addr) laddr, err := net.ResolveTCPAddr("tcp", options.Addr) if err != nil { xlog.Fatalf("net.ResolveTCPAddr failed: %v", err) } l, err = net.ListenTCP("tcp", laddr) if err != nil { xlog.Fatalf("net.ListenTCP failed: %v", err) } go http.Serve(l, handler) } else { StatCount("satsuma reload", 1) go http.Serve(l, handler) if err := goagain.KillParent(ppid); err != nil { xlog.Fatalf("goagain.KillParent failed: %v", err) } } if err := goagain.AwaitSignals(l); nil != err { xlog.Fatalf("goagain.AwaitSignals failed: %v", err) } if err := l.Close(); err != nil { xlog.Fatalf("Closing listening socket failed: %v", err) } // TODO: make sure all requests are finished before exiting, e.g. through a common waitgroup. time.Sleep(1 * time.Second) }
func (a *App) goAgainListenAndServe(listenNet, listenAddr string) { l, ppid, err := goagain.GetEnvs() if err != nil { a.Info("No parent - starting listener on %s:%s", listenNet, listenAddr) // No parent, start our own listener l, err = net.Listen(listenNet, listenAddr) a.Debug("Listener is %v err is %v", l, err) if err != nil { a.Fatalln(err) } } else { // We have a parent, and we're now listening. Tell them to shut down. a.Info("Child taking over from graceful parent. Killing ppid %d\n", ppid) if err := goagain.KillParent(ppid); nil != err { a.Fatalln(err) } } go func() { a.Serve(l) }() // Block the main goroutine awaiting signals. if err := goagain.AwaitSignals(l); nil != err { a.Fatalln(err) } a.Error("Signal received - starting exit or restart") // We're the parent. Our child has taken over the listening duties. We can close // off our listener and drain pending requests. l.Close() waitSecs, _ := a.Cfg.GetInt("gop", "graceful_wait_secs", 60) timeoutChan := time.After(time.Second * time.Duration(waitSecs)) tickMillis, _ := a.Cfg.GetInt("gop", "graceful_poll_msecs", 500) tickChan := time.Tick(time.Millisecond * time.Duration(tickMillis)) waiting := true for waiting { select { case <-timeoutChan: { a.Errorf("Graceful restart timed out after %d seconds - being less graceful and exiting", waitSecs) waiting = false } case <-tickChan: { appStats := a.GetStats() if appStats.currentReqs-appStats.currentWSReqs >= 0 { a.Error("Graceful restart - no pending non-ws requests - time to die") waiting = false } else { a.Info("Graceful restart - tick still have %d pending reqs", appStats.currentReqs) } } } } appStats := a.GetStats() a.Info("Graceful restart/exit - with %d pending reqs", appStats.currentReqs) }
func main() { flag.Usage = usage flag.Parse() runtime.SetBlockProfileRate(*blockProfileRate) runtime.MemProfileRate = *memProfileRate // Default to strict validation config.Legacy_metric_validation.Level = m20.Strict config_file = "/etc/carbon-relay-ng.ini" if 1 == flag.NArg() { config_file = flag.Arg(0) } if _, err := toml.DecodeFile(config_file, &config); err != nil { log.Error("Cannot use config file '%s':\n", config_file) log.Error(err.Error()) usage() return } //runtime.SetBlockProfileRate(1) // to enable block profiling. in my experience, adds 35% overhead. levels := map[string]logging.Level{ "critical": logging.CRITICAL, "error": logging.ERROR, "warning": logging.WARNING, "notice": logging.NOTICE, "info": logging.INFO, "debug": logging.DEBUG, } level, ok := levels[config.Log_level] if !ok { log.Error("unrecognized log level '%s'\n", config.Log_level) return } logging.SetLevel(level, "carbon-relay-ng") if *cpuprofile != "" { f, err := os.Create(*cpuprofile) if err != nil { log.Fatal(err) } pprof.StartCPUProfile(f) defer pprof.StopCPUProfile() } if len(config.Instance) == 0 { log.Error("instance identifier cannot be empty") os.Exit(1) } runtime.GOMAXPROCS(config.max_procs) instance = config.Instance expvar.NewString("instance").Set(instance) expvar.NewString("service").Set(service) log.Notice("===== carbon-relay-ng instance '%s' starting. =====\n", instance) numIn = Counter("unit=Metric.direction=in") numInvalid = Counter("unit=Err.type=invalid") if config.Instrumentation.Graphite_addr != "" { addr, err := net.ResolveTCPAddr("tcp", config.Instrumentation.Graphite_addr) if err != nil { log.Fatal(err) } go metrics.Graphite(metrics.DefaultRegistry, time.Duration(config.Instrumentation.Graphite_interval)*time.Millisecond, "", addr) } log.Notice("creating routing table...") maxAge, err := time.ParseDuration(config.Bad_metrics_max_age) if err != nil { log.Error("could not parse badMetrics max age") log.Error(err.Error()) os.Exit(1) } badMetrics = badmetrics.New(maxAge) table = NewTable(config.Spool_dir) log.Notice("initializing routing table...") for i, cmd := range config.Init { log.Notice("applying: %s", cmd) err = applyCommand(table, cmd) if err != nil { log.Error("could not apply init cmd #%d", i+1) log.Error(err.Error()) os.Exit(1) } } tablePrinted := table.Print() log.Notice("===========================") log.Notice("========== TABLE ==========") log.Notice("===========================") for _, line := range strings.Split(tablePrinted, "\n") { log.Notice(line) } // Follow the goagain protocol, <https://github.com/rcrowley/goagain>. l, ppid, err := goagain.GetEnvs() if nil != err { laddr, err := net.ResolveTCPAddr("tcp", config.Listen_addr) if nil != err { log.Error(err.Error()) os.Exit(1) } l, err = net.ListenTCP("tcp", laddr) if nil != err { log.Error(err.Error()) os.Exit(1) } log.Notice("listening on %v/tcp", laddr) go accept(l.(*net.TCPListener), config) } else { log.Notice("resuming listening on %v/tcp", l.Addr()) go accept(l.(*net.TCPListener), config) if err := goagain.KillParent(ppid); nil != err { log.Error(err.Error()) os.Exit(1) } for { err := syscall.Kill(ppid, 0) if err != nil { break } time.Sleep(10 * time.Millisecond) } } udp_addr, err := net.ResolveUDPAddr("udp", config.Listen_addr) if nil != err { log.Error(err.Error()) os.Exit(1) } udp_conn, err := net.ListenUDP("udp", udp_addr) if nil != err { log.Error(err.Error()) os.Exit(1) } log.Notice("listening on %v/udp", udp_addr) go handle(udp_conn, config) if config.Pid_file != "" { f, err := os.Create(config.Pid_file) if err != nil { fmt.Println("error creating pidfile:", err.Error()) os.Exit(1) } _, err = f.Write([]byte(strconv.Itoa(os.Getpid()))) if err != nil { fmt.Println("error writing to pidfile:", err.Error()) os.Exit(1) } f.Close() } if config.Admin_addr != "" { go func() { err := adminListener(config.Admin_addr) if err != nil { fmt.Println("Error listening:", err.Error()) os.Exit(1) } }() } if config.Http_addr != "" { go HttpListener(config.Http_addr, table) } if err := goagain.AwaitSignals(l); nil != err { log.Error(err.Error()) os.Exit(1) } }