Files
fiber/services.go

177 lines
5.2 KiB
Go

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
}