* Graceful Shutdown for windows and others Restructures modules/graceful, adding shutdown for windows, removing and replacing the old minwinsvc code. Creates a new waitGroup - terminate which allows for goroutines to finish up after the shutdown of the servers. Shutdown and terminate hooks are added for goroutines. * Remove unused functions - these can be added in a different PR * Add startup timeout functionality * Document STARTUP_TIMEOUTfor-closed-social
@ -1,37 +0,0 @@ | |||
// +build windows | |||
// Copyright 2016 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. | |||
package cmd | |||
import ( | |||
"crypto/tls" | |||
"net/http" | |||
) | |||
func runHTTP(listenAddr string, m http.Handler) error { | |||
return http.ListenAndServe(listenAddr, m) | |||
} | |||
func runHTTPS(listenAddr, certFile, keyFile string, m http.Handler) error { | |||
return http.ListenAndServeTLS(listenAddr, certFile, keyFile, m) | |||
} | |||
func runHTTPSWithTLSConfig(listenAddr string, tlsConfig *tls.Config, m http.Handler) error { | |||
server := &http.Server{ | |||
Addr: listenAddr, | |||
Handler: m, | |||
TLSConfig: tlsConfig, | |||
} | |||
return server.ListenAndServeTLS("", "") | |||
} | |||
// NoHTTPRedirector is a no-op on Windows | |||
func NoHTTPRedirector() { | |||
} | |||
// NoMainListener is a no-op on Windows | |||
func NoMainListener() { | |||
} |
@ -1,40 +0,0 @@ | |||
// +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. | |||
package graceful | |||
import "sync" | |||
var cleanupWaitGroup sync.WaitGroup | |||
func init() { | |||
cleanupWaitGroup = sync.WaitGroup{} | |||
// There are three places that could inherit sockets: | |||
// | |||
// * HTTP or HTTPS main listener | |||
// * HTTP redirection fallback | |||
// * SSH | |||
// | |||
// If you add an additional place you must increment this number | |||
// and add a function to call InformCleanup if it's not going to be used | |||
cleanupWaitGroup.Add(3) | |||
// Wait till we're done getting all of the listeners and then close | |||
// the unused ones | |||
go func() { | |||
cleanupWaitGroup.Wait() | |||
// Ignore the error here there's not much we can do with it | |||
// They're logged in the CloseProvidedListeners function | |||
_ = CloseProvidedListeners() | |||
}() | |||
} | |||
// InformCleanup tells the cleanup wait group that we have either taken a listener | |||
// or will not be taking a listener | |||
func InformCleanup() { | |||
cleanupWaitGroup.Done() | |||
} |
@ -1,16 +0,0 @@ | |||
// +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 | |||
// This file contains shims for windows builds | |||
const IsChild = false | |||
// WaitForServers waits for all running servers to finish | |||
func WaitForServers() { | |||
} |
@ -0,0 +1,187 @@ | |||
// 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. | |||
package graceful | |||
import ( | |||
"time" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/setting" | |||
) | |||
type state uint8 | |||
const ( | |||
stateInit state = iota | |||
stateRunning | |||
stateShuttingDown | |||
stateTerminate | |||
) | |||
// There are three places that could inherit sockets: | |||
// | |||
// * HTTP or HTTPS main listener | |||
// * HTTP redirection fallback | |||
// * SSH | |||
// | |||
// If you add an additional place you must increment this number | |||
// and add a function to call manager.InformCleanup if it's not going to be used | |||
const numberOfServersToCreate = 3 | |||
// Manager represents the graceful server manager interface | |||
var Manager *gracefulManager | |||
func init() { | |||
Manager = newGracefulManager() | |||
} | |||
func (g *gracefulManager) doShutdown() { | |||
if !g.setStateTransition(stateRunning, stateShuttingDown) { | |||
return | |||
} | |||
g.lock.Lock() | |||
close(g.shutdown) | |||
g.lock.Unlock() | |||
if setting.GracefulHammerTime >= 0 { | |||
go g.doHammerTime(setting.GracefulHammerTime) | |||
} | |||
go func() { | |||
g.WaitForServers() | |||
<-time.After(1 * time.Second) | |||
g.doTerminate() | |||
}() | |||
} | |||
func (g *gracefulManager) doHammerTime(d time.Duration) { | |||
time.Sleep(d) | |||
select { | |||
case <-g.hammer: | |||
default: | |||
log.Warn("Setting Hammer condition") | |||
close(g.hammer) | |||
} | |||
} | |||
func (g *gracefulManager) doTerminate() { | |||
if !g.setStateTransition(stateShuttingDown, stateTerminate) { | |||
return | |||
} | |||
g.lock.Lock() | |||
close(g.terminate) | |||
g.lock.Unlock() | |||
} | |||
// IsChild returns if the current process is a child of previous Gitea process | |||
func (g *gracefulManager) IsChild() bool { | |||
return g.isChild | |||
} | |||
// IsShutdown returns a channel which will be closed at shutdown. | |||
// The order of closure is IsShutdown, IsHammer (potentially), IsTerminate | |||
func (g *gracefulManager) IsShutdown() <-chan struct{} { | |||
g.lock.RLock() | |||
if g.shutdown == nil { | |||
g.lock.RUnlock() | |||
g.lock.Lock() | |||
if g.shutdown == nil { | |||
g.shutdown = make(chan struct{}) | |||
} | |||
defer g.lock.Unlock() | |||
return g.shutdown | |||
} | |||
defer g.lock.RUnlock() | |||
return g.shutdown | |||
} | |||
// IsHammer returns a channel which will be closed at hammer | |||
// The order of closure is IsShutdown, IsHammer (potentially), IsTerminate | |||
// Servers running within the running server wait group should respond to IsHammer | |||
// if not shutdown already | |||
func (g *gracefulManager) IsHammer() <-chan struct{} { | |||
g.lock.RLock() | |||
if g.hammer == nil { | |||
g.lock.RUnlock() | |||
g.lock.Lock() | |||
if g.hammer == nil { | |||
g.hammer = make(chan struct{}) | |||
} | |||
defer g.lock.Unlock() | |||
return g.hammer | |||
} | |||
defer g.lock.RUnlock() | |||
return g.hammer | |||
} | |||
// IsTerminate returns a channel which will be closed at terminate | |||
// The order of closure is IsShutdown, IsHammer (potentially), IsTerminate | |||
// IsTerminate will only close once all running servers have stopped | |||
func (g *gracefulManager) IsTerminate() <-chan struct{} { | |||
g.lock.RLock() | |||
if g.terminate == nil { | |||
g.lock.RUnlock() | |||
g.lock.Lock() | |||
if g.terminate == nil { | |||
g.terminate = make(chan struct{}) | |||
} | |||
defer g.lock.Unlock() | |||
return g.terminate | |||
} | |||
defer g.lock.RUnlock() | |||
return g.terminate | |||
} | |||
// ServerDone declares a running server done and subtracts one from the | |||
// running server wait group. Users probably do not want to call this | |||
// and should use one of the RunWithShutdown* functions | |||
func (g *gracefulManager) ServerDone() { | |||
g.runningServerWaitGroup.Done() | |||
} | |||
// WaitForServers waits for all running servers to finish. Users should probably | |||
// instead use AtTerminate or IsTerminate | |||
func (g *gracefulManager) WaitForServers() { | |||
g.runningServerWaitGroup.Wait() | |||
} | |||
// WaitForTerminate waits for all terminating actions to finish. | |||
// Only the main go-routine should use this | |||
func (g *gracefulManager) WaitForTerminate() { | |||
g.terminateWaitGroup.Wait() | |||
} | |||
func (g *gracefulManager) getState() state { | |||
g.lock.RLock() | |||
defer g.lock.RUnlock() | |||
return g.state | |||
} | |||
func (g *gracefulManager) setStateTransition(old, new state) bool { | |||
if old != g.getState() { | |||
return false | |||
} | |||
g.lock.Lock() | |||
if g.state != old { | |||
g.lock.Unlock() | |||
return false | |||
} | |||
g.state = new | |||
g.lock.Unlock() | |||
return true | |||
} | |||
func (g *gracefulManager) setState(st state) { | |||
g.lock.Lock() | |||
defer g.lock.Unlock() | |||
g.state = st | |||
} | |||
// InformCleanup tells the cleanup wait group that we have either taken a listener | |||
// or will not be taking a listener | |||
func (g *gracefulManager) InformCleanup() { | |||
g.createServerWaitGroup.Done() | |||
} |
@ -0,0 +1,141 @@ | |||
// +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. | |||
package graceful | |||
import ( | |||
"errors" | |||
"os" | |||
"os/signal" | |||
"sync" | |||
"syscall" | |||
"time" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/setting" | |||
) | |||
type gracefulManager struct { | |||
isChild bool | |||
forked bool | |||
lock *sync.RWMutex | |||
state state | |||
shutdown chan struct{} | |||
hammer chan struct{} | |||
terminate chan struct{} | |||
runningServerWaitGroup sync.WaitGroup | |||
createServerWaitGroup sync.WaitGroup | |||
terminateWaitGroup sync.WaitGroup | |||
} | |||
func newGracefulManager() *gracefulManager { | |||
manager := &gracefulManager{ | |||
isChild: len(os.Getenv(listenFDs)) > 0 && os.Getppid() > 1, | |||
lock: &sync.RWMutex{}, | |||
} | |||
manager.createServerWaitGroup.Add(numberOfServersToCreate) | |||
manager.Run() | |||
return manager | |||
} | |||
func (g *gracefulManager) Run() { | |||
g.setState(stateRunning) | |||
go g.handleSignals() | |||
c := make(chan struct{}) | |||
go func() { | |||
defer close(c) | |||
// Wait till we're done getting all of the listeners and then close | |||
// the unused ones | |||
g.createServerWaitGroup.Wait() | |||
// Ignore the error here there's not much we can do with it | |||
// They're logged in the CloseProvidedListeners function | |||
_ = CloseProvidedListeners() | |||
}() | |||
if setting.StartupTimeout > 0 { | |||
go func() { | |||
select { | |||
case <-c: | |||
return | |||
case <-g.IsShutdown(): | |||
return | |||
case <-time.After(setting.StartupTimeout): | |||
log.Error("Startup took too long! Shutting down") | |||
g.doShutdown() | |||
} | |||
}() | |||
} | |||
} | |||
func (g *gracefulManager) handleSignals() { | |||
var sig os.Signal | |||
signalChannel := make(chan os.Signal, 1) | |||
signal.Notify( | |||
signalChannel, | |||
syscall.SIGHUP, | |||
syscall.SIGUSR1, | |||
syscall.SIGUSR2, | |||
syscall.SIGINT, | |||
syscall.SIGTERM, | |||
syscall.SIGTSTP, | |||
) | |||
pid := syscall.Getpid() | |||
for { | |||
sig = <-signalChannel | |||
switch sig { | |||
case syscall.SIGHUP: | |||
if setting.GracefulRestartable { | |||
log.Info("PID: %d. Received SIGHUP. Forking...", pid) | |||
err := g.doFork() | |||
if err != nil && err.Error() != "another process already forked. Ignoring this one" { | |||
log.Error("Error whilst forking from PID: %d : %v", pid, err) | |||
} | |||
} else { | |||
log.Info("PID: %d. Received SIGHUP. Not set restartable. Shutting down...", pid) | |||
g.doShutdown() | |||
} | |||
case syscall.SIGUSR1: | |||
log.Info("PID %d. Received SIGUSR1.", pid) | |||
case syscall.SIGUSR2: | |||
log.Warn("PID %d. Received SIGUSR2. Hammering...", pid) | |||
g.doHammerTime(0 * time.Second) | |||
case syscall.SIGINT: | |||
log.Warn("PID %d. Received SIGINT. Shutting down...", pid) | |||
g.doShutdown() | |||
case syscall.SIGTERM: | |||
log.Warn("PID %d. Received SIGTERM. Shutting down...", pid) | |||
g.doShutdown() | |||
case syscall.SIGTSTP: | |||
log.Info("PID %d. Received SIGTSTP.", pid) | |||
default: | |||
log.Info("PID %d. Received %v.", pid, sig) | |||
} | |||
} | |||
} | |||
func (g *gracefulManager) doFork() error { | |||
g.lock.Lock() | |||
if g.forked { | |||
g.lock.Unlock() | |||
return errors.New("another process already forked. Ignoring this one") | |||
} | |||
g.forked = true | |||
g.lock.Unlock() | |||
// We need to move the file logs to append pids | |||
setting.RestartLogsWithPIDSuffix() | |||
_, err := RestartProcess() | |||
return err | |||
} | |||
func (g *gracefulManager) RegisterServer() { | |||
KillParent() | |||
g.runningServerWaitGroup.Add(1) | |||
} |
@ -0,0 +1,162 @@ | |||
// +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 ( | |||
"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" | |||
) | |||
var WindowsServiceName = "gitea" | |||
const ( | |||
hammerCode = 128 | |||
hammerCmd = svc.Cmd(hammerCode) | |||
acceptHammerCode = svc.Accepted(hammerCode) | |||
) | |||
type gracefulManager struct { | |||
isChild bool | |||
lock *sync.RWMutex | |||
state state | |||
shutdown chan struct{} | |||
hammer chan struct{} | |||
terminate chan struct{} | |||
runningServerWaitGroup sync.WaitGroup | |||
createServerWaitGroup sync.WaitGroup | |||
terminateWaitGroup sync.WaitGroup | |||
} | |||
func newGracefulManager() *gracefulManager { | |||
manager := &gracefulManager{ | |||
isChild: false, | |||
lock: &sync.RWMutex{}, | |||
} | |||
manager.createServerWaitGroup.Add(numberOfServersToCreate) | |||
manager.Run() | |||
return manager | |||
} | |||
func (g *gracefulManager) Run() { | |||
g.setState(stateRunning) | |||
if skip, _ := strconv.ParseBool(os.Getenv("SKIP_MINWINSVC")); skip { | |||
return | |||
} | |||
run := svc.Run | |||
isInteractive, err := svc.IsAnInteractiveSession() | |||
if err != nil { | |||
log.Error("Unable to ascertain if running as an Interactive Session: %v", err) | |||
return | |||
} | |||
if isInteractive { | |||
run = debug.Run | |||
} | |||
go run(WindowsServiceName, g) | |||
} | |||
// Execute makes gracefulManager implement svc.Handler | |||
func (g *gracefulManager) 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} | |||
} else { | |||
status <- svc.Status{State: svc.StartPending, WaitHint: uint32(setting.StartupTimeout/time.Millisecond)} | |||
} | |||
// 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: | |||
for change := range changes { | |||
switch change.Cmd { | |||
case svc.Interrogate: | |||
status <- change.CurrentStatus | |||
case svc.Stop, svc.Shutdown: | |||
g.doShutdown() | |||
waitTime += setting.GracefulHammerTime | |||
break loop | |||
case hammerCode: | |||
g.doShutdown() | |||
g.doHammerTime(0 *time.Second) | |||
break loop | |||
default: | |||
log.Debug("Unexpected control request: %v", change.Cmd) | |||
} | |||
} | |||
status <- svc.Status{ | |||
State: svc.StopPending, | |||
WaitHint: uint32(waitTime/time.Millisecond), | |||
} | |||
hammerLoop: | |||
for { | |||
select { | |||
case change := <-changes: | |||
switch change.Cmd { | |||
case svc.Interrogate: | |||
status <- change.CurrentStatus | |||
case svc.Stop, svc.Shutdown, hammerCmd: | |||
g.doHammerTime(0 * time.Second) | |||
break hammerLoop | |||
default: | |||
log.Debug("Unexpected control request: %v", change.Cmd) | |||
} | |||
case <-g.hammer: | |||
break hammerLoop | |||
} | |||
} | |||
return false, 0 | |||
} | |||
func (g *gracefulManager) RegisterServer() { | |||
g.runningServerWaitGroup.Add(1) | |||
} | |||
func (g *gracefulManager) awaitServer(limit time.Duration) bool { | |||
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 | |||
} | |||
} | |||
} |
@ -0,0 +1,19 @@ | |||
// +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 "net" | |||
// GetListener obtains a listener for the local network address. | |||
// On windows this is basically just a shim around net.Listen. | |||
func GetListener(network, address string) (net.Listener, error) { | |||
// Add a deferral to say that we've tried to grab a listener | |||
defer Manager.InformCleanup() | |||
return net.Listen(network, address) | |||
} |
@ -1,95 +0,0 @@ | |||
// +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. | |||
package graceful | |||
import ( | |||
"os" | |||
"os/signal" | |||
"syscall" | |||
"time" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/setting" | |||
) | |||
var hookableSignals []os.Signal | |||
func init() { | |||
hookableSignals = []os.Signal{ | |||
syscall.SIGHUP, | |||
syscall.SIGUSR1, | |||
syscall.SIGUSR2, | |||
syscall.SIGINT, | |||
syscall.SIGTERM, | |||
syscall.SIGTSTP, | |||
} | |||
} | |||
// handleSignals listens for os Signals and calls any hooked in function that the | |||
// user had registered with the signal. | |||
func (srv *Server) handleSignals() { | |||
var sig os.Signal | |||
signal.Notify( | |||
srv.sigChan, | |||
hookableSignals..., | |||
) | |||
pid := syscall.Getpid() | |||
for { | |||
sig = <-srv.sigChan | |||
srv.preSignalHooks(sig) | |||
switch sig { | |||
case syscall.SIGHUP: | |||
if setting.GracefulRestartable { | |||
log.Info("PID: %d. Received SIGHUP. Forking...", pid) | |||
err := srv.fork() | |||
if err != nil && err.Error() != "another process already forked. Ignoring this one" { | |||
log.Error("Error whilst forking from PID: %d : %v", pid, err) | |||
} | |||
} else { | |||
log.Info("PID: %d. Received SIGHUP. Not set restartable. Shutting down...", pid) | |||
srv.shutdown() | |||
} | |||
case syscall.SIGUSR1: | |||
log.Info("PID %d. Received SIGUSR1.", pid) | |||
case syscall.SIGUSR2: | |||
log.Warn("PID %d. Received SIGUSR2. Hammering...", pid) | |||
srv.hammerTime(0 * time.Second) | |||
case syscall.SIGINT: | |||
log.Warn("PID %d. Received SIGINT. Shutting down...", pid) | |||
srv.shutdown() | |||
case syscall.SIGTERM: | |||
log.Warn("PID %d. Received SIGTERM. Shutting down...", pid) | |||
srv.shutdown() | |||
case syscall.SIGTSTP: | |||
log.Info("PID %d. Received SIGTSTP.") | |||
default: | |||
log.Info("PID %d. Received %v.", sig) | |||
} | |||
srv.postSignalHooks(sig) | |||
} | |||
} | |||
func (srv *Server) preSignalHooks(sig os.Signal) { | |||
if _, notSet := srv.PreSignalHooks[sig]; !notSet { | |||
return | |||
} | |||
for _, f := range srv.PreSignalHooks[sig] { | |||
f() | |||
} | |||
} | |||
func (srv *Server) postSignalHooks(sig os.Signal) { | |||
if _, notSet := srv.PostSignalHooks[sig]; !notSet { | |||
return | |||
} | |||
for _, f := range srv.PostSignalHooks[sig] { | |||
f() | |||
} | |||
} |
@ -1,20 +0,0 @@ | |||
Copyright (c) 2015 Daniel Theophanes | |||
This software is provided 'as-is', without any express or implied | |||
warranty. In no event will the authors be held liable for any damages | |||
arising from the use of this software. | |||
Permission is granted to anyone to use this software for any purpose, | |||
including commercial applications, and to alter it and redistribute it | |||
freely, subject to the following restrictions: | |||
1. The origin of this software must not be misrepresented; you must not | |||
claim that you wrote the original software. If you use this software | |||
in a product, an acknowledgment in the product documentation would be | |||
appreciated but is not required. | |||
2. Altered source versions must be plainly marked as such, and must not be | |||
misrepresented as being the original software. | |||
3. This notice may not be removed or altered from any source | |||
distribution. |
@ -1,18 +0,0 @@ | |||
### Minimal windows service stub | |||
Programs designed to run from most *nix style operating systems | |||
can import this package to enable running programs as services without modifying | |||
them. | |||
``` | |||
import _ "github.com/kardianos/minwinsvc" | |||
``` | |||
If you need more control over the exit behavior, set | |||
``` | |||
minwinsvc.SetOnExit(func() { | |||
// Do something. | |||
// Within 10 seconds call: | |||
os.Exit(0) | |||
}) | |||
``` |
@ -1,18 +0,0 @@ | |||
// Copyright 2015 Daniel Theophanes. | |||
// Use of this source code is governed by a zlib-style | |||
// license that can be found in the LICENSE file.package service | |||
// Package minwinsvc is a minimal non-invasive windows only service stub. | |||
// | |||
// Import to allow running as a windows service. | |||
// import _ "github.com/kardianos/minwinsvc" | |||
// This will detect if running as a windows service | |||
// and install required callbacks for windows. | |||
package minwinsvc | |||
// SetOnExit sets the function to be called when the windows service | |||
// requests an exit. If this is not called, or if it is called where | |||
// f == nil, then it defaults to calling "os.Exit(0)". | |||
func SetOnExit(f func()) { | |||
setOnExit(f) | |||
} |
@ -1,11 +0,0 @@ | |||
// Copyright 2015 Daniel Theophanes. | |||
// Use of this source code is governed by a zlib-style | |||
// license that can be found in the LICENSE file.package service | |||
//+build !windows | |||
package minwinsvc | |||
func setOnExit(f func()) { | |||
// Nothing. | |||
} |
@ -1,76 +0,0 @@ | |||
// Copyright 2015 Daniel Theophanes. | |||
// Use of this source code is governed by a zlib-style | |||
// license that can be found in the LICENSE file.package service | |||
//+build windows | |||
package minwinsvc | |||
import ( | |||
"os" | |||
"strconv" | |||
"sync" | |||
"golang.org/x/sys/windows/svc" | |||
) | |||
var ( | |||
onExit func() | |||
guard sync.Mutex | |||
skip, _ = strconv.ParseBool(os.Getenv("SKIP_MINWINSVC")) | |||
isSSH = os.Getenv("SSH_ORIGINAL_COMMAND") != "" | |||
) | |||
func init() { | |||
if skip || isSSH { | |||
return | |||
} | |||
interactive, err := svc.IsAnInteractiveSession() | |||
if err != nil { | |||
panic(err) | |||
} | |||
if interactive { | |||
return | |||
} | |||
go func() { | |||
_ = svc.Run("", runner{}) | |||
guard.Lock() | |||
f := onExit | |||
guard.Unlock() | |||
// Don't hold this lock in user code. | |||
if f != nil { | |||
f() | |||
} | |||
// Make sure we exit. | |||
os.Exit(0) | |||
}() | |||
} | |||
func setOnExit(f func()) { | |||
guard.Lock() | |||
onExit = f | |||
guard.Unlock() | |||
} | |||
type runner struct{} | |||
func (runner) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (bool, uint32) { | |||
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | |||
changes <- svc.Status{State: svc.StartPending} | |||
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} | |||
for { | |||
c := <-r | |||
switch c.Cmd { | |||
case svc.Interrogate: | |||
changes <- c.CurrentStatus | |||
case svc.Stop, svc.Shutdown: | |||
changes <- svc.Status{State: svc.StopPending} | |||
return false, 0 | |||
} | |||
} | |||
return false, 0 | |||
} |
@ -1,24 +0,0 @@ | |||
// +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. | |||
package ssh | |||
import ( | |||
"code.gitea.io/gitea/modules/log" | |||
"github.com/gliderlabs/ssh" | |||
) | |||
func listen(server *ssh.Server) { | |||
err := server.ListenAndServe() | |||
if err != nil { | |||
log.Critical("Failed to serve with builtin SSH server. %s", err) | |||
} | |||
} | |||
// Unused does nothing on windows | |||
func Unused() { | |||
// Do nothing | |||
} |
@ -0,0 +1,56 @@ | |||
// Copyright 2012 The Go Authors. All rights reserved. | |||
// Use of this source code is governed by a BSD-style | |||
// license that can be found in the LICENSE file. | |||
// +build windows | |||
package debug | |||
import ( | |||
"os" | |||
"strconv" | |||
) | |||
// Log interface allows different log implementations to be used. | |||
type Log interface { | |||
Close() error | |||
Info(eid uint32, msg string) error | |||
Warning(eid uint32, msg string) error | |||
Error(eid uint32, msg string) error | |||
} | |||
// ConsoleLog provides access to the console. | |||
type ConsoleLog struct { | |||
Name string | |||
} | |||
// New creates new ConsoleLog. | |||
func New(source string) *ConsoleLog { | |||
return &ConsoleLog{Name: source} | |||
} | |||
// Close closes console log l. | |||
func (l *ConsoleLog) Close() error { | |||
return nil | |||
} | |||
func (l *ConsoleLog) report(kind string, eid uint32, msg string) error { | |||
s := l.Name + "." + kind + "(" + strconv.Itoa(int(eid)) + "): " + msg + "\n" | |||
_, err := os.Stdout.Write([]byte(s)) | |||
return err | |||
} | |||
// Info writes an information event msg with event id eid to the console l. | |||
func (l *ConsoleLog) Info(eid uint32, msg string) error { | |||
return l.report("info", eid, msg) | |||
} | |||
// Warning writes an warning event msg with event id eid to the console l. | |||
func (l *ConsoleLog) Warning(eid uint32, msg string) error { | |||
return l.report("warn", eid, msg) | |||
} | |||
// Error writes an error event msg with event id eid to the console l. | |||
func (l *ConsoleLog) Error(eid uint32, msg string) error { | |||
return l.report("error", eid, msg) | |||
} |
@ -0,0 +1,45 @@ | |||
// Copyright 2012 The Go Authors. All rights reserved. | |||
// Use of this source code is governed by a BSD-style | |||
// license that can be found in the LICENSE file. | |||
// +build windows | |||
// Package debug provides facilities to execute svc.Handler on console. | |||
// | |||
package debug | |||
import ( | |||
"os" | |||
"os/signal" | |||
"syscall" | |||
"golang.org/x/sys/windows/svc" | |||
) | |||
// Run executes service name by calling appropriate handler function. | |||
// The process is running on console, unlike real service. Use Ctrl+C to | |||
// send "Stop" command to your service. | |||
func Run(name string, handler svc.Handler) error { | |||
cmds := make(chan svc.ChangeRequest) | |||
changes := make(chan svc.Status) | |||
sig := make(chan os.Signal) | |||
signal.Notify(sig) | |||
go func() { | |||
status := svc.Status{State: svc.Stopped} | |||
for { | |||
select { | |||
case <-sig: | |||
cmds <- svc.ChangeRequest{Cmd: svc.Stop, CurrentStatus: status} | |||
case status = <-changes: | |||
} | |||
} | |||
}() | |||
_, errno := handler.Execute([]string{name}, cmds, changes) | |||
if errno != 0 { | |||
return syscall.Errno(errno) | |||
} | |||
return nil | |||
} |