Skip to content

Commit 03ef142

Browse files
clarfontheyzeripath
authored andcommitted
Implement systemd-notify protocol
This PR adds support for the systemd notify protocol. Several status messagess are provided. We should likely add a common notify/status message for graceful. Replaces go-gitea#21140 Signed-off-by: Andrew Thornton <[email protected]>
1 parent ec82a24 commit 03ef142

File tree

4 files changed

+108
-12
lines changed

4 files changed

+108
-12
lines changed

contrib/systemd/gitea.service

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ After=network.target
5656
#LimitMEMLOCK=infinity
5757
#LimitNOFILE=65535
5858
RestartSec=2s
59-
Type=simple
59+
Type=notify
6060
User=git
6161
Group=git
6262
WorkingDirectory=/var/lib/gitea/

modules/graceful/manager_unix.go

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"os"
1313
"os/signal"
1414
"runtime/pprof"
15+
"strconv"
1516
"sync"
1617
"syscall"
1718
"time"
@@ -46,14 +47,48 @@ type Manager struct {
4647

4748
func newGracefulManager(ctx context.Context) *Manager {
4849
manager := &Manager{
49-
isChild: len(os.Getenv(listenFDs)) > 0 && os.Getppid() > 1,
50+
isChild: len(os.Getenv(listenFDsEnv)) > 0 && os.Getppid() > 1,
5051
lock: &sync.RWMutex{},
5152
}
5253
manager.createServerWaitGroup.Add(numberOfServersToCreate)
5354
manager.start(ctx)
5455
return manager
5556
}
5657

58+
type systemdNotifyMsg string
59+
60+
const (
61+
readyMsg systemdNotifyMsg = "READY=1"
62+
stoppingMsg systemdNotifyMsg = "STOPPING=1"
63+
reloadingMsg systemdNotifyMsg = "RELOADING=1"
64+
)
65+
66+
func statusMsg(msg string) systemdNotifyMsg {
67+
return systemdNotifyMsg("STATUS=" + msg)
68+
}
69+
70+
func pidMsg() systemdNotifyMsg {
71+
return systemdNotifyMsg("MAINPID=" + strconv.Itoa(os.Getpid()))
72+
}
73+
74+
// Notify systemd of status via the notify protocol
75+
func (g *Manager) notify(msg systemdNotifyMsg) {
76+
conn, err := getNotifySocket()
77+
if err != nil {
78+
// the err is logged in getNotifySocket
79+
return
80+
}
81+
if conn == nil {
82+
return
83+
}
84+
defer conn.Close()
85+
86+
if _, err = conn.Write([]byte(msg)); err != nil {
87+
log.Warn("Failed to notify NOTIFY_SOCKET: %v", err)
88+
return
89+
}
90+
}
91+
5792
func (g *Manager) start(ctx context.Context) {
5893
// Make contexts
5994
g.terminateCtx, g.terminateCtxCancel = context.WithCancel(ctx)
@@ -73,6 +108,8 @@ func (g *Manager) start(ctx context.Context) {
73108

74109
// Set the running state & handle signals
75110
g.setState(stateRunning)
111+
g.notify(statusMsg("Starting Gitea"))
112+
g.notify(pidMsg())
76113
go g.handleSignals(g.managerCtx)
77114

78115
// Handle clean up of unused provided listeners and delayed start-up
@@ -90,6 +127,7 @@ func (g *Manager) start(ctx context.Context) {
90127
go func() {
91128
select {
92129
case <-startupDone:
130+
g.notify(readyMsg)
93131
return
94132
case <-g.IsShutdown():
95133
func() {
@@ -105,6 +143,8 @@ func (g *Manager) start(ctx context.Context) {
105143
return
106144
case <-time.After(setting.StartupTimeout):
107145
log.Error("Startup took too long! Shutting down")
146+
g.notify(statusMsg("Startup took too long! Shutting down"))
147+
g.notify(stoppingMsg)
108148
g.doShutdown()
109149
}
110150
}()
@@ -137,6 +177,7 @@ func (g *Manager) handleSignals(ctx context.Context) {
137177
g.DoGracefulRestart()
138178
case syscall.SIGUSR1:
139179
log.Warn("PID %d. Received SIGUSR1. Releasing and reopening logs", pid)
180+
g.notify(statusMsg("Releasing and reopening logs"))
140181
if err := log.ReleaseReopen(); err != nil {
141182
log.Error("Error whilst releasing and reopening logs: %v", err)
142183
}
@@ -170,6 +211,9 @@ func (g *Manager) doFork() error {
170211
}
171212
g.forked = true
172213
g.lock.Unlock()
214+
215+
g.notify(reloadingMsg)
216+
173217
// We need to move the file logs to append pids
174218
setting.RestartLogsWithPIDSuffix()
175219

@@ -192,18 +236,27 @@ func (g *Manager) DoGracefulRestart() {
192236
}
193237
} else {
194238
log.Info("PID: %d. Not set restartable. Shutting down...", os.Getpid())
195-
239+
g.notify(stoppingMsg)
196240
g.doShutdown()
197241
}
198242
}
199243

200244
// DoImmediateHammer causes an immediate hammer
201245
func (g *Manager) DoImmediateHammer() {
246+
g.notify(statusMsg("Sending immediate hammer"))
202247
g.doHammerTime(0 * time.Second)
203248
}
204249

205250
// DoGracefulShutdown causes a graceful shutdown
206251
func (g *Manager) DoGracefulShutdown() {
252+
g.lock.Lock()
253+
if !g.forked {
254+
g.lock.Unlock()
255+
g.notify(stoppingMsg)
256+
} else {
257+
g.lock.Unlock()
258+
g.notify(statusMsg("shutting down after fork"))
259+
}
207260
g.doShutdown()
208261
}
209262

modules/graceful/net_unix.go

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ import (
2121
)
2222

2323
const (
24-
listenFDs = "LISTEN_FDS"
25-
startFD = 3
26-
unlinkFDs = "GITEA_UNLINK_FDS"
24+
listenFDsEnv = "LISTEN_FDS"
25+
startFD = 3
26+
unlinkFDsEnv = "GITEA_UNLINK_FDS"
27+
28+
notifySocketEnv = "NOTIFY_SOCKET"
2729
)
2830

2931
// In order to keep the working directory the same as when we started we record
@@ -38,6 +40,8 @@ var (
3840
activeListenersToUnlink = []bool{}
3941
providedListeners = []net.Listener{}
4042
activeListeners = []net.Listener{}
43+
44+
notifySocketAddr string
4145
)
4246

4347
func getProvidedFDs() (savedErr error) {
@@ -46,17 +50,17 @@ func getProvidedFDs() (savedErr error) {
4650
mutex.Lock()
4751
defer mutex.Unlock()
4852

49-
numFDs := os.Getenv(listenFDs)
53+
numFDs := os.Getenv(listenFDsEnv)
5054
if numFDs == "" {
5155
return
5256
}
5357
n, err := strconv.Atoi(numFDs)
5458
if err != nil {
55-
savedErr = fmt.Errorf("%s is not a number: %s. Err: %v", listenFDs, numFDs, err)
59+
savedErr = fmt.Errorf("%s is not a number: %s. Err: %v", listenFDsEnv, numFDs, err)
5660
return
5761
}
5862

59-
fdsToUnlinkStr := strings.Split(os.Getenv(unlinkFDs), ",")
63+
fdsToUnlinkStr := strings.Split(os.Getenv(unlinkFDsEnv), ",")
6064
providedListenersToUnlink = make([]bool, n)
6165
for _, fdStr := range fdsToUnlinkStr {
6266
i, err := strconv.Atoi(fdStr)
@@ -84,6 +88,18 @@ func getProvidedFDs() (savedErr error) {
8488
savedErr = fmt.Errorf("Error getting provided socket fd %d: %v", i, err)
8589
return
8690
}
91+
92+
notifySocketAddr = os.Getenv(notifySocketEnv)
93+
if notifySocketAddr != "" {
94+
log.Debug("Systemd Notify Socket provided: %s", notifySocketAddr)
95+
savedErr = os.Unsetenv(notifySocketEnv)
96+
if savedErr != nil {
97+
log.Warn("Unable to Unset the NOTIFY_SOCKET environment variable: %v", savedErr)
98+
return
99+
}
100+
} else {
101+
log.Trace("No Systemd Notify Socket provided")
102+
}
87103
})
88104
return savedErr
89105
}
@@ -255,3 +271,26 @@ func getActiveListenersToUnlink() []bool {
255271
copy(listenersToUnlink, activeListenersToUnlink)
256272
return listenersToUnlink
257273
}
274+
275+
func getNotifySocket() (*net.UnixConn, error) {
276+
if err := getProvidedFDs(); err != nil {
277+
return nil, err
278+
}
279+
280+
if notifySocketAddr == "" {
281+
return nil, nil
282+
}
283+
284+
socketAddr := &net.UnixAddr{
285+
Name: notifySocketAddr,
286+
Net: "unixgram",
287+
}
288+
289+
notifySocket, err := net.DialUnix(socketAddr.Net, nil, socketAddr)
290+
if err != nil {
291+
log.Warn("failed to dial NOTIFY_SOCKET %s: %v", socketAddr, err)
292+
return nil, err
293+
}
294+
295+
return notifySocket, nil
296+
}

modules/graceful/restart_unix.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,15 @@ func RestartProcess() (int, error) {
7070
// Pass on the environment and replace the old count key with the new one.
7171
var env []string
7272
for _, v := range os.Environ() {
73-
if !strings.HasPrefix(v, listenFDs+"=") {
73+
if !strings.HasPrefix(v, listenFDsEnv+"=") {
7474
env = append(env, v)
7575
}
7676
}
77-
env = append(env, fmt.Sprintf("%s=%d", listenFDs, len(listeners)))
77+
env = append(env, fmt.Sprintf("%s=%d", listenFDsEnv, len(listeners)))
78+
79+
if notifySocketAddr != "" {
80+
env = append(env, fmt.Sprintf("%s=%s", notifySocketEnv, notifySocketAddr))
81+
}
7882

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

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

0 commit comments

Comments
 (0)