package fiber import ( "context" "errors" "fmt" "io" utilsstrings "github.com/gofiber/utils/v2/strings" ) // Service is an interface that defines the methods for a service. type Service interface { // Start starts the service, returning an error if it fails. Start(ctx context.Context) error // String returns a string representation of the service. // It is used to print a human-readable name of the service in the startup message. String() string // State returns the current state of the service. State(ctx context.Context) (string, error) // Terminate terminates the service, returning an error if it fails. Terminate(ctx context.Context) error } // hasConfiguredServices Checks if there are any services for the current application. func (app *App) hasConfiguredServices() bool { return len(app.configured.Services) > 0 } func (app *App) validateConfiguredServices() error { return validateServicesSlice(app.configured.Services) } func validateServicesSlice(services []Service) error { for idx, srv := range services { if srv == nil { return fmt.Errorf("fiber: service at index %d is nil", idx) } } return nil } // initServices If the app is configured to use services, this function registers // a post shutdown hook to shutdown them after the server is closed. // This function panics if there is an error starting the services. func (app *App) initServices() { if !app.hasConfiguredServices() { return } if err := app.startServices(app.servicesStartupCtx()); err != nil { panic(err) } } // servicesStartupCtx Returns the context for the services startup. // If the ServicesStartupContextProvider is not set, it returns a new background context. func (app *App) servicesStartupCtx() context.Context { if app.configured.ServicesStartupContextProvider != nil { return app.configured.ServicesStartupContextProvider() } return context.Background() } // servicesShutdownCtx Returns the context for the services shutdown. // If the ServicesShutdownContextProvider is not set, it returns a new background context. func (app *App) servicesShutdownCtx() context.Context { if app.configured.ServicesShutdownContextProvider != nil { return app.configured.ServicesShutdownContextProvider() } return context.Background() } // startServices Handles the start process of services for the current application. // Iterates over all configured services and tries to start them, returning an error if any error occurs. func (app *App) startServices(ctx context.Context) error { if !app.hasConfiguredServices() { return nil } var errs []error for idx, srv := range app.configured.Services { if srv == nil { return fmt.Errorf("fiber: service at index %d is nil", idx) } if err := ctx.Err(); err != nil { // Context is canceled, return an error the soonest possible, so that // the user can see the context cancellation error and act on it. return fmt.Errorf("context canceled while starting service %s: %w", srv.String(), err) } err := srv.Start(ctx) if err == nil { // mark the service as started app.state.setService(srv) continue } if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { return fmt.Errorf("service %s start: %w", srv.String(), err) } errs = append(errs, fmt.Errorf("service %s start: %w", srv.String(), err)) } return errors.Join(errs...) } // shutdownServices Handles the shutdown process of services for the current application. // Iterates over all the started services in reverse order and tries to terminate them, // returning an error if any error occurs. func (app *App) shutdownServices(ctx context.Context) error { if app.state.ServicesLen() == 0 { return nil } var errs []error for key, srv := range app.state.Services() { if srv == nil { return fmt.Errorf("fiber: service %q is nil", key) } if err := ctx.Err(); err != nil { // Context is canceled, do a best effort to terminate the services. errs = append(errs, fmt.Errorf("service %s terminate: %w", srv.String(), err)) continue } err := srv.Terminate(ctx) if err != nil { // Best effort to terminate the services. errs = append(errs, fmt.Errorf("service %s terminate: %w", srv.String(), err)) continue } // Remove the service from the State app.state.deleteService(srv) } return errors.Join(errs...) } // logServices logs information about services and returns an error // if any configured service is nil. func (app *App) logServices(ctx context.Context, out io.Writer, colors *Colors) error { if !app.hasConfiguredServices() { return nil } scheme := colors if scheme == nil { scheme = &DefaultColors } fmt.Fprintf(out, "%sINFO%s Services: \t%s%d%s\n", scheme.Green, scheme.Reset, scheme.Blue, app.state.ServicesLen(), scheme.Reset) for key, srv := range app.state.Services() { if srv == nil { return fmt.Errorf("fiber: service %q is nil", key) } var state string var stateColor string state, err := srv.State(ctx) if err != nil { state = errString stateColor = scheme.Red } else { stateColor = scheme.Blue } fmt.Fprintf(out, "%sINFO%s 🧩 %s[ %s ] %s%s\n", scheme.Green, scheme.Reset, stateColor, utilsstrings.ToUpper(state), srv.String(), scheme.Reset) } return nil }