2019-11-21 21:32:02 +03:00
// +build windows
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// This code is heavily inspired by the archived gofacebook/gracenet/net.go handler
package graceful
import (
2019-11-30 17:40:22 +03:00
"context"
2019-11-21 21:32:02 +03:00
"os"
"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
shutdown chan struct { }
hammer chan struct { }
terminate chan struct { }
2019-12-15 12:51:28 +03:00
done chan struct { }
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 { }
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 ( ) {
// Make channels
g . terminate = make ( chan struct { } )
g . shutdown = make ( chan struct { } )
g . hammer = make ( chan struct { } )
g . done = make ( chan struct { } )
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 {
return
}
2019-12-15 12:51:28 +03:00
// Make SVC process
2019-11-21 21:32:02 +03:00
run := svc . Run
2021-01-06 04:38:00 +03:00
isInteractive , err := svc . IsWindowsService ( )
2019-11-21 21:32:02 +03:00
if err != nil {
log . Error ( "Unable to ascertain if running as an Interactive Session: %v" , err )
return
}
if isInteractive {
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 {
status <- svc . Status { State : svc . StartPending }
} else {
2019-11-23 00:00:01 +03:00
status <- svc . Status { State : svc . StartPending , WaitHint : uint32 ( setting . StartupTimeout / time . Millisecond ) }
2019-11-21 21:32:02 +03:00
}
// Now need to wait for everything to start...
if ! g . awaitServer ( setting . StartupTimeout ) {
return false , 1
}
// We need to implement some way of svc.AcceptParamChange/svc.ParamChange
status <- svc . Status {
State : svc . Running ,
Accepts : svc . AcceptStop | svc . AcceptShutdown | acceptHammerCode ,
}
waitTime := 30 * time . Second
loop :
2019-11-30 17:40:22 +03:00
for {
select {
case <- g . ctx . Done ( ) :
2020-01-29 04:01:06 +03:00
g . DoGracefulShutdown ( )
waitTime += setting . GracefulHammerTime
break loop
case <- g . shutdownRequested :
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 :
status <- change . CurrentStatus
case svc . Stop , svc . Shutdown :
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 :
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
}
}
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 :
status <- change . CurrentStatus
case svc . Stop , svc . Shutdown , hammerCmd :
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 )
}
case <- g . hammer :
break hammerLoop
}
}
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
}
}
}