diff --git a/cmd/web.go b/cmd/web.go index f0e1b16e7fe12..86718fd6efd4c 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -209,5 +209,5 @@ func runWeb(ctx *cli.Context) error { <-graceful.GetManager().Done() log.Info("PID: %d Gitea Web Finished", os.Getpid()) log.Close() - return nil + return graceful.GetManager().DoFinalRestartIfNeeded() } diff --git a/modules/graceful/manager.go b/modules/graceful/manager.go index 6b134e7d0cceb..a5c399f86c52c 100644 --- a/modules/graceful/manager.go +++ b/modules/graceful/manager.go @@ -6,6 +6,7 @@ package graceful import ( "context" + "fmt" "sync" "time" @@ -312,6 +313,22 @@ func (g *Manager) InformCleanup() { g.createServerWaitGroup.Done() } +// DoFinalRestartIfNeeded will restart the process if needed +func (g *Manager) DoFinalRestartIfNeeded() error { + select { + case <-g.done: + default: + return fmt.Errorf("This should only be called once the manager is done") + } + g.lock.Lock() + defer g.lock.Unlock() + if g.needsRestart { + _, err := RestartProcessNoListeners() + return err + } + return nil +} + // Done allows the manager to be viewed as a context.Context, it returns a channel that is closed when the server is finished terminating func (g *Manager) Done() <-chan struct{} { return g.done diff --git a/modules/graceful/manager_unix.go b/modules/graceful/manager_unix.go index 540974454c34c..99220b2418a27 100644 --- a/modules/graceful/manager_unix.go +++ b/modules/graceful/manager_unix.go @@ -23,6 +23,7 @@ import ( type Manager struct { isChild bool forked bool + needsRestart bool lock *sync.RWMutex state state shutdown chan struct{} @@ -169,6 +170,23 @@ func (g *Manager) DoGracefulRestart() { } } +// DoForcedRestart causes a graceful restart if can otherwise graceful shutdown and restart at end of web.go +func (g *Manager) DoForcedRestart() { + if setting.GracefulRestartable { + log.Info("PID: %d. Forking...", os.Getpid()) + err := g.doFork() + if err != nil && err.Error() != "another process already forked. Ignoring this one" { + log.Error("Error whilst forking from PID: %d : %v", os.Getpid(), err) + } + } else { + log.Info("PID: %d. Not set graceful restartable. Shutting down and will attempt restart at the end of web.go ...", os.Getpid()) + g.lock.Lock() + g.needsRestart = true + g.lock.Unlock() + g.doShutdown() + } +} + // DoImmediateHammer causes an immediate hammer func (g *Manager) DoImmediateHammer() { g.doHammerTime(0 * time.Second) diff --git a/modules/graceful/manager_windows.go b/modules/graceful/manager_windows.go index d412e94f9ac54..eff195030ce02 100644 --- a/modules/graceful/manager_windows.go +++ b/modules/graceful/manager_windows.go @@ -34,6 +34,7 @@ const ( type Manager struct { ctx context.Context isChild bool + needsRestart bool lock *sync.RWMutex state state shutdown chan struct{} @@ -175,6 +176,14 @@ func (g *Manager) DoGracefulShutdown() { } } +// DoForcedRestart causes a graceful shutdown and restart during Terminate phase +func (g *Manager) DoForcedRestart() { + g.lock.Lock() + g.needsRestart = true + g.lock.Unlock() + g.DoGracefulShutdown() +} + // RegisterServer registers the running of a listening server. // Any call to RegisterServer must be matched by a call to ServerDone func (g *Manager) RegisterServer() { diff --git a/modules/graceful/net_unix.go b/modules/graceful/net_unix.go index 2dc714955e1ed..d776282822d25 100644 --- a/modules/graceful/net_unix.go +++ b/modules/graceful/net_unix.go @@ -25,10 +25,6 @@ const ( startFD = 3 ) -// In order to keep the working directory the same as when we started we record -// it at startup. -var originalWD, _ = os.Getwd() - var ( once = sync.Once{} mutex = sync.Mutex{} diff --git a/modules/graceful/restart.go b/modules/graceful/restart.go new file mode 100644 index 0000000000000..a52c39d6207f3 --- /dev/null +++ b/modules/graceful/restart.go @@ -0,0 +1,33 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package graceful + +import ( + "os" + "os/exec" +) + +// In order to keep the working directory the same as when we started we record +// it at startup. +var originalWD, _ = os.Getwd() + +// RestartProcessNoListeners starts a new process without passing it the active listeners. +func RestartProcessNoListeners() (int, error) { + // Use the original binary location. This works with symlinks such that if + // the file it points to has been changed we will use the updated symlink. + argv0, err := exec.LookPath(os.Args[0]) + if err != nil { + return 0, err + } + process, err := os.StartProcess(argv0, os.Args, &os.ProcAttr{ + Dir: originalWD, + Env: os.Environ(), + Files: []*os.File{os.Stdin, os.Stdout, os.Stderr}, + }) + if err != nil { + return 0, err + } + return process.Pid, nil +} diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 94d7ab27fb1b5..fb7d7e666c8e6 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -191,6 +191,7 @@ run_user_not_match = The 'run as' username is not the current username: %s -> %s save_config_failed = Failed to save configuration: %v invalid_admin_setting = Administrator account setting is invalid: %v install_success = Welcome! Thank you for choosing Gitea. Have fun and take care! +install_restart = Gitea will now attempt to restart. You will be redirected to User Login in 5 seconds. invalid_log_root_path = The log path is invalid: %v default_keep_email_private = Hide Email Addresses by Default default_keep_email_private_popup = Hide email addresses of new user accounts by default. diff --git a/routers/install.go b/routers/install.go index 9eda18f941ba6..aa41a63275d72 100644 --- a/routers/install.go +++ b/routers/install.go @@ -27,7 +27,8 @@ import ( const ( // tplInstall template for installation page - tplInstall base.TplName = "install" + tplInstall base.TplName = "install" + tplInstallSuccess base.TplName = "install_success" ) // InstallInit prepare for rendering installation page @@ -393,12 +394,7 @@ func InstallPost(ctx *context.Context, form auth.InstallForm) { } log.Info("First-time run install finished!") - // FIXME: This isn't really enough to completely take account of new configuration - // We should really be restarting: - // - On windows this is probably just a simple restart - // - On linux we can't just use graceful.RestartProcess() everything that was passed in on LISTEN_FDS - // (active or not) needs to be passed out and everything new passed out too. - // This means we need to prevent the cleanup goroutine from running prior to the second GlobalInit - ctx.Flash.Success(ctx.Tr("install.install_success")) - ctx.Redirect(form.AppURL + "user/login") + ctx.Data["RedirectURL"] = form.AppURL + "user/login" + ctx.HTML(200, tplInstallSuccess) + graceful.GetManager().DoForcedRestart() } diff --git a/routers/private/manager_unix.go b/routers/private/manager_unix.go index ec5e97605981d..70b35fceb6965 100644 --- a/routers/private/manager_unix.go +++ b/routers/private/manager_unix.go @@ -14,9 +14,9 @@ import ( "gitea.com/macaron/macaron" ) -// Restart causes the server to perform a graceful restart +// Restart causes the server to perform a graceful restart if possible but otherwise a graceful shutdown and restart func Restart(ctx *macaron.Context) { - graceful.GetManager().DoGracefulRestart() + graceful.GetManager().DoForcedRestart() ctx.PlainText(http.StatusOK, []byte("success")) } diff --git a/routers/private/manager_windows.go b/routers/private/manager_windows.go index ac840a9d81ce8..1ae1437120042 100644 --- a/routers/private/manager_windows.go +++ b/routers/private/manager_windows.go @@ -16,9 +16,8 @@ import ( // Restart is not implemented for Windows based servers as they can't fork func Restart(ctx *macaron.Context) { - ctx.JSON(http.StatusNotImplemented, map[string]interface{}{ - "err": "windows servers cannot be gracefully restarted - shutdown and restart manually", - }) + graceful.GetManager().DoForcedRestart() + ctx.PlainText(http.StatusOK, []byte("success")) } // Shutdown causes the server to perform a graceful shutdown diff --git a/templates/install_success.tmpl b/templates/install_success.tmpl new file mode 100644 index 0000000000000..912b7875fdc8b --- /dev/null +++ b/templates/install_success.tmpl @@ -0,0 +1,19 @@ +{{template "base/head" .}} +
{{.i18n.Tr "install.install_success"}}
+{{.i18n.Tr "install.install_restart" (.RedirectURL|Escape)}}
+ +