summaryrefslogtreecommitdiffstats
path: root/modules/graceful/manager_unix.go
diff options
context:
space:
mode:
Diffstat (limited to 'modules/graceful/manager_unix.go')
-rw-r--r--modules/graceful/manager_unix.go201
1 files changed, 201 insertions, 0 deletions
diff --git a/modules/graceful/manager_unix.go b/modules/graceful/manager_unix.go
new file mode 100644
index 00000000..931b0f1b
--- /dev/null
+++ b/modules/graceful/manager_unix.go
@@ -0,0 +1,201 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+//go:build !windows
+
+package graceful
+
+import (
+ "context"
+ "errors"
+ "os"
+ "os/signal"
+ "runtime/pprof"
+ "strconv"
+ "syscall"
+ "time"
+
+ "code.gitea.io/gitea/modules/graceful/releasereopen"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/process"
+ "code.gitea.io/gitea/modules/setting"
+)
+
+func pidMsg() systemdNotifyMsg {
+ return systemdNotifyMsg("MAINPID=" + strconv.Itoa(os.Getpid()))
+}
+
+// Notify systemd of status via the notify protocol
+func (g *Manager) notify(msg systemdNotifyMsg) {
+ conn, err := getNotifySocket()
+ if err != nil {
+ // the err is logged in getNotifySocket
+ return
+ }
+ if conn == nil {
+ return
+ }
+ defer conn.Close()
+
+ if _, err = conn.Write([]byte(msg)); err != nil {
+ log.Warn("Failed to notify NOTIFY_SOCKET: %v", err)
+ return
+ }
+}
+
+func (g *Manager) start() {
+ // Now label this and all goroutines created by this goroutine with the gracefulLifecycle manager
+ pprof.SetGoroutineLabels(g.managerCtx)
+ defer pprof.SetGoroutineLabels(g.ctx)
+
+ g.isChild = len(os.Getenv(listenFDsEnv)) > 0 && os.Getppid() > 1
+
+ g.notify(statusMsg("Starting Gitea"))
+ g.notify(pidMsg())
+ go g.handleSignals(g.managerCtx)
+
+ // Handle clean up of unused provided listeners and delayed start-up
+ startupDone := make(chan struct{})
+ go func() {
+ defer func() {
+ close(startupDone)
+ // Close the unused listeners
+ closeProvidedListeners()
+ }()
+ // Wait for all servers to be created
+ g.createServerCond.L.Lock()
+ for {
+ if g.createdServer >= numberOfServersToCreate {
+ g.createServerCond.L.Unlock()
+ g.notify(readyMsg)
+ return
+ }
+ select {
+ case <-g.IsShutdown():
+ g.createServerCond.L.Unlock()
+ return
+ default:
+ }
+ g.createServerCond.Wait()
+ }
+ }()
+ if setting.StartupTimeout > 0 {
+ go func() {
+ select {
+ case <-startupDone:
+ return
+ case <-g.IsShutdown():
+ g.createServerCond.Signal()
+ return
+ case <-time.After(setting.StartupTimeout):
+ log.Error("Startup took too long! Shutting down")
+ g.notify(statusMsg("Startup took too long! Shutting down"))
+ g.notify(stoppingMsg)
+ g.doShutdown()
+ }
+ }()
+ }
+}
+
+func (g *Manager) handleSignals(ctx context.Context) {
+ ctx, _, finished := process.GetManager().AddTypedContext(ctx, "Graceful: HandleSignals", process.SystemProcessType, true)
+ defer finished()
+
+ signalChannel := make(chan os.Signal, 1)
+
+ signal.Notify(
+ signalChannel,
+ syscall.SIGHUP,
+ syscall.SIGUSR1,
+ syscall.SIGUSR2,
+ syscall.SIGINT,
+ syscall.SIGTERM,
+ syscall.SIGTSTP,
+ )
+
+ watchdogTimeout := getWatchdogTimeout()
+ t := &time.Ticker{}
+ if watchdogTimeout != 0 {
+ g.notify(watchdogMsg)
+ t = time.NewTicker(watchdogTimeout / 2)
+ }
+
+ pid := syscall.Getpid()
+ for {
+ select {
+ case sig := <-signalChannel:
+ switch sig {
+ case syscall.SIGHUP:
+ log.Info("PID: %d. Received SIGHUP. Attempting GracefulRestart...", pid)
+ g.DoGracefulRestart()
+ case syscall.SIGUSR1:
+ log.Warn("PID %d. Received SIGUSR1. Releasing and reopening logs", pid)
+ g.notify(statusMsg("Releasing and reopening logs"))
+ if err := releasereopen.GetManager().ReleaseReopen(); err != nil {
+ log.Error("Error whilst releasing and reopening logs: %v", err)
+ }
+ case syscall.SIGUSR2:
+ log.Warn("PID %d. Received SIGUSR2. Hammering...", pid)
+ g.DoImmediateHammer()
+ case syscall.SIGINT:
+ log.Warn("PID %d. Received SIGINT. Shutting down...", pid)
+ g.DoGracefulShutdown()
+ case syscall.SIGTERM:
+ log.Warn("PID %d. Received SIGTERM. Shutting down...", pid)
+ g.DoGracefulShutdown()
+ case syscall.SIGTSTP:
+ log.Info("PID %d. Received SIGTSTP.", pid)
+ default:
+ log.Info("PID %d. Received %v.", pid, sig)
+ }
+ case <-t.C:
+ g.notify(watchdogMsg)
+ case <-ctx.Done():
+ log.Warn("PID: %d. Background context for manager closed - %v - Shutting down...", pid, ctx.Err())
+ g.DoGracefulShutdown()
+ return
+ }
+ }
+}
+
+func (g *Manager) doFork() error {
+ g.lock.Lock()
+ if g.forked {
+ g.lock.Unlock()
+ return errors.New("another process already forked. Ignoring this one")
+ }
+ g.forked = true
+ g.lock.Unlock()
+
+ g.notify(reloadingMsg)
+
+ // We need to move the file logs to append pids
+ setting.RestartLogsWithPIDSuffix()
+
+ _, err := RestartProcess()
+
+ return err
+}
+
+// DoGracefulRestart causes a graceful restart
+func (g *Manager) DoGracefulRestart() {
+ if setting.GracefulRestartable {
+ log.Info("PID: %d. Forking...", os.Getpid())
+ err := g.doFork()
+ if err != nil {
+ if err.Error() == "another process already forked. Ignoring this one" {
+ g.DoImmediateHammer()
+ } else {
+ log.Error("Error whilst forking from PID: %d : %v", os.Getpid(), err)
+ }
+ }
+ // doFork calls RestartProcess which starts a new Gitea process, so this parent process needs to exit
+ // Otherwise some resources (eg: leveldb lock) will be held by this parent process and the new process will fail to start
+ log.Info("PID: %d. Shutting down after forking ...", os.Getpid())
+ g.doShutdown()
+ } else {
+ log.Info("PID: %d. Not set restartable. Shutting down...", os.Getpid())
+ g.notify(stoppingMsg)
+ g.doShutdown()
+ }
+}