Skip to content

Implement systemd-notify protocol #21151

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

Merged
merged 16 commits into from
May 15, 2023
Merged
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 contrib/systemd/gitea.service
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ After=network.target
#LimitMEMLOCK=infinity
#LimitNOFILE=65535
RestartSec=2s
Type=simple
Type=notify
User=git
Group=git
WorkingDirectory=/var/lib/gitea/
Expand Down
57 changes: 55 additions & 2 deletions modules/graceful/manager_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"os"
"os/signal"
"runtime/pprof"
"strconv"
"sync"
"syscall"
"time"
Expand Down Expand Up @@ -46,14 +47,48 @@ type Manager struct {

func newGracefulManager(ctx context.Context) *Manager {
manager := &Manager{
isChild: len(os.Getenv(listenFDs)) > 0 && os.Getppid() > 1,
isChild: len(os.Getenv(listenFDsEnv)) > 0 && os.Getppid() > 1,
lock: &sync.RWMutex{},
}
manager.createServerWaitGroup.Add(numberOfServersToCreate)
manager.start(ctx)
return manager
}

type systemdNotifyMsg string

const (
readyMsg systemdNotifyMsg = "READY=1"
stoppingMsg systemdNotifyMsg = "STOPPING=1"
reloadingMsg systemdNotifyMsg = "RELOADING=1"
)

func statusMsg(msg string) systemdNotifyMsg {
return systemdNotifyMsg("STATUS=" + msg)
}

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(ctx context.Context) {
// Make contexts
g.terminateCtx, g.terminateCtxCancel = context.WithCancel(ctx)
Expand All @@ -73,6 +108,8 @@ func (g *Manager) start(ctx context.Context) {

// Set the running state & handle signals
g.setState(stateRunning)
g.notify(statusMsg("Starting Gitea"))
g.notify(pidMsg())
go g.handleSignals(g.managerCtx)

// Handle clean up of unused provided listeners and delayed start-up
Expand All @@ -90,6 +127,7 @@ func (g *Manager) start(ctx context.Context) {
go func() {
select {
case <-startupDone:
g.notify(readyMsg)
return
case <-g.IsShutdown():
func() {
Expand All @@ -105,6 +143,8 @@ func (g *Manager) start(ctx context.Context) {
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()
}
}()
Expand Down Expand Up @@ -137,6 +177,7 @@ func (g *Manager) handleSignals(ctx context.Context) {
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 := log.ReleaseReopen(); err != nil {
log.Error("Error whilst releasing and reopening logs: %v", err)
}
Expand Down Expand Up @@ -170,6 +211,9 @@ func (g *Manager) doFork() error {
}
g.forked = true
g.lock.Unlock()

g.notify(reloadingMsg)

// We need to move the file logs to append pids
setting.RestartLogsWithPIDSuffix()

Expand All @@ -192,18 +236,27 @@ func (g *Manager) DoGracefulRestart() {
}
} else {
log.Info("PID: %d. Not set restartable. Shutting down...", os.Getpid())

g.notify(stoppingMsg)
g.doShutdown()
}
}

// DoImmediateHammer causes an immediate hammer
func (g *Manager) DoImmediateHammer() {
g.notify(statusMsg("Sending immediate hammer"))
g.doHammerTime(0 * time.Second)
}

// DoGracefulShutdown causes a graceful shutdown
func (g *Manager) DoGracefulShutdown() {
g.lock.Lock()
if !g.forked {
g.lock.Unlock()
g.notify(stoppingMsg)
} else {
g.lock.Unlock()
g.notify(statusMsg("shutting down after fork"))
}
g.doShutdown()
}

Expand Down
51 changes: 45 additions & 6 deletions modules/graceful/net_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ import (
)

const (
listenFDs = "LISTEN_FDS"
startFD = 3
unlinkFDs = "GITEA_UNLINK_FDS"
listenFDsEnv = "LISTEN_FDS"
startFD = 3
unlinkFDsEnv = "GITEA_UNLINK_FDS"

notifySocketEnv = "NOTIFY_SOCKET"
)

// In order to keep the working directory the same as when we started we record
Expand All @@ -38,6 +40,8 @@ var (
activeListenersToUnlink = []bool{}
providedListeners = []net.Listener{}
activeListeners = []net.Listener{}

notifySocketAddr string
)

func getProvidedFDs() (savedErr error) {
Expand All @@ -46,17 +50,17 @@ func getProvidedFDs() (savedErr error) {
mutex.Lock()
defer mutex.Unlock()

numFDs := os.Getenv(listenFDs)
numFDs := os.Getenv(listenFDsEnv)
if numFDs == "" {
return
}
n, err := strconv.Atoi(numFDs)
if err != nil {
savedErr = fmt.Errorf("%s is not a number: %s. Err: %v", listenFDs, numFDs, err)
savedErr = fmt.Errorf("%s is not a number: %s. Err: %v", listenFDsEnv, numFDs, err)
return
}

fdsToUnlinkStr := strings.Split(os.Getenv(unlinkFDs), ",")
fdsToUnlinkStr := strings.Split(os.Getenv(unlinkFDsEnv), ",")
providedListenersToUnlink = make([]bool, n)
for _, fdStr := range fdsToUnlinkStr {
i, err := strconv.Atoi(fdStr)
Expand Down Expand Up @@ -84,6 +88,18 @@ func getProvidedFDs() (savedErr error) {
savedErr = fmt.Errorf("Error getting provided socket fd %d: %v", i, err)
return
}

notifySocketAddr = os.Getenv(notifySocketEnv)
if notifySocketAddr != "" {
log.Debug("Systemd Notify Socket provided: %s", notifySocketAddr)
savedErr = os.Unsetenv(notifySocketEnv)
if savedErr != nil {
log.Warn("Unable to Unset the NOTIFY_SOCKET environment variable: %v", savedErr)
return
}
} else {
log.Trace("No Systemd Notify Socket provided")
}
})
return savedErr
}
Expand Down Expand Up @@ -255,3 +271,26 @@ func getActiveListenersToUnlink() []bool {
copy(listenersToUnlink, activeListenersToUnlink)
return listenersToUnlink
}

func getNotifySocket() (*net.UnixConn, error) {
if err := getProvidedFDs(); err != nil {
return nil, err
}

if notifySocketAddr == "" {
return nil, nil
}

socketAddr := &net.UnixAddr{
Name: notifySocketAddr,
Net: "unixgram",
}

notifySocket, err := net.DialUnix(socketAddr.Net, nil, socketAddr)
if err != nil {
log.Warn("failed to dial NOTIFY_SOCKET %s: %v", socketAddr, err)
return nil, err
}

return notifySocket, nil
}
10 changes: 7 additions & 3 deletions modules/graceful/restart_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,15 @@ func RestartProcess() (int, error) {
// Pass on the environment and replace the old count key with the new one.
var env []string
for _, v := range os.Environ() {
if !strings.HasPrefix(v, listenFDs+"=") {
if !strings.HasPrefix(v, listenFDsEnv+"=") {
env = append(env, v)
}
}
env = append(env, fmt.Sprintf("%s=%d", listenFDs, len(listeners)))
env = append(env, fmt.Sprintf("%s=%d", listenFDsEnv, len(listeners)))

if notifySocketAddr != "" {
env = append(env, fmt.Sprintf("%s=%s", notifySocketEnv, notifySocketAddr))
}

sb := &strings.Builder{}
for i, unlink := range getActiveListenersToUnlink() {
Expand All @@ -87,7 +91,7 @@ func RestartProcess() (int, error) {
unlinkStr := sb.String()
if len(unlinkStr) > 0 {
unlinkStr = unlinkStr[:len(unlinkStr)-1]
env = append(env, fmt.Sprintf("%s=%s", unlinkFDs, unlinkStr))
env = append(env, fmt.Sprintf("%s=%s", unlinkFDsEnv, unlinkStr))
}

allFiles := append([]*os.File{os.Stdin, os.Stdout, os.Stderr}, files...)
Expand Down