Close UART gracefully on SIGINT/SIGTERM in goTool.
Go exits on Ctrl+C without running defers, leaving the serial port locked; register shutdown hooks for serve and CLI commands. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
0eea27a876
commit
e4ce18edd8
@ -62,6 +62,8 @@ func runTest(portOverride string, baudOverride int, args []string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("open %s: %w", port, err)
|
||||
}
|
||||
registerShutdown(func() { _ = sp.Close() })
|
||||
enableShutdownOnInterrupt()
|
||||
defer sp.Close()
|
||||
|
||||
if !*verbose {
|
||||
|
||||
@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
@ -34,21 +35,29 @@ func runServe(portName string, baud int, args []string) error {
|
||||
|
||||
link := newManagedSerial(portName, baud)
|
||||
link.quiet = true
|
||||
defer link.Close()
|
||||
|
||||
hub := newWSHub()
|
||||
streamCtl := newAccelStreamCtl()
|
||||
tapCtl := newTapNotifyCtl()
|
||||
stop := make(chan struct{})
|
||||
defer close(stop)
|
||||
|
||||
var dashSrv *http.Server
|
||||
var apiSrv *http.Server
|
||||
registerShutdown(func() {
|
||||
close(stop)
|
||||
shutdownHTTPServer(dashSrv)
|
||||
shutdownAPIServer(apiSrv)
|
||||
if err := link.Close(); err != nil {
|
||||
log.Printf("UART close: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
go runPoller(link, portName, hub, streamCtl, tapCtl, *interval, stop)
|
||||
go runBatteryPoller(link, hub, 5*time.Second, stop)
|
||||
go runCacheStatusDashboardPoller(link, hub, *accelInterval, stop)
|
||||
|
||||
var apiSrv *http.Server
|
||||
if *apiAddr != "" {
|
||||
apiSrv = runAPIServer(portName, link, *apiAddr, *accelInterval, hub, streamCtl, tapCtl, stop)
|
||||
defer shutdownAPIServer(apiSrv)
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
@ -81,5 +90,14 @@ func runServe(portName string, baud int, args []string) error {
|
||||
if *apiAddr == "" {
|
||||
log.Printf("external API disabled (-api-addr \"\")")
|
||||
}
|
||||
return http.ListenAndServe(*addr, mux)
|
||||
|
||||
dashSrv = &http.Server{Addr: *addr, Handler: mux}
|
||||
go func() {
|
||||
if err := dashSrv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
log.Printf("dashboard server: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
waitForShutdown()
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -62,6 +62,8 @@ func main() {
|
||||
if err != nil {
|
||||
log.Fatalf("open serial: %v", err)
|
||||
}
|
||||
registerShutdown(func() { _ = sp.Close() })
|
||||
enableShutdownOnInterrupt()
|
||||
defer sp.Close()
|
||||
switch cmd {
|
||||
case "version":
|
||||
|
||||
75
goTool/shutdown.go
Normal file
75
goTool/shutdown.go
Normal file
@ -0,0 +1,75 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
shutdownMu sync.Mutex
|
||||
shutdownFns []func()
|
||||
hooksOnce sync.Once
|
||||
bgHandlerOnce sync.Once
|
||||
)
|
||||
|
||||
// registerShutdown runs fn on SIGINT/SIGTERM (LIFO order).
|
||||
func registerShutdown(fn func()) {
|
||||
shutdownMu.Lock()
|
||||
shutdownFns = append(shutdownFns, fn)
|
||||
shutdownMu.Unlock()
|
||||
}
|
||||
|
||||
func runShutdownHooks() {
|
||||
hooksOnce.Do(func() {
|
||||
shutdownMu.Lock()
|
||||
fns := shutdownFns
|
||||
shutdownMu.Unlock()
|
||||
for i := len(fns) - 1; i >= 0; i-- {
|
||||
fns[i]()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// enableShutdownOnInterrupt listens for SIGINT/SIGTERM in the background and exits
|
||||
// after running shutdown hooks. Use for one-shot CLI commands (OTA, etc.).
|
||||
func enableShutdownOnInterrupt() {
|
||||
bgHandlerOnce.Do(func() {
|
||||
ch := make(chan os.Signal, 1)
|
||||
signal.Notify(ch, os.Interrupt, syscall.SIGTERM)
|
||||
go func() {
|
||||
sig := <-ch
|
||||
signal.Stop(ch)
|
||||
log.Printf("received %v, shutting down…", sig)
|
||||
runShutdownHooks()
|
||||
os.Exit(0)
|
||||
}()
|
||||
})
|
||||
}
|
||||
|
||||
// waitForShutdown blocks until SIGINT/SIGTERM, runs hooks, and returns.
|
||||
// Use for long-running servers (serve/dashboard).
|
||||
func waitForShutdown() {
|
||||
ch := make(chan os.Signal, 1)
|
||||
signal.Notify(ch, os.Interrupt, syscall.SIGTERM)
|
||||
sig := <-ch
|
||||
signal.Stop(ch)
|
||||
log.Printf("received %v, shutting down…", sig)
|
||||
runShutdownHooks()
|
||||
}
|
||||
|
||||
func shutdownHTTPServer(srv *http.Server) {
|
||||
if srv == nil {
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
if err := srv.Shutdown(ctx); err != nil {
|
||||
log.Printf("HTTP shutdown: %v", err)
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user