Skip to content

[WIP] Restart after install page #12696

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
17 changes: 17 additions & 0 deletions modules/graceful/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package graceful

import (
"context"
"fmt"
"sync"
"time"

Expand Down Expand Up @@ -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
Expand Down
18 changes: 18 additions & 0 deletions modules/graceful/manager_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
type Manager struct {
isChild bool
forked bool
needsRestart bool
lock *sync.RWMutex
state state
shutdown chan struct{}
Expand Down Expand Up @@ -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)
Expand Down
9 changes: 9 additions & 0 deletions modules/graceful/manager_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const (
type Manager struct {
ctx context.Context
isChild bool
needsRestart bool
lock *sync.RWMutex
state state
shutdown chan struct{}
Expand Down Expand Up @@ -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() {
Expand Down
4 changes: 0 additions & 4 deletions modules/graceful/net_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
Expand Down
33 changes: 33 additions & 0 deletions modules/graceful/restart.go
Original file line number Diff line number Diff line change
@@ -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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

modules\graceful\restart.go:21:10: undefined: originalWD

Env: os.Environ(),
Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},
})
if err != nil {
return 0, err
}
return process.Pid, nil
}
1 change: 1 addition & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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 <a href="%s">User Login</a> 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.
Expand Down
14 changes: 5 additions & 9 deletions routers/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
}
4 changes: 2 additions & 2 deletions routers/private/manager_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"))

}
Expand Down
5 changes: 2 additions & 3 deletions routers/private/manager_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions templates/install_success.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{{template "base/head" .}}
<div class="install">
<div class="ui middle very relaxed page grid">
<div class="sixteen wide center aligned centered column">
<h3 class="ui top attached header">
{{.i18n.Tr "install.title"}}
</h3>
<p>{{.i18n.Tr "install.install_success"}}</p>
<p>{{.i18n.Tr "install.install_restart" (.RedirectURL|Escape)}}</p>
<script type="text/javascript">
setTimeout(function () {
//Redirect with JavaScript
window.location.href= '{{.RedirectURL}}';
}, 5000);
</script>
</div>
</div>
</div>
{{template "base/footer" .}}