fix(api): return listen errors immediately

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-01 14:20:30 +00:00
parent 926a723d9c
commit 684a37cd84
2 changed files with 35 additions and 2 deletions

8
api.go
View file

@ -120,8 +120,12 @@ func (e *Engine) Serve(ctx context.Context) error {
close(errCh)
}()
// Block until context is cancelled.
<-ctx.Done()
// Return immediately if the listener fails before shutdown is requested.
select {
case err := <-errCh:
return err
case <-ctx.Done():
}
// Signal SSE clients first so their handlers can exit cleanly before the
// HTTP server begins its own shutdown sequence.

View file

@ -202,3 +202,32 @@ func TestServe_Good_GracefulShutdown(t *testing.T) {
t.Fatal("Serve did not return within 5 seconds after context cancellation")
}
}
func TestServe_Bad_ReturnsListenErrorBeforeCancel(t *testing.T) {
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("failed to reserve port: %v", err)
}
addr := ln.Addr().String()
defer ln.Close()
e, _ := api.New(api.WithAddr(addr))
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
errCh := make(chan error, 1)
go func() {
errCh <- e.Serve(ctx)
}()
select {
case serveErr := <-errCh:
if serveErr == nil {
t.Fatal("expected Serve to return a listen error, got nil")
}
case <-time.After(2 * time.Second):
cancel()
t.Fatal("Serve did not return promptly after listener failure")
}
}