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
*.exe
*.exe~
# Editor files
.vscode

View File

@ -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
}

112
main.go
View File

@ -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 <f|b> <command...>\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
}