gitea/modules/graceful/manager_windows.go
2024-03-15 10:59:11 +00:00

191 lines
4.6 KiB
Go

// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
// This code is heavily inspired by the archived gofacebook/gracenet/net.go handler
//go:build windows
package graceful
import (
"os"
"runtime/pprof"
"strconv"
"time"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/debug"
)
// WindowsServiceName is the name of the Windows service
var WindowsServiceName = "gitea"
const (
hammerCode = 128
hammerCmd = svc.Cmd(hammerCode)
acceptHammerCode = svc.Accepted(hammerCode)
)
func (g *Manager) start() {
// Now label this and all goroutines created by this goroutine with the graceful-lifecycle manager
pprof.SetGoroutineLabels(g.managerCtx)
defer pprof.SetGoroutineLabels(g.ctx)
if skip, _ := strconv.ParseBool(os.Getenv("SKIP_MINWINSVC")); skip {
log.Trace("Skipping SVC check as SKIP_MINWINSVC is set")
return
}
// Make SVC process
run := svc.Run
//lint:ignore SA1019 We use IsAnInteractiveSession because IsWindowsService has a different permissions profile
isAnInteractiveSession, err := svc.IsAnInteractiveSession() //nolint:staticcheck
if err != nil {
log.Error("Unable to ascertain if running as an Windows Service: %v", err)
return
}
if isAnInteractiveSession {
log.Trace("Not running a service ... using the debug SVC manager")
run = debug.Run
}
go func() {
_ = run(WindowsServiceName, g)
}()
}
// Execute makes Manager implement svc.Handler
func (g *Manager) Execute(args []string, changes <-chan svc.ChangeRequest, status chan<- svc.Status) (svcSpecificEC bool, exitCode uint32) {
if setting.StartupTimeout > 0 {
status <- svc.Status{State: svc.StartPending, WaitHint: uint32(setting.StartupTimeout / time.Millisecond)}
} else {
status <- svc.Status{State: svc.StartPending}
}
log.Trace("Awaiting server start-up")
// Now need to wait for everything to start...
if !g.awaitServer(setting.StartupTimeout) {
log.Trace("... start-up failed ... Stopped")
return false, 1
}
log.Trace("Sending Running state to SVC")
// We need to implement some way of svc.AcceptParamChange/svc.ParamChange
status <- svc.Status{
State: svc.Running,
Accepts: svc.AcceptStop | svc.AcceptShutdown | acceptHammerCode,
}
log.Trace("Started")
waitTime := 30 * time.Second
loop:
for {
select {
case <-g.ctx.Done():
log.Trace("Shutting down")
g.DoGracefulShutdown()
waitTime += setting.GracefulHammerTime
break loop
case <-g.shutdownRequested:
log.Trace("Shutting down")
waitTime += setting.GracefulHammerTime
break loop
case change := <-changes:
switch change.Cmd {
case svc.Interrogate:
log.Trace("SVC sent interrogate")
status <- change.CurrentStatus
case svc.Stop, svc.Shutdown:
log.Trace("SVC requested shutdown - shutting down")
g.DoGracefulShutdown()
waitTime += setting.GracefulHammerTime
break loop
case hammerCode:
log.Trace("SVC requested hammer - shutting down and hammering immediately")
g.DoGracefulShutdown()
g.DoImmediateHammer()
break loop
default:
log.Debug("Unexpected control request: %v", change.Cmd)
}
}
}
log.Trace("Sending StopPending state to SVC")
status <- svc.Status{
State: svc.StopPending,
WaitHint: uint32(waitTime / time.Millisecond),
}
hammerLoop:
for {
select {
case change := <-changes:
switch change.Cmd {
case svc.Interrogate:
log.Trace("SVC sent interrogate")
status <- change.CurrentStatus
case svc.Stop, svc.Shutdown, hammerCmd:
log.Trace("SVC requested hammer - hammering immediately")
g.DoImmediateHammer()
break hammerLoop
default:
log.Debug("Unexpected control request: %v", change.Cmd)
}
case <-g.hammerCtx.Done():
break hammerLoop
}
}
log.Trace("Stopped")
return false, 0
}
func (g *Manager) awaitServer(limit time.Duration) bool {
c := make(chan struct{})
go func() {
g.createServerCond.L.Lock()
for {
if g.createdServer >= numberOfServersToCreate {
g.createServerCond.L.Unlock()
close(c)
return
}
select {
case <-g.IsShutdown():
g.createServerCond.L.Unlock()
return
default:
}
g.createServerCond.Wait()
}
}()
var tc <-chan time.Time
if limit > 0 {
tc = time.After(limit)
}
select {
case <-c:
return true // completed normally
case <-tc:
return false // timed out
case <-g.IsShutdown():
g.createServerCond.Signal()
return false
}
}
func (g *Manager) notify(msg systemdNotifyMsg) {
// Windows doesn't use systemd to notify
}
func KillParent() {
// Windows doesn't need to "kill parent" because there is no graceful restart
}