Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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
29 changes: 29 additions & 0 deletions modules/graceful/restart.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// 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"
)

// 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" .}}