Files
openfsd/bootstrap/service/fsd.go
Reese Norris 57d54d6705 v0.1.0-alpha
Changes:
- Implement bootstrapping library for managing several concurrent internal services
- Refactor concurrency model for connections/logical clients and their associated I/O
- Refactor server context singleton
- Refactor error handling
    - Most errors are now gracefully sent to the FSD client directly encoded as an $ER packet,
      enhancing visibility and debugging
    - Most errors are now rightfully treated as non-fatal
- Refactor package/dependency graph
- Refactor calling conventions/interfaces for many packages
- Refactor database package
- Refactor post office

Features:
- Add VATSIM-esque HTTP/JSON "data feed"
- Add ephemeral in-memory database option
- Add user management REST API
- Add improved web interface
- Add MySQL support (drop SQLite support)
2024-10-07 12:50:39 -07:00

117 lines
2.5 KiB
Go

package service
import (
"context"
"errors"
"github.com/renorris/openfsd/client"
"github.com/renorris/openfsd/servercontext"
"log"
"net"
"os"
"slices"
"sync"
)
type FSDService struct{}
func (s *FSDService) Start(ctx context.Context, doneErr chan<- error) (err error) {
// Set "AlwaysImmediate" to true in test cases
if slices.Contains(os.Environ(), "ALWAYS_IMMEDIATE=true") {
client.AlwaysImmediate = true
}
// Attempt to listen. Once a listener is acquired, we can guarantee that clients can connect.
var listener *net.TCPListener
if listener, err = s.resolveAndListen(); err != nil {
return err
}
log.Printf("FSD server listening on %s", servercontext.Config().FSDListenAddress)
// boot FSD on its own goroutine
go func(ctx context.Context, doneErr chan<- error, listener *net.TCPListener) {
doneErr <- s.boot(ctx, listener)
}(ctx, doneErr, listener)
return nil
}
func (s *FSDService) boot(ctx context.Context, listener *net.TCPListener) error {
defer listener.Close()
defer log.Println("FSD server shutting down...")
incomingConns := make(chan *net.TCPConn)
go acceptorWorker(ctx, listener, incomingConns)
// Track each client connection in a wait group
waitGroup := sync.WaitGroup{}
for {
select {
// Handle incoming connections
case conn, ok := <-incomingConns:
if !ok {
return errors.New("listener closed")
}
waitGroup.Add(1)
go func(ctx context.Context, conn *net.TCPConn) {
defer waitGroup.Done()
// Recover from a panic
defer func() {
if err := recover(); err != nil {
log.Println("panic occurred:", err)
}
}()
var connection *client.Connection
var err error
if connection, err = client.NewConnection(ctx, conn); err != nil {
return
}
connection.Start()
}(ctx, conn)
// Wait for cleanup on context done
case <-ctx.Done():
waitGroup.Wait()
return nil
}
}
}
func acceptorWorker(ctx context.Context, listener *net.TCPListener, conns chan<- *net.TCPConn) {
defer close(conns)
for {
conn, err := listener.AcceptTCP()
if err != nil {
return
}
select {
case conns <- conn:
case <-ctx.Done():
return
}
}
}
func (s *FSDService) resolveAndListen() (listener *net.TCPListener, err error) {
addr, err := net.ResolveTCPAddr("tcp4", servercontext.Config().FSDListenAddress)
if err != nil {
return nil, err
}
listener, err = net.ListenTCP("tcp4", addr)
if err != nil {
log.Println(err)
return nil, err
}
return listener, nil
}