2019-11-21 18:32:02 +00:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2019-11-21 18:32:02 +00:00
// This code is heavily inspired by the archived gofacebook/gracenet/net.go handler
2021-08-24 11:47:09 -05:00
//go:build windows
2019-11-21 18:32:02 +00:00
package graceful
import (
"os"
2022-03-25 12:47:12 +00:00
"runtime/pprof"
2019-11-21 18:32:02 +00:00
"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"
)
2019-12-04 01:16:29 +01:00
// WindowsServiceName is the name of the Windows service
2019-11-21 18:32:02 +00:00
var WindowsServiceName = "gitea"
const (
hammerCode = 128
hammerCmd = svc . Cmd ( hammerCode )
acceptHammerCode = svc . Accepted ( hammerCode )
)
2019-12-15 09:51:28 +00:00
func ( g * Manager ) start ( ) {
2022-03-25 12:47:12 +00:00
// Now label this and all goroutines created by this goroutine with the graceful-lifecycle manager
pprof . SetGoroutineLabels ( g . managerCtx )
defer pprof . SetGoroutineLabels ( g . ctx )
2021-05-15 15:22:26 +01:00
2019-11-21 18:32:02 +00:00
if skip , _ := strconv . ParseBool ( os . Getenv ( "SKIP_MINWINSVC" ) ) ; skip {
2021-03-31 20:48:48 +01:00
log . Trace ( "Skipping SVC check as SKIP_MINWINSVC is set" )
2019-11-21 18:32:02 +00:00
return
}
2019-12-15 09:51:28 +00:00
// Make SVC process
2019-11-21 18:32:02 +00:00
run := svc . Run
2021-05-07 10:27:31 +01:00
//lint:ignore SA1019 We use IsAnInteractiveSession because IsWindowsService has a different permissions profile
2023-04-20 04:08:01 +02:00
isAnInteractiveSession , err := svc . IsAnInteractiveSession ( ) //nolint:staticcheck
2019-11-21 18:32:02 +00:00
if err != nil {
2021-03-31 20:48:48 +01:00
log . Error ( "Unable to ascertain if running as an Windows Service: %v" , err )
2019-11-21 18:32:02 +00:00
return
}
2021-05-07 10:27:31 +01:00
if isAnInteractiveSession {
2021-03-31 20:48:48 +01:00
log . Trace ( "Not running a service ... using the debug SVC manager" )
2019-11-21 18:32:02 +00:00
run = debug . Run
}
2021-01-06 09:38:00 +08:00
go func ( ) {
_ = run ( WindowsServiceName , g )
} ( )
2019-11-21 18:32:02 +00:00
}
2019-12-15 09:51:28 +00:00
// Execute makes Manager implement svc.Handler
func ( g * Manager ) Execute ( args [ ] string , changes <- chan svc . ChangeRequest , status chan <- svc . Status ) ( svcSpecificEC bool , exitCode uint32 ) {
2019-11-21 18:32:02 +00:00
if setting . StartupTimeout > 0 {
2019-11-22 22:00:01 +01:00
status <- svc . Status { State : svc . StartPending , WaitHint : uint32 ( setting . StartupTimeout / time . Millisecond ) }
2022-08-20 22:09:41 +01:00
} else {
status <- svc . Status { State : svc . StartPending }
2019-11-21 18:32:02 +00:00
}
2021-03-31 20:48:48 +01:00
log . Trace ( "Awaiting server start-up" )
2019-11-21 18:32:02 +00:00
// Now need to wait for everything to start...
if ! g . awaitServer ( setting . StartupTimeout ) {
2021-03-31 20:48:48 +01:00
log . Trace ( "... start-up failed ... Stopped" )
2019-11-21 18:32:02 +00:00
return false , 1
}
2021-03-31 20:48:48 +01:00
log . Trace ( "Sending Running state to SVC" )
2019-11-21 18:32:02 +00:00
// We need to implement some way of svc.AcceptParamChange/svc.ParamChange
status <- svc . Status {
State : svc . Running ,
Accepts : svc . AcceptStop | svc . AcceptShutdown | acceptHammerCode ,
}
2021-03-31 20:48:48 +01:00
log . Trace ( "Started" )
2019-11-21 18:32:02 +00:00
waitTime := 30 * time . Second
loop :
2019-11-30 08:40:22 -06:00
for {
select {
case <- g . ctx . Done ( ) :
2021-03-31 20:48:48 +01:00
log . Trace ( "Shutting down" )
2020-01-29 01:01:06 +00:00
g . DoGracefulShutdown ( )
waitTime += setting . GracefulHammerTime
break loop
case <- g . shutdownRequested :
2021-03-31 20:48:48 +01:00
log . Trace ( "Shutting down" )
2019-11-21 18:32:02 +00:00
waitTime += setting . GracefulHammerTime
break loop
2019-11-30 08:40:22 -06:00
case change := <- changes :
switch change . Cmd {
case svc . Interrogate :
2021-03-31 20:48:48 +01:00
log . Trace ( "SVC sent interrogate" )
2019-11-30 08:40:22 -06:00
status <- change . CurrentStatus
case svc . Stop , svc . Shutdown :
2021-03-31 20:48:48 +01:00
log . Trace ( "SVC requested shutdown - shutting down" )
2020-01-29 01:01:06 +00:00
g . DoGracefulShutdown ( )
2019-11-30 08:40:22 -06:00
waitTime += setting . GracefulHammerTime
break loop
case hammerCode :
2021-03-31 20:48:48 +01:00
log . Trace ( "SVC requested hammer - shutting down and hammering immediately" )
2020-01-29 01:01:06 +00:00
g . DoGracefulShutdown ( )
g . DoImmediateHammer ( )
2019-11-30 08:40:22 -06:00
break loop
default :
log . Debug ( "Unexpected control request: %v" , change . Cmd )
}
2019-11-21 18:32:02 +00:00
}
}
2021-03-31 20:48:48 +01:00
log . Trace ( "Sending StopPending state to SVC" )
2019-11-21 18:32:02 +00:00
status <- svc . Status {
2019-11-22 22:00:01 +01:00
State : svc . StopPending ,
WaitHint : uint32 ( waitTime / time . Millisecond ) ,
2019-11-21 18:32:02 +00:00
}
hammerLoop :
for {
select {
case change := <- changes :
switch change . Cmd {
case svc . Interrogate :
2021-03-31 20:48:48 +01:00
log . Trace ( "SVC sent interrogate" )
2019-11-21 18:32:02 +00:00
status <- change . CurrentStatus
case svc . Stop , svc . Shutdown , hammerCmd :
2021-03-31 20:48:48 +01:00
log . Trace ( "SVC requested hammer - hammering immediately" )
2020-01-29 01:01:06 +00:00
g . DoImmediateHammer ( )
2019-11-21 18:32:02 +00:00
break hammerLoop
default :
log . Debug ( "Unexpected control request: %v" , change . Cmd )
}
2021-05-15 15:22:26 +01:00
case <- g . hammerCtx . Done ( ) :
2019-11-21 18:32:02 +00:00
break hammerLoop
}
}
2021-03-31 20:48:48 +01:00
log . Trace ( "Stopped" )
2019-11-21 18:32:02 +00:00
return false , 0
}
2019-12-15 09:51:28 +00:00
func ( g * Manager ) awaitServer ( limit time . Duration ) bool {
2019-11-21 18:32:02 +00:00
c := make ( chan struct { } )
go func ( ) {
2024-03-15 18:59:11 +08:00
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 ( )
}
2019-11-21 18:32:02 +00:00
} ( )
2024-03-15 18:59:11 +08:00
var tc <- chan time . Time
2019-11-21 18:32:02 +00:00
if limit > 0 {
2024-03-15 18:59:11 +08:00
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
2019-11-21 18:32:02 +00:00
}
}
2023-11-24 22:21:46 +08:00
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
}