2019-11-21 21:32:02 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2019-11-21 21:32:02 +03:00
// This code is heavily inspired by the archived gofacebook/gracenet/net.go handler
2021-08-24 19:47:09 +03:00
//go:build windows
2019-11-21 21:32:02 +03:00
package graceful
import (
2019-11-30 17:40:22 +03:00
"context"
2019-11-21 21:32:02 +03:00
"os"
2022-03-25 15:47:12 +03:00
"runtime/pprof"
2019-11-21 21:32:02 +03:00
"strconv"
"sync"
"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 03:16:29 +03:00
// WindowsServiceName is the name of the Windows service
2019-11-21 21:32:02 +03:00
var WindowsServiceName = "gitea"
const (
hammerCode = 128
hammerCmd = svc . Cmd ( hammerCode )
acceptHammerCode = svc . Accepted ( hammerCode )
)
2019-12-15 12:51:28 +03:00
// Manager manages the graceful shutdown process
type Manager struct {
2019-11-30 17:40:22 +03:00
ctx context . Context
2019-11-21 21:32:02 +03:00
isChild bool
lock * sync . RWMutex
state state
2021-05-15 17:22:26 +03:00
shutdownCtx context . Context
hammerCtx context . Context
terminateCtx context . Context
2022-03-25 15:47:12 +03:00
managerCtx context . Context
2021-05-15 17:22:26 +03:00
shutdownCtxCancel context . CancelFunc
hammerCtxCancel context . CancelFunc
terminateCtxCancel context . CancelFunc
2022-03-25 15:47:12 +03:00
managerCtxCancel context . CancelFunc
2019-11-21 21:32:02 +03:00
runningServerWaitGroup sync . WaitGroup
createServerWaitGroup sync . WaitGroup
terminateWaitGroup sync . WaitGroup
2020-01-29 21:54:34 +03:00
shutdownRequested chan struct { }
2021-05-15 17:22:26 +03:00
toRunAtShutdown [ ] func ( )
toRunAtHammer [ ] func ( )
toRunAtTerminate [ ] func ( )
2019-11-21 21:32:02 +03:00
}
2019-12-15 12:51:28 +03:00
func newGracefulManager ( ctx context . Context ) * Manager {
manager := & Manager {
2019-11-21 21:32:02 +03:00
isChild : false ,
lock : & sync . RWMutex { } ,
2019-11-30 17:40:22 +03:00
ctx : ctx ,
2019-11-21 21:32:02 +03:00
}
manager . createServerWaitGroup . Add ( numberOfServersToCreate )
2019-12-15 12:51:28 +03:00
manager . start ( )
2019-11-21 21:32:02 +03:00
return manager
}
2019-12-15 12:51:28 +03:00
func ( g * Manager ) start ( ) {
2021-05-15 17:22:26 +03:00
// Make contexts
g . terminateCtx , g . terminateCtxCancel = context . WithCancel ( g . ctx )
g . shutdownCtx , g . shutdownCtxCancel = context . WithCancel ( g . ctx )
g . hammerCtx , g . hammerCtxCancel = context . WithCancel ( g . ctx )
2022-03-25 15:47:12 +03:00
g . managerCtx , g . managerCtxCancel = context . WithCancel ( g . ctx )
// Next add pprof labels to these contexts
g . terminateCtx = pprof . WithLabels ( g . terminateCtx , pprof . Labels ( "graceful-lifecycle" , "with-terminate" ) )
g . shutdownCtx = pprof . WithLabels ( g . shutdownCtx , pprof . Labels ( "graceful-lifecycle" , "with-shutdown" ) )
g . hammerCtx = pprof . WithLabels ( g . hammerCtx , pprof . Labels ( "graceful-lifecycle" , "with-hammer" ) )
g . managerCtx = pprof . WithLabels ( g . managerCtx , pprof . Labels ( "graceful-lifecycle" , "with-manager" ) )
// 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 17:22:26 +03:00
2019-12-15 12:51:28 +03:00
// Make channels
2020-01-29 04:01:06 +03:00
g . shutdownRequested = make ( chan struct { } )
2019-12-15 12:51:28 +03:00
// Set the running state
2019-11-21 21:32:02 +03:00
g . setState ( stateRunning )
if skip , _ := strconv . ParseBool ( os . Getenv ( "SKIP_MINWINSVC" ) ) ; skip {
2021-03-31 22:48:48 +03:00
log . Trace ( "Skipping SVC check as SKIP_MINWINSVC is set" )
2019-11-21 21:32:02 +03:00
return
}
2019-12-15 12:51:28 +03:00
// Make SVC process
2019-11-21 21:32:02 +03:00
run := svc . Run
2021-05-07 12:27:31 +03:00
//lint:ignore SA1019 We use IsAnInteractiveSession because IsWindowsService has a different permissions profile
isAnInteractiveSession , err := svc . IsAnInteractiveSession ( )
2019-11-21 21:32:02 +03:00
if err != nil {
2021-03-31 22:48:48 +03:00
log . Error ( "Unable to ascertain if running as an Windows Service: %v" , err )
2019-11-21 21:32:02 +03:00
return
}
2021-05-07 12:27:31 +03:00
if isAnInteractiveSession {
2021-03-31 22:48:48 +03:00
log . Trace ( "Not running a service ... using the debug SVC manager" )
2019-11-21 21:32:02 +03:00
run = debug . Run
}
2021-01-06 04:38:00 +03:00
go func ( ) {
_ = run ( WindowsServiceName , g )
} ( )
2019-11-21 21:32:02 +03:00
}
2019-12-15 12:51:28 +03: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 21:32:02 +03:00
if setting . StartupTimeout > 0 {
2019-11-23 00:00:01 +03:00
status <- svc . Status { State : svc . StartPending , WaitHint : uint32 ( setting . StartupTimeout / time . Millisecond ) }
2022-08-21 00:09:41 +03:00
} else {
status <- svc . Status { State : svc . StartPending }
2019-11-21 21:32:02 +03:00
}
2021-03-31 22:48:48 +03:00
log . Trace ( "Awaiting server start-up" )
2019-11-21 21:32:02 +03:00
// Now need to wait for everything to start...
if ! g . awaitServer ( setting . StartupTimeout ) {
2021-03-31 22:48:48 +03:00
log . Trace ( "... start-up failed ... Stopped" )
2019-11-21 21:32:02 +03:00
return false , 1
}
2021-03-31 22:48:48 +03:00
log . Trace ( "Sending Running state to SVC" )
2019-11-21 21:32:02 +03: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 22:48:48 +03:00
log . Trace ( "Started" )
2019-11-21 21:32:02 +03:00
waitTime := 30 * time . Second
loop :
2019-11-30 17:40:22 +03:00
for {
select {
case <- g . ctx . Done ( ) :
2021-03-31 22:48:48 +03:00
log . Trace ( "Shutting down" )
2020-01-29 04:01:06 +03:00
g . DoGracefulShutdown ( )
waitTime += setting . GracefulHammerTime
break loop
case <- g . shutdownRequested :
2021-03-31 22:48:48 +03:00
log . Trace ( "Shutting down" )
2019-11-21 21:32:02 +03:00
waitTime += setting . GracefulHammerTime
break loop
2019-11-30 17:40:22 +03:00
case change := <- changes :
switch change . Cmd {
case svc . Interrogate :
2021-03-31 22:48:48 +03:00
log . Trace ( "SVC sent interrogate" )
2019-11-30 17:40:22 +03:00
status <- change . CurrentStatus
case svc . Stop , svc . Shutdown :
2021-03-31 22:48:48 +03:00
log . Trace ( "SVC requested shutdown - shutting down" )
2020-01-29 04:01:06 +03:00
g . DoGracefulShutdown ( )
2019-11-30 17:40:22 +03:00
waitTime += setting . GracefulHammerTime
break loop
case hammerCode :
2021-03-31 22:48:48 +03:00
log . Trace ( "SVC requested hammer - shutting down and hammering immediately" )
2020-01-29 04:01:06 +03:00
g . DoGracefulShutdown ( )
g . DoImmediateHammer ( )
2019-11-30 17:40:22 +03:00
break loop
default :
log . Debug ( "Unexpected control request: %v" , change . Cmd )
}
2019-11-21 21:32:02 +03:00
}
}
2021-03-31 22:48:48 +03:00
log . Trace ( "Sending StopPending state to SVC" )
2019-11-21 21:32:02 +03:00
status <- svc . Status {
2019-11-23 00:00:01 +03:00
State : svc . StopPending ,
WaitHint : uint32 ( waitTime / time . Millisecond ) ,
2019-11-21 21:32:02 +03:00
}
hammerLoop :
for {
select {
case change := <- changes :
switch change . Cmd {
case svc . Interrogate :
2021-03-31 22:48:48 +03:00
log . Trace ( "SVC sent interrogate" )
2019-11-21 21:32:02 +03:00
status <- change . CurrentStatus
case svc . Stop , svc . Shutdown , hammerCmd :
2021-03-31 22:48:48 +03:00
log . Trace ( "SVC requested hammer - hammering immediately" )
2020-01-29 04:01:06 +03:00
g . DoImmediateHammer ( )
2019-11-21 21:32:02 +03:00
break hammerLoop
default :
log . Debug ( "Unexpected control request: %v" , change . Cmd )
}
2021-05-15 17:22:26 +03:00
case <- g . hammerCtx . Done ( ) :
2019-11-21 21:32:02 +03:00
break hammerLoop
}
}
2021-03-31 22:48:48 +03:00
log . Trace ( "Stopped" )
2019-11-21 21:32:02 +03:00
return false , 0
}
2020-01-29 04:01:06 +03:00
// DoImmediateHammer causes an immediate hammer
func ( g * Manager ) DoImmediateHammer ( ) {
g . doHammerTime ( 0 * time . Second )
}
// DoGracefulShutdown causes a graceful shutdown
func ( g * Manager ) DoGracefulShutdown ( ) {
g . lock . Lock ( )
select {
case <- g . shutdownRequested :
g . lock . Unlock ( )
default :
close ( g . shutdownRequested )
g . lock . Unlock ( )
g . doShutdown ( )
}
}
2019-12-15 12:51:28 +03:00
// RegisterServer registers the running of a listening server.
// Any call to RegisterServer must be matched by a call to ServerDone
func ( g * Manager ) RegisterServer ( ) {
2019-11-21 21:32:02 +03:00
g . runningServerWaitGroup . Add ( 1 )
}
2019-12-15 12:51:28 +03:00
func ( g * Manager ) awaitServer ( limit time . Duration ) bool {
2019-11-21 21:32:02 +03:00
c := make ( chan struct { } )
go func ( ) {
defer close ( c )
g . createServerWaitGroup . Wait ( )
} ( )
if limit > 0 {
select {
case <- c :
return true // completed normally
case <- time . After ( limit ) :
return false // timed out
case <- g . IsShutdown ( ) :
return false
}
} else {
select {
case <- c :
return true // completed normally
case <- g . IsShutdown ( ) :
return false
}
}
}