fix(httpserver): bind port synchronously in OnStart so bind errors propagate
Previously ListenAndServe ran entirely in a goroutine, so a port-in-use error was only logged and the launcher received nil from OnStart — leaving the application running without an HTTP server. Replace with net.Listen (synchronous) + srv.Serve(ln) (goroutine). A bind failure now returns an error from OnStart, which the launcher treats as fatal and triggers a clean shutdown immediately. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -80,7 +81,10 @@ func (s *httpServer) OnInit() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnStart starts the HTTP server in a background goroutine.
|
// OnStart binds the TCP listener synchronously so that a port conflict returns
|
||||||
|
// an error immediately — allowing the launcher to trigger a clean shutdown
|
||||||
|
// instead of running silently without an HTTP server. The accepted connections
|
||||||
|
// are then served in a background goroutine.
|
||||||
func (s *httpServer) OnStart() error {
|
func (s *httpServer) OnStart() error {
|
||||||
host := s.cfg.Host
|
host := s.cfg.Host
|
||||||
if host == "" {
|
if host == "" {
|
||||||
@@ -90,16 +94,23 @@ func (s *httpServer) OnStart() error {
|
|||||||
if port == 0 {
|
if port == 0 {
|
||||||
port = 8080
|
port = 8080
|
||||||
}
|
}
|
||||||
|
addr := fmt.Sprintf("%s:%d", host, port)
|
||||||
|
|
||||||
|
ln, err := net.Listen("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("httpserver: bind %s: %w", addr, err)
|
||||||
|
}
|
||||||
|
|
||||||
s.srv = &http.Server{
|
s.srv = &http.Server{
|
||||||
Addr: fmt.Sprintf("%s:%d", host, port),
|
Addr: addr,
|
||||||
Handler: s.Router,
|
Handler: s.Router,
|
||||||
ReadTimeout: s.cfg.ReadTimeout,
|
ReadTimeout: s.cfg.ReadTimeout,
|
||||||
WriteTimeout: s.cfg.WriteTimeout,
|
WriteTimeout: s.cfg.WriteTimeout,
|
||||||
IdleTimeout: s.cfg.IdleTimeout,
|
IdleTimeout: s.cfg.IdleTimeout,
|
||||||
}
|
}
|
||||||
s.logger.Info("httpserver: starting", "addr", s.srv.Addr)
|
s.logger.Info("httpserver: starting", "addr", addr)
|
||||||
go func() {
|
go func() {
|
||||||
if err := s.srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
if err := s.srv.Serve(ln); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
s.logger.Error("httpserver: fatal error", err)
|
s.logger.Error("httpserver: fatal error", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|||||||
Reference in New Issue
Block a user