feature/epic1-core-infrastructure #2
@@ -145,7 +145,7 @@ func ProvideErrorBus() fx.Option {
|
||||
func ProvideHealthRegistry() fx.Option {
|
||||
return fx.Provide(func(dbClient *database.Client) (*health.Registry, error) {
|
||||
registry := health.NewRegistry()
|
||||
|
||||
|
||||
// Register database health checker
|
||||
registry.Register("database", health.NewDatabaseChecker(dbClient))
|
||||
|
||||
@@ -237,19 +237,54 @@ func ProvideHTTPServer() fx.Option {
|
||||
host = "0.0.0.0"
|
||||
}
|
||||
addr := fmt.Sprintf("%s:%d", host, port)
|
||||
|
||||
|
||||
log.Info("HTTP server starting",
|
||||
logger.String("addr", addr),
|
||||
)
|
||||
|
||||
// Start server in a goroutine
|
||||
// ListenAndServe blocks, so we need to start it async
|
||||
// If there's an immediate error (like port in use), it will return quickly
|
||||
errChan := make(chan error, 1)
|
||||
go func() {
|
||||
log.Info("HTTP server starting",
|
||||
logger.String("addr", addr),
|
||||
)
|
||||
if err := srv.Start(); err != nil && err != http.ErrServerClosed {
|
||||
log.Error("HTTP server error",
|
||||
log.Error("HTTP server failed",
|
||||
logger.String("error", err.Error()),
|
||||
)
|
||||
select {
|
||||
case errChan <- err:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
|
||||
// Wait a short time to detect immediate binding errors
|
||||
// If ListenAndServe fails immediately (e.g., port in use), it will return quickly
|
||||
select {
|
||||
case err := <-errChan:
|
||||
return fmt.Errorf("HTTP server failed to start: %w", err)
|
||||
case <-time.After(500 * time.Millisecond):
|
||||
// If no error after 500ms, verify server is actually listening
|
||||
// by attempting a connection
|
||||
client := &http.Client{Timeout: 1 * time.Second}
|
||||
checkURL := fmt.Sprintf("http://localhost:%d/healthz", port)
|
||||
resp, err := client.Get(checkURL)
|
||||
if err != nil {
|
||||
// Server might still be starting, but log the attempt
|
||||
log.Warn("Could not verify HTTP server is listening (may still be starting)",
|
||||
logger.String("url", checkURL),
|
||||
logger.String("error", err.Error()),
|
||||
)
|
||||
// Continue anyway - server might still be starting
|
||||
} else {
|
||||
resp.Body.Close()
|
||||
}
|
||||
|
||||
log.Info("HTTP server started successfully",
|
||||
logger.String("addr", addr),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
},
|
||||
OnStop: func(ctx context.Context) error {
|
||||
return srv.Shutdown(ctx)
|
||||
|
||||
@@ -123,9 +123,23 @@ func registerRoutes(
|
||||
|
||||
// Start starts the HTTP server.
|
||||
func (s *Server) Start() error {
|
||||
// ListenAndServe will block until the server is closed
|
||||
// If there's an immediate error (like port in use), it will return immediately
|
||||
return s.httpServer.ListenAndServe()
|
||||
}
|
||||
|
||||
// StartAsync starts the HTTP server in a goroutine and returns a channel that signals when it's ready or errored.
|
||||
func (s *Server) StartAsync() <-chan error {
|
||||
errChan := make(chan error, 1)
|
||||
go func() {
|
||||
if err := s.Start(); err != nil && err != http.ErrServerClosed {
|
||||
errChan <- err
|
||||
}
|
||||
close(errChan)
|
||||
}()
|
||||
return errChan
|
||||
}
|
||||
|
||||
// Shutdown gracefully shuts down the HTTP server.
|
||||
func (s *Server) Shutdown(ctx context.Context) error {
|
||||
return s.httpServer.Shutdown(ctx)
|
||||
|
||||
Reference in New Issue
Block a user