mirror of
https://github.com/gofiber/fiber.git
synced 2025-05-31 11:52:41 +00:00
* feat: support for starting devtime dependencies in an abstract manner * feat: support for starting devtime dependencies in an abstract manner * fix: spell * fix: lint * fix: markdown lint * fix: b.Helper * fix: lint spell * fix: field padding * chore: protect the usage of dev dependencies with the "dev" build tag * fix: error message * docs: fix type name * fix: mock context cancellation * docs: simpler * fix: lint unused receiver * fix: handle error in benchmarks * lint: remove build tag * fix: wrap error * fix: lint * fix: explain why lint exclusion * chore: best effort while terminating dependencies * gix: lintern name * fix: reduce flakiness in tests * chore: get dependency state for logs * chore: protect dev time tests and benchmarks under build tag * chore: add build tag in more places * fix: more conservative context cancellation timeout in tests * chore: remove build tags * chore: rename to Services * fix: update tests * fix: lint * fix: lint * fix: apply coderrabit suggestion * chore: add more unit tests * chore: add more unit tests * chore: refactor tests * fix: avoid control flags in tests * chore: consistent error message in start * chore: simplify error logic * chore: remove flag coupling * chore: simplify benchmarks * chore: add corerabbit suggetion * fix: wording * chore: log error on service termination * docs: wording * fix: typo in error message * fix: wording * fix: panic on startup error * chore: store started services separately, so that we can terminate them properly * docs: update example * fix: use context provider instead of storing the context * chore: use require.Empty * fix: no tabs in docs * chore: move field for better alignment * docs: do not use interface as method receiver * docs: proper usage of JSON bind * fix: use startup context for bootstrap log * chore: move happy path to the left * fix: use configured consistently * chore: terminate started services in reverse order * fix: consistent access to the config context * chore: test names and benchmarks location * chore: benchmark refinement * chore: store the services into the global State * chore: add functions to access the Services in the state * chore: hex-encode the hashes * chore: consistent var name for services * chore: non racey service initialisation * fix: wrong range iteration in service keys * fix: use inline * chore: more tests for the generics functions for services * chore: add benchmarks for service functions * fix: benchmarks refactor was wrong * fix. refine error message * fix: do not cause overhead in newState, instead pre-calculate the prefix hash at init * chore: simplify hashing * chore: use smaller, and testable function for initServices * chore: initialize services in the app.init * chore: init services before blocking the app init * Revert "chore: init services before blocking the app init" This reverts commit bb67cf6380cb71ad5ae4ab4807cdfbf0c7eafa1b. * chore: move happy path to the left at initServices * fix: register shutdown hooks for services after app's mutext is unlocked --------- Co-authored-by: Juan Calderon-Perez <835733+gaby@users.noreply.github.com>
148 lines
4.5 KiB
Go
148 lines
4.5 KiB
Go
package fiber
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"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
|
|
}
|
|
|
|
// 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 _, srv := range app.configured.Services {
|
|
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 _, srv := range app.state.Services() {
|
|
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
|
|
func (app *App) logServices(ctx context.Context, out io.Writer, colors Colors) {
|
|
if !app.hasConfiguredServices() {
|
|
return
|
|
}
|
|
|
|
fmt.Fprintf(out,
|
|
"%sINFO%s Services: \t%s%d%s\n",
|
|
colors.Green, colors.Reset, colors.Blue, app.state.ServicesLen(), colors.Reset)
|
|
for _, srv := range app.state.Services() {
|
|
var state string
|
|
var stateColor string
|
|
state, err := srv.State(ctx)
|
|
if err != nil {
|
|
state = "ERROR"
|
|
stateColor = colors.Red
|
|
} else {
|
|
stateColor = colors.Blue
|
|
state = strings.ToUpper(state)
|
|
}
|
|
fmt.Fprintf(out, "%sINFO%s 🥡 %s[ %s ] %s%s\n", colors.Green, colors.Reset, stateColor, strings.ToUpper(state), srv.String(), colors.Reset)
|
|
}
|
|
}
|