Skip to content

Commit

Permalink
run commands with context. use go-ps to find out when server manager …
Browse files Browse the repository at this point in the history
…process has stopped
  • Loading branch information
cj123 committed Apr 3, 2019
1 parent b5b550d commit 7fc73d0
Show file tree
Hide file tree
Showing 12 changed files with 78 additions and 35 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Expand Up @@ -41,7 +41,7 @@ Added:
* Added a "Delete" group - they are the only group (other than admin) allowed to delete content, championships, races, etc.
* You can now change which assetto server executable file is run by Server Manager. By default, acServer(.exe) is used.
See config.yml "executable_path" for more information. This means tools such as ac-server-wrapper should now be
compatible with Server Manager!
compatible with Server Manager! You can even write your own wrapper scripts around acServer if you'd like.

Fixes:

Expand Down
4 changes: 4 additions & 0 deletions championship_manager_test.go
Expand Up @@ -77,6 +77,10 @@ func (dummyServerProcess) IsRunning() bool {
return true
}

func (dummyServerProcess) Done() <-chan struct{} {
return nil
}

func (dummyServerProcess) EventType() ServerEventType {
return EventTypeChampionship
}
Expand Down
4 changes: 4 additions & 0 deletions cmd/server-manager/main.go
Expand Up @@ -20,6 +20,10 @@ var debug = os.Getenv("DEBUG") == "true"

var defaultAddress = "0.0.0.0:8772"

func init() {
runtime.LockOSThread()
}

func main() {
config, err := servermanager.ReadConfig("config.yml")

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Expand Up @@ -5,7 +5,6 @@ require (
github.com/Masterminds/semver v1.4.2 // indirect
github.com/Masterminds/sprig v2.18.0+incompatible
github.com/cj123/ini v1.42.0
github.com/davecgh/go-spew v1.1.1
github.com/etcd-io/bbolt v1.3.2
github.com/fatih/camelcase v1.0.0
github.com/go-chi/chi v4.0.2+incompatible
Expand All @@ -18,6 +17,7 @@ require (
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/mattn/go-zglob v0.0.1
github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936
github.com/mitchellh/go-wordwrap v1.0.0
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4
github.com/sethvargo/go-diceware v0.0.0-20181024230814-74428ac65346
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Expand Up @@ -44,6 +44,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattn/go-zglob v0.0.1 h1:xsEx/XUoVlI6yXjqBK062zYhRTZltCNmYPx6v+8DNaY=
github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936 h1:kw1v0NlnN+GZcU8Ma8CLF2Zzgjfx95gs3/GN3vYAPpo=
github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk=
github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 h1:49lOXmGaUpV9Fz3gd7TFZY106KVlPVa5jcYD1gaQf98=
Expand Down
8 changes: 2 additions & 6 deletions live_map.go
Expand Up @@ -61,7 +61,7 @@ func (h *liveMapHub) run() {
delete(h.clients, client)
}
}
case <-serverStoppedChan:
case <-AssettoProcess.Done():
for _, client := range connectedCars {
client := client

Expand Down Expand Up @@ -115,11 +115,7 @@ func (c *liveMapClient) writePump() {
}
}

var mapHub = newLiveMapHub()

func init() {
go mapHub.run()
}
var mapHub *liveMapHub

func liveMapHandler(w http.ResponseWriter, r *http.Request) {
c, err := upgrader.Upgrade(w, r, nil)
Expand Down
2 changes: 1 addition & 1 deletion live_timings.go
Expand Up @@ -299,7 +299,7 @@ func timeTick(done <-chan struct{}) {
logrus.Errorf("Couldn't send session info udp request, err: %s", err)
}
case <-done:
logrus.Info("Closing udp request channel")
logrus.Debugf("Closing udp request channel")
tickChan.Stop()
return
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/udp/messages.go
Expand Up @@ -406,7 +406,7 @@ func (asu *AssettoServerUDP) handleMessage(r io.Reader) (Message, error) {

response = sessionInfo

if RealtimePosIntervalMs > 0 {
if RealtimePosIntervalMs > 0 && eventType == EventNewSession {
err = asu.SendMessage(NewEnableRealtimePosInterval(uint16(RealtimePosIntervalMs)))

if err != nil {
Expand Down
14 changes: 10 additions & 4 deletions race_manager.go
Expand Up @@ -24,8 +24,7 @@ import (
)

var (
raceManager *RaceManager
serverStoppedChan = make(chan struct{})
raceManager *RaceManager

ErrCustomRaceNotFound = errors.New("servermanager: custom race not found")
)
Expand All @@ -38,14 +37,17 @@ func InitWithStore(store Store) {
raceManager = NewRaceManager(store)
championshipManager = NewChampionshipManager(raceManager)
accountManager = NewAccountManager(store)
AssettoProcess = &AssettoServerProcess{doneCh: serverStoppedChan}
AssettoProcess = NewAssettoServerProcess()

err := raceManager.raceStore.GetMeta(serverAccountOptionsMetaKey, &accountOptions)

if err != nil {
logrus.WithError(err).Errorf("Could not load server account options")
}

mapHub = newLiveMapHub()
go mapHub.run()

c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)

Expand All @@ -55,7 +57,11 @@ func InitWithStore(store Store) {
if AssettoProcess.IsRunning() {
if AssettoProcess.EventType() == EventTypeChampionship {
if err := championshipManager.StopActiveEvent(); err != nil {
logrus.Errorf("Error stopping event, err: %s", err)
logrus.WithError(err).Errorf("Error stopping event")
}
} else {
if err := AssettoProcess.Stop(); err != nil {
logrus.WithError(err).Errorf("Could not stop server")
}
}

Expand Down
63 changes: 46 additions & 17 deletions server_process.go
Expand Up @@ -2,9 +2,8 @@ package servermanager

import (
"bytes"
"context"
"errors"
"github.com/go-chi/chi"
"github.com/sirupsen/logrus"
"net"
"net/http"
"os"
Expand All @@ -13,6 +12,10 @@ import (
"strings"
"sync"
"time"

"github.com/go-chi/chi"
"github.com/mitchellh/go-ps"
"github.com/sirupsen/logrus"
)

const MaxLogSizeBytes = 1e6
Expand All @@ -31,6 +34,8 @@ type ServerProcess interface {
Restart() error
IsRunning() bool
EventType() ServerEventType

Done() <-chan struct{}
}

var AssettoProcess ServerProcess
Expand Down Expand Up @@ -87,11 +92,28 @@ type AssettoServerProcess struct {
out *logBuffer
mutex sync.Mutex

ctx context.Context
cfn context.CancelFunc

doneCh chan struct{}

extraProcesses []*exec.Cmd
}

func NewAssettoServerProcess() *AssettoServerProcess {
ctx, cfn := context.WithCancel(context.Background())

return &AssettoServerProcess{
ctx: ctx,
cfn: cfn,
doneCh: make(chan struct{}),
}
}

func (as *AssettoServerProcess) Done() <-chan struct{} {
return as.doneCh
}

// Logs outputs the server logs
func (as *AssettoServerProcess) Logs() string {
if as.out == nil {
Expand Down Expand Up @@ -124,7 +146,7 @@ func (as *AssettoServerProcess) Start() error {
executablePath = filepath.Join(ServerInstallPath, config.Steam.ExecutablePath)
}

as.cmd = exec.Command(executablePath)
as.cmd = buildCommand(as.ctx, executablePath)
as.cmd.Dir = ServerInstallPath

if as.out == nil {
Expand All @@ -147,9 +169,9 @@ func (as *AssettoServerProcess) Start() error {
var cmd *exec.Cmd

if len(parts) > 1 {
cmd = buildCommand(parts[0], parts[1:]...)
cmd = buildCommand(as.ctx, parts[0], parts[1:]...)
} else {
cmd = buildCommand(parts[0])
cmd = buildCommand(as.ctx, parts[0])
}

cmd.Stdout = pluginsOutput
Expand All @@ -167,15 +189,8 @@ func (as *AssettoServerProcess) Start() error {
}

go func() {
err = as.cmd.Wait()

if err != nil {
logrus.Errorf("Wait errored: %s", err)
}

_ = as.cmd.Wait()
as.stopChildProcesses()

as.doneCh <- struct{}{}
}()

return nil
Expand Down Expand Up @@ -234,17 +249,31 @@ func (as *AssettoServerProcess) Stop() error {
as.mutex.Lock()
defer as.mutex.Unlock()

err := as.cmd.Process.Kill()
err := kill(as.cmd.Process)

if err != nil && !strings.Contains(err.Error(), "process already finished") {
return err
}

as.cmd = nil

as.stopChildProcesses()

time.Sleep(time.Millisecond * 500)
for {
proc, err := ps.FindProcess(as.cmd.Process.Pid)

if err != nil {
return err
}

if proc == nil && err == nil {
break
}

logrus.Debugf("Waiting for Assetto Corsa Server process to finish...")
time.Sleep(time.Millisecond * 500)
}

as.cmd = nil
as.doneCh <- struct{}{}

return nil
}
Expand Down
5 changes: 3 additions & 2 deletions server_process_nonwindows.go
Expand Up @@ -3,6 +3,7 @@
package servermanager

import (
"context"
"os"
"os/exec"
"syscall"
Expand Down Expand Up @@ -32,8 +33,8 @@ func kill(process *os.Process) error {
return syscall.Kill(-process.Pid, syscall.SIGKILL|syscall.SIGINT)
}

func buildCommand(command string, args ...string) *exec.Cmd {
cmd := exec.Command(command, args...)
func buildCommand(ctx context.Context, command string, args ...string) *exec.Cmd {
cmd := exec.CommandContext(ctx, command, args...)
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

return cmd
Expand Down
5 changes: 3 additions & 2 deletions server_process_windows.go
Expand Up @@ -3,6 +3,7 @@
package servermanager

import (
"context"
"fmt"
"os"
"os/exec"
Expand All @@ -23,10 +24,10 @@ func kill(process *os.Process) error {
return nil
}

func buildCommand(command string, args ...string) *exec.Cmd {
func buildCommand(ctx context.Context, command string, args ...string) *exec.Cmd {
args = append([]string{"/c", "start", "/wait", command}, args...)

cmd := exec.Command("cmd", args...)
cmd := exec.CommandContext(ctx, "cmd", args...)

return cmd
}

0 comments on commit 7fc73d0

Please sign in to comment.