From b952c9e38628a631480301dbb9a7e85886b471e3 Mon Sep 17 00:00:00 2001 From: mkwcat Date: Tue, 7 May 2024 23:31:23 -0400 Subject: [PATCH] Main: Change backend reload system --- .gitignore | 1 + common/connection.go | 28 ++++++++++- main.go | 112 ++++++++++++++++++------------------------- 3 files changed, 75 insertions(+), 66 deletions(-) diff --git a/.gitignore b/.gitignore index a64e8eb..f733bca 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ state # Executables *.exe +*.exe~ # Editor files .vscode diff --git a/common/connection.go b/common/connection.go index 736bdf7..d0ad84b 100644 --- a/common/connection.go +++ b/common/connection.go @@ -18,7 +18,7 @@ func ConnectFrontend() { var err error rpcFrontend, err = rpc.Dial("tcp", "localhost:29998") 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 } + +// 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 +} diff --git a/main.go b/main.go index 6c5a711..3eeff01 100644 --- a/main.go +++ b/main.go @@ -2,14 +2,15 @@ package main import ( "errors" - "fmt" "net" "net/rpc" "os" "os/exec" + "os/signal" "strconv" "strings" "sync" + "syscall" "wwfc/api" "wwfc/common" "wwfc/gamestats" @@ -25,7 +26,9 @@ import ( "github.com/logrusorgru/aurora/v3" ) -var config = common.GetConfig() +var ( + config = common.GetConfig() +) func main() { logging.SetLevel(*config.LogLevel) @@ -38,10 +41,8 @@ func main() { // Start the backend instead of the frontend if the first argument is "backend" if len(args) > 0 && args[0] == "backend" { backendMain(len(args) > 1 && args[1] == "reload") - } else if len(args) > 0 && args[0] == "cmd" { - handleCommand(args[1:]) } 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 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 { logging.Error("BACKEND", err) } @@ -96,8 +100,17 @@ func backendMain(reload bool) { logging.Notice("BACKEND", "Listening on", aurora.BrightCyan(address)) - // Prevent application from exiting - select {} + common.Ready() + + // 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 @@ -183,12 +196,13 @@ var ( rpcMutex sync.Mutex rpcBusyCount sync.WaitGroup + backendReady = make(chan struct{}) connections = map[string]map[uint64]net.Conn{} ) // 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) logOutput := config.LogOutput if logOutput == "StdOutAndFile" { @@ -203,7 +217,7 @@ func frontendMain(skipBackend bool) { startFrontendServer() - if !skipBackend { + if !noBackend { go startBackendProcess(false, true) } else { go waitForBackend() @@ -285,11 +299,16 @@ func startBackendProcess(reload bool, wait bool) { // waitForBackend waits for the backend to start. // Expects the RPC mutex to be locked. func waitForBackend() { + <-backendReady + backendReady = make(chan struct{}) + for { client, err := rpc.Dial("tcp", "localhost:29999") if err == nil { rpcClient = client rpcMutex.Unlock() + + logging.Notice("FRONTEND", "Connected to backend") return } } @@ -431,19 +450,6 @@ func (r *RPCFrontendPacket) CloseConnection(args RPCFrontendPacket, _ *struct{}) func (r *RPCFrontendPacket) ReloadBackend(_ struct{}, _ *struct{}) error { 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) 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) @@ -454,52 +460,28 @@ func (r *RPCFrontendPacket) ShutdownBackend(_ struct{}, _ *struct{}) error { 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() return nil } -// handleCommand is used to send a command to the backend -func handleCommand(args []string) { - if len(args) < 2 { - fmt.Printf("Usage: %s cmd \n", os.Args[0]) - 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) - } +// RPCFrontendPacket.Ready is called by the backend to indicate it is ready to accept connections +func (r *RPCFrontendPacket) Ready(_ struct{}, _ *struct{}) error { + close(backendReady) + return nil }