Skip to content

Commit 2ac2796

Browse files
authored
Refactor graceful manager to use shared code (#28073)
Make "windows" and "unix" share as much code as possible. No logic change.
1 parent ad9aac3 commit 2ac2796

File tree

3 files changed

+119
-174
lines changed

3 files changed

+119
-174
lines changed

modules/graceful/manager_common.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package graceful
5+
6+
import (
7+
"context"
8+
"runtime/pprof"
9+
"sync"
10+
"time"
11+
)
12+
13+
type systemdNotifyMsg string
14+
15+
const (
16+
readyMsg systemdNotifyMsg = "READY=1"
17+
stoppingMsg systemdNotifyMsg = "STOPPING=1"
18+
reloadingMsg systemdNotifyMsg = "RELOADING=1"
19+
watchdogMsg systemdNotifyMsg = "WATCHDOG=1"
20+
)
21+
22+
func statusMsg(msg string) systemdNotifyMsg {
23+
return systemdNotifyMsg("STATUS=" + msg)
24+
}
25+
26+
// Manager manages the graceful shutdown process
27+
type Manager struct {
28+
ctx context.Context
29+
isChild bool
30+
forked bool
31+
lock sync.RWMutex
32+
state state
33+
shutdownCtx context.Context
34+
hammerCtx context.Context
35+
terminateCtx context.Context
36+
managerCtx context.Context
37+
shutdownCtxCancel context.CancelFunc
38+
hammerCtxCancel context.CancelFunc
39+
terminateCtxCancel context.CancelFunc
40+
managerCtxCancel context.CancelFunc
41+
runningServerWaitGroup sync.WaitGroup
42+
createServerWaitGroup sync.WaitGroup
43+
terminateWaitGroup sync.WaitGroup
44+
shutdownRequested chan struct{}
45+
46+
toRunAtShutdown []func()
47+
toRunAtTerminate []func()
48+
}
49+
50+
func newGracefulManager(ctx context.Context) *Manager {
51+
manager := &Manager{ctx: ctx, shutdownRequested: make(chan struct{})}
52+
manager.createServerWaitGroup.Add(numberOfServersToCreate)
53+
manager.prepare(ctx)
54+
manager.start()
55+
return manager
56+
}
57+
58+
func (g *Manager) prepare(ctx context.Context) {
59+
g.terminateCtx, g.terminateCtxCancel = context.WithCancel(ctx)
60+
g.shutdownCtx, g.shutdownCtxCancel = context.WithCancel(ctx)
61+
g.hammerCtx, g.hammerCtxCancel = context.WithCancel(ctx)
62+
g.managerCtx, g.managerCtxCancel = context.WithCancel(ctx)
63+
64+
g.terminateCtx = pprof.WithLabels(g.terminateCtx, pprof.Labels("graceful-lifecycle", "with-terminate"))
65+
g.shutdownCtx = pprof.WithLabels(g.shutdownCtx, pprof.Labels("graceful-lifecycle", "with-shutdown"))
66+
g.hammerCtx = pprof.WithLabels(g.hammerCtx, pprof.Labels("graceful-lifecycle", "with-hammer"))
67+
g.managerCtx = pprof.WithLabels(g.managerCtx, pprof.Labels("graceful-lifecycle", "with-manager"))
68+
69+
if !g.setStateTransition(stateInit, stateRunning) {
70+
panic("invalid graceful manager state: transition from init to running failed")
71+
}
72+
}
73+
74+
// DoImmediateHammer causes an immediate hammer
75+
func (g *Manager) DoImmediateHammer() {
76+
g.notify(statusMsg("Sending immediate hammer"))
77+
g.doHammerTime(0 * time.Second)
78+
}
79+
80+
// DoGracefulShutdown causes a graceful shutdown
81+
func (g *Manager) DoGracefulShutdown() {
82+
g.lock.Lock()
83+
select {
84+
case <-g.shutdownRequested:
85+
default:
86+
close(g.shutdownRequested)
87+
}
88+
forked := g.forked
89+
g.lock.Unlock()
90+
91+
if !forked {
92+
g.notify(stoppingMsg)
93+
} else {
94+
g.notify(statusMsg("Shutting down after fork"))
95+
}
96+
g.doShutdown()
97+
}
98+
99+
// RegisterServer registers the running of a listening server, in the case of unix this means that the parent process can now die.
100+
// Any call to RegisterServer must be matched by a call to ServerDone
101+
func (g *Manager) RegisterServer() {
102+
KillParent()
103+
g.runningServerWaitGroup.Add(1)
104+
}

modules/graceful/manager_unix.go

Lines changed: 7 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"os/signal"
1313
"runtime/pprof"
1414
"strconv"
15-
"sync"
1615
"syscall"
1716
"time"
1817

@@ -22,51 +21,6 @@ import (
2221
"code.gitea.io/gitea/modules/setting"
2322
)
2423

25-
// Manager manages the graceful shutdown process
26-
type Manager struct {
27-
isChild bool
28-
forked bool
29-
lock *sync.RWMutex
30-
state state
31-
shutdownCtx context.Context
32-
hammerCtx context.Context
33-
terminateCtx context.Context
34-
managerCtx context.Context
35-
shutdownCtxCancel context.CancelFunc
36-
hammerCtxCancel context.CancelFunc
37-
terminateCtxCancel context.CancelFunc
38-
managerCtxCancel context.CancelFunc
39-
runningServerWaitGroup sync.WaitGroup
40-
createServerWaitGroup sync.WaitGroup
41-
terminateWaitGroup sync.WaitGroup
42-
43-
toRunAtShutdown []func()
44-
toRunAtTerminate []func()
45-
}
46-
47-
func newGracefulManager(ctx context.Context) *Manager {
48-
manager := &Manager{
49-
isChild: len(os.Getenv(listenFDsEnv)) > 0 && os.Getppid() > 1,
50-
lock: &sync.RWMutex{},
51-
}
52-
manager.createServerWaitGroup.Add(numberOfServersToCreate)
53-
manager.start(ctx)
54-
return manager
55-
}
56-
57-
type systemdNotifyMsg string
58-
59-
const (
60-
readyMsg systemdNotifyMsg = "READY=1"
61-
stoppingMsg systemdNotifyMsg = "STOPPING=1"
62-
reloadingMsg systemdNotifyMsg = "RELOADING=1"
63-
watchdogMsg systemdNotifyMsg = "WATCHDOG=1"
64-
)
65-
66-
func statusMsg(msg string) systemdNotifyMsg {
67-
return systemdNotifyMsg("STATUS=" + msg)
68-
}
69-
7024
func pidMsg() systemdNotifyMsg {
7125
return systemdNotifyMsg("MAINPID=" + strconv.Itoa(os.Getpid()))
7226
}
@@ -89,27 +43,13 @@ func (g *Manager) notify(msg systemdNotifyMsg) {
8943
}
9044
}
9145

92-
func (g *Manager) start(ctx context.Context) {
93-
// Make contexts
94-
g.terminateCtx, g.terminateCtxCancel = context.WithCancel(ctx)
95-
g.shutdownCtx, g.shutdownCtxCancel = context.WithCancel(ctx)
96-
g.hammerCtx, g.hammerCtxCancel = context.WithCancel(ctx)
97-
g.managerCtx, g.managerCtxCancel = context.WithCancel(ctx)
98-
99-
// Next add pprof labels to these contexts
100-
g.terminateCtx = pprof.WithLabels(g.terminateCtx, pprof.Labels("graceful-lifecycle", "with-terminate"))
101-
g.shutdownCtx = pprof.WithLabels(g.shutdownCtx, pprof.Labels("graceful-lifecycle", "with-shutdown"))
102-
g.hammerCtx = pprof.WithLabels(g.hammerCtx, pprof.Labels("graceful-lifecycle", "with-hammer"))
103-
g.managerCtx = pprof.WithLabels(g.managerCtx, pprof.Labels("graceful-lifecycle", "with-manager"))
104-
46+
func (g *Manager) start() {
10547
// Now label this and all goroutines created by this goroutine with the graceful-lifecycle manager
10648
pprof.SetGoroutineLabels(g.managerCtx)
107-
defer pprof.SetGoroutineLabels(ctx)
49+
defer pprof.SetGoroutineLabels(g.ctx)
50+
51+
g.isChild = len(os.Getenv(listenFDsEnv)) > 0 && os.Getppid() > 1
10852

109-
// Set the running state & handle signals
110-
if !g.setStateTransition(stateInit, stateRunning) {
111-
panic("invalid graceful manager state: transition from init to running failed")
112-
}
11353
g.notify(statusMsg("Starting Gitea"))
11454
g.notify(pidMsg())
11555
go g.handleSignals(g.managerCtx)
@@ -118,11 +58,9 @@ func (g *Manager) start(ctx context.Context) {
11858
startupDone := make(chan struct{})
11959
go func() {
12060
defer close(startupDone)
121-
// Wait till we're done getting all of the listeners and then close
122-
// the unused ones
61+
// Wait till we're done getting all the listeners and then close the unused ones
12362
g.createServerWaitGroup.Wait()
124-
// Ignore the error here there's not much we can do with it
125-
// They're logged in the CloseProvidedListeners function
63+
// Ignore the error here there's not much we can do with it, they're logged in the CloseProvidedListeners function
12664
_ = CloseProvidedListeners()
12765
g.notify(readyMsg)
12866
}()
@@ -133,7 +71,7 @@ func (g *Manager) start(ctx context.Context) {
13371
return
13472
case <-g.IsShutdown():
13573
func() {
136-
// When waitgroup counter goes negative it will panic - we don't care about this so we can just ignore it.
74+
// When WaitGroup counter goes negative it will panic - we don't care about this so we can just ignore it.
13775
defer func() {
13876
_ = recover()
13977
}()
@@ -255,29 +193,3 @@ func (g *Manager) DoGracefulRestart() {
255193
g.doShutdown()
256194
}
257195
}
258-
259-
// DoImmediateHammer causes an immediate hammer
260-
func (g *Manager) DoImmediateHammer() {
261-
g.notify(statusMsg("Sending immediate hammer"))
262-
g.doHammerTime(0 * time.Second)
263-
}
264-
265-
// DoGracefulShutdown causes a graceful shutdown
266-
func (g *Manager) DoGracefulShutdown() {
267-
g.lock.Lock()
268-
if !g.forked {
269-
g.lock.Unlock()
270-
g.notify(stoppingMsg)
271-
} else {
272-
g.lock.Unlock()
273-
g.notify(statusMsg("Shutting down after fork"))
274-
}
275-
g.doShutdown()
276-
}
277-
278-
// RegisterServer registers the running of a listening server, in the case of unix this means that the parent process can now die.
279-
// Any call to RegisterServer must be matched by a call to ServerDone
280-
func (g *Manager) RegisterServer() {
281-
KillParent()
282-
g.runningServerWaitGroup.Add(1)
283-
}

modules/graceful/manager_windows.go

Lines changed: 8 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,9 @@
77
package graceful
88

99
import (
10-
"context"
1110
"os"
1211
"runtime/pprof"
1312
"strconv"
14-
"sync"
1513
"time"
1614

1715
"code.gitea.io/gitea/modules/log"
@@ -30,64 +28,11 @@ const (
3028
acceptHammerCode = svc.Accepted(hammerCode)
3129
)
3230

33-
// Manager manages the graceful shutdown process
34-
type Manager struct {
35-
ctx context.Context
36-
isChild bool
37-
lock *sync.RWMutex
38-
state state
39-
shutdownCtx context.Context
40-
hammerCtx context.Context
41-
terminateCtx context.Context
42-
managerCtx context.Context
43-
shutdownCtxCancel context.CancelFunc
44-
hammerCtxCancel context.CancelFunc
45-
terminateCtxCancel context.CancelFunc
46-
managerCtxCancel context.CancelFunc
47-
runningServerWaitGroup sync.WaitGroup
48-
createServerWaitGroup sync.WaitGroup
49-
terminateWaitGroup sync.WaitGroup
50-
shutdownRequested chan struct{}
51-
52-
toRunAtShutdown []func()
53-
toRunAtTerminate []func()
54-
}
55-
56-
func newGracefulManager(ctx context.Context) *Manager {
57-
manager := &Manager{
58-
isChild: false,
59-
lock: &sync.RWMutex{},
60-
ctx: ctx,
61-
}
62-
manager.createServerWaitGroup.Add(numberOfServersToCreate)
63-
manager.start()
64-
return manager
65-
}
66-
6731
func (g *Manager) start() {
68-
// Make contexts
69-
g.terminateCtx, g.terminateCtxCancel = context.WithCancel(g.ctx)
70-
g.shutdownCtx, g.shutdownCtxCancel = context.WithCancel(g.ctx)
71-
g.hammerCtx, g.hammerCtxCancel = context.WithCancel(g.ctx)
72-
g.managerCtx, g.managerCtxCancel = context.WithCancel(g.ctx)
73-
74-
// Next add pprof labels to these contexts
75-
g.terminateCtx = pprof.WithLabels(g.terminateCtx, pprof.Labels("graceful-lifecycle", "with-terminate"))
76-
g.shutdownCtx = pprof.WithLabels(g.shutdownCtx, pprof.Labels("graceful-lifecycle", "with-shutdown"))
77-
g.hammerCtx = pprof.WithLabels(g.hammerCtx, pprof.Labels("graceful-lifecycle", "with-hammer"))
78-
g.managerCtx = pprof.WithLabels(g.managerCtx, pprof.Labels("graceful-lifecycle", "with-manager"))
79-
8032
// Now label this and all goroutines created by this goroutine with the graceful-lifecycle manager
8133
pprof.SetGoroutineLabels(g.managerCtx)
8234
defer pprof.SetGoroutineLabels(g.ctx)
8335

84-
// Make channels
85-
g.shutdownRequested = make(chan struct{})
86-
87-
// Set the running state
88-
if !g.setStateTransition(stateInit, stateRunning) {
89-
panic("invalid graceful manager state: transition from init to running failed")
90-
}
9136
if skip, _ := strconv.ParseBool(os.Getenv("SKIP_MINWINSVC")); skip {
9237
log.Trace("Skipping SVC check as SKIP_MINWINSVC is set")
9338
return
@@ -201,30 +146,6 @@ hammerLoop:
201146
return false, 0
202147
}
203148

204-
// DoImmediateHammer causes an immediate hammer
205-
func (g *Manager) DoImmediateHammer() {
206-
g.doHammerTime(0 * time.Second)
207-
}
208-
209-
// DoGracefulShutdown causes a graceful shutdown
210-
func (g *Manager) DoGracefulShutdown() {
211-
g.lock.Lock()
212-
select {
213-
case <-g.shutdownRequested:
214-
g.lock.Unlock()
215-
default:
216-
close(g.shutdownRequested)
217-
g.lock.Unlock()
218-
g.doShutdown()
219-
}
220-
}
221-
222-
// RegisterServer registers the running of a listening server.
223-
// Any call to RegisterServer must be matched by a call to ServerDone
224-
func (g *Manager) RegisterServer() {
225-
g.runningServerWaitGroup.Add(1)
226-
}
227-
228149
func (g *Manager) awaitServer(limit time.Duration) bool {
229150
c := make(chan struct{})
230151
go func() {
@@ -249,3 +170,11 @@ func (g *Manager) awaitServer(limit time.Duration) bool {
249170
}
250171
}
251172
}
173+
174+
func (g *Manager) notify(msg systemdNotifyMsg) {
175+
// Windows doesn't use systemd to notify
176+
}
177+
178+
func KillParent() {
179+
// Windows doesn't need to "kill parent" because there is no graceful restart
180+
}

0 commit comments

Comments
 (0)