Main: Change backend reload system

This commit is contained in:
mkwcat 2024-05-07 23:31:23 -04:00
parent c6927a3cb6
commit b952c9e386
No known key found for this signature in database
GPG Key ID: 7A505679CE9E7AA9
3 changed files with 75 additions and 66 deletions

1
.gitignore vendored
View File

@ -19,6 +19,7 @@ state
# Executables # Executables
*.exe *.exe
*.exe~
# Editor files # Editor files
.vscode .vscode

View File

@ -18,7 +18,7 @@ func ConnectFrontend() {
var err error var err error
rpcFrontend, err = rpc.Dial("tcp", "localhost:29998") rpcFrontend, err = rpc.Dial("tcp", "localhost:29998")
if err != nil { if err != nil {
logging.Error("BACKEND", "Failed to connect to frontend:", err) panic(err)
} }
} }
@ -47,3 +47,29 @@ func CloseConnection(server string, index uint64) error {
} }
return err return err
} }
// Ready will notify the frontend that the backend is ready to accept connections
func Ready() error {
if rpcFrontend == nil {
ConnectFrontend()
}
err := rpcFrontend.Call("RPCFrontendPacket.Ready", struct{}{}, nil)
if err != nil {
logging.Error("COMMON", "Failed to notify frontend that backend is ready:", err)
}
return err
}
// Shutdown will notify the frontend that the backend is shutting down
func Shutdown() error {
if rpcFrontend == nil {
ConnectFrontend()
}
err := rpcFrontend.Call("RPCFrontendPacket.ShutdownBackend", struct{}{}, nil)
if err != nil {
logging.Error("COMMON", "Failed to notify frontend that backend is shutting down:", err)
}
return err
}

112
main.go
View File

@ -2,14 +2,15 @@ package main
import ( import (
"errors" "errors"
"fmt"
"net" "net"
"net/rpc" "net/rpc"
"os" "os"
"os/exec" "os/exec"
"os/signal"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"syscall"
"wwfc/api" "wwfc/api"
"wwfc/common" "wwfc/common"
"wwfc/gamestats" "wwfc/gamestats"
@ -25,7 +26,9 @@ import (
"github.com/logrusorgru/aurora/v3" "github.com/logrusorgru/aurora/v3"
) )
var config = common.GetConfig() var (
config = common.GetConfig()
)
func main() { func main() {
logging.SetLevel(*config.LogLevel) logging.SetLevel(*config.LogLevel)
@ -38,10 +41,8 @@ func main() {
// Start the backend instead of the frontend if the first argument is "backend" // Start the backend instead of the frontend if the first argument is "backend"
if len(args) > 0 && args[0] == "backend" { if len(args) > 0 && args[0] == "backend" {
backendMain(len(args) > 1 && args[1] == "reload") backendMain(len(args) > 1 && args[1] == "reload")
} else if len(args) > 0 && args[0] == "cmd" {
handleCommand(args[1:])
} else { } else {
frontendMain(len(args) > 0 && args[0] == "skipbackend") frontendMain(len(args) > 0 && args[0] == "frontend")
} }
} }
@ -54,6 +55,9 @@ type RPCPacket struct {
// backendMain starts all the servers and creates an RPC server to communicate with the frontend // backendMain starts all the servers and creates an RPC server to communicate with the frontend
func backendMain(reload bool) { func backendMain(reload bool) {
sigExit := make(chan os.Signal, 1)
signal.Notify(sigExit, syscall.SIGINT, syscall.SIGTERM)
if err := logging.SetOutput(config.LogOutput); err != nil { if err := logging.SetOutput(config.LogOutput); err != nil {
logging.Error("BACKEND", err) logging.Error("BACKEND", err)
} }
@ -96,8 +100,17 @@ func backendMain(reload bool) {
logging.Notice("BACKEND", "Listening on", aurora.BrightCyan(address)) logging.Notice("BACKEND", "Listening on", aurora.BrightCyan(address))
// Prevent application from exiting common.Ready()
select {}
// Wait for a signal to shutdown
<-sigExit
err = common.Shutdown()
if err != nil {
panic(err)
}
(&RPCPacket{}).Shutdown(struct{}{}, &struct{}{})
} }
// RPCPacket.NewConnection is called by the frontend to notify the backend of a new connection // RPCPacket.NewConnection is called by the frontend to notify the backend of a new connection
@ -183,12 +196,13 @@ var (
rpcMutex sync.Mutex rpcMutex sync.Mutex
rpcBusyCount sync.WaitGroup rpcBusyCount sync.WaitGroup
backendReady = make(chan struct{})
connections = map[string]map[uint64]net.Conn{} connections = map[string]map[uint64]net.Conn{}
) )
// frontendMain starts the backend process and communicates with it using RPC // frontendMain starts the backend process and communicates with it using RPC
func frontendMain(skipBackend bool) { func frontendMain(noBackend bool) {
// Don't allow the frontend to output to a file (there's no reason to) // Don't allow the frontend to output to a file (there's no reason to)
logOutput := config.LogOutput logOutput := config.LogOutput
if logOutput == "StdOutAndFile" { if logOutput == "StdOutAndFile" {
@ -203,7 +217,7 @@ func frontendMain(skipBackend bool) {
startFrontendServer() startFrontendServer()
if !skipBackend { if !noBackend {
go startBackendProcess(false, true) go startBackendProcess(false, true)
} else { } else {
go waitForBackend() go waitForBackend()
@ -285,11 +299,16 @@ func startBackendProcess(reload bool, wait bool) {
// waitForBackend waits for the backend to start. // waitForBackend waits for the backend to start.
// Expects the RPC mutex to be locked. // Expects the RPC mutex to be locked.
func waitForBackend() { func waitForBackend() {
<-backendReady
backendReady = make(chan struct{})
for { for {
client, err := rpc.Dial("tcp", "localhost:29999") client, err := rpc.Dial("tcp", "localhost:29999")
if err == nil { if err == nil {
rpcClient = client rpcClient = client
rpcMutex.Unlock() rpcMutex.Unlock()
logging.Notice("FRONTEND", "Connected to backend")
return return
} }
} }
@ -431,19 +450,6 @@ func (r *RPCFrontendPacket) CloseConnection(args RPCFrontendPacket, _ *struct{})
func (r *RPCFrontendPacket) ReloadBackend(_ struct{}, _ *struct{}) error { func (r *RPCFrontendPacket) ReloadBackend(_ struct{}, _ *struct{}) error {
r.ShutdownBackend(struct{}{}, &struct{}{}) r.ShutdownBackend(struct{}{}, &struct{}{})
// Unlocks the mutex locked by ShutdownBackend
startBackendProcess(true, false)
return nil
}
// RPCFrontendPacket.ShutdownBackend is called by an external program to shutdown the backend
func (r *RPCFrontendPacket) ShutdownBackend(_ struct{}, _ *struct{}) error {
// Lock indefinitely
rpcMutex.Lock()
rpcBusyCount.Wait()
err := rpcClient.Call("RPCPacket.Shutdown", struct{}{}, nil) err := rpcClient.Call("RPCPacket.Shutdown", struct{}{}, nil)
if err != nil && !strings.Contains(err.Error(), "An existing connection was forcibly closed by the remote host.") { if err != nil && !strings.Contains(err.Error(), "An existing connection was forcibly closed by the remote host.") {
logging.Error("FRONTEND", "Failed to reload backend:", err) logging.Error("FRONTEND", "Failed to reload backend:", err)
@ -454,52 +460,28 @@ func (r *RPCFrontendPacket) ShutdownBackend(_ struct{}, _ *struct{}) error {
logging.Error("FRONTEND", "Failed to close RPC client:", err) logging.Error("FRONTEND", "Failed to close RPC client:", err)
} }
// Unlocks the mutex locked by ShutdownBackend
startBackendProcess(true, true)
return nil
}
// RPCFrontendPacket.ShutdownBackend is called by an external program to shutdown the backend
func (r *RPCFrontendPacket) ShutdownBackend(_ struct{}, _ *struct{}) error {
logging.Notice("FRONTEND", "Shutting down backend")
// Lock indefinitely
rpcMutex.Lock()
rpcBusyCount.Wait()
go waitForBackend() go waitForBackend()
return nil return nil
} }
// handleCommand is used to send a command to the backend // RPCFrontendPacket.Ready is called by the backend to indicate it is ready to accept connections
func handleCommand(args []string) { func (r *RPCFrontendPacket) Ready(_ struct{}, _ *struct{}) error {
if len(args) < 2 { close(backendReady)
fmt.Printf("Usage: %s cmd <f|b> <command...>\n", os.Args[0]) return nil
return
}
var client *rpc.Client
var err error
if args[0] == "f" {
client, err = rpc.Dial("tcp", "localhost:29998")
} else if args[0] == "b" {
client, err = rpc.Dial("tcp", "localhost:29999")
} else {
fmt.Printf("Unknown command type: '%s', please supply 'f' or 'b' (for frontend or backend)\n", args[0])
return
}
if err != nil {
fmt.Println("Failed to connect to RPC server:", err)
return
}
defer client.Close()
if args[0] == "b" {
fmt.Printf("Unknown backend command: '%s'\n", args[1])
} else {
if args[1] == "backend" {
if len(args) > 2 && args[2] == "shutdown" {
err = client.Call("RPCFrontendPacket.ShutdownBackend", struct{}{}, nil)
} else {
err = client.Call("RPCFrontendPacket.ReloadBackend", struct{}{}, nil)
}
} else {
fmt.Printf("Unknown frontend command: '%s'\n", args[1])
}
}
if err != nil {
fmt.Println("Failed to send command:", err)
}
} }