services.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. package fiber
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "io"
  7. utilsstrings "github.com/gofiber/utils/v2/strings"
  8. )
  9. // Service is an interface that defines the methods for a service.
  10. type Service interface {
  11. // Start starts the service, returning an error if it fails.
  12. Start(ctx context.Context) error
  13. // String returns a string representation of the service.
  14. // It is used to print a human-readable name of the service in the startup message.
  15. String() string
  16. // State returns the current state of the service.
  17. State(ctx context.Context) (string, error)
  18. // Terminate terminates the service, returning an error if it fails.
  19. Terminate(ctx context.Context) error
  20. }
  21. // hasConfiguredServices Checks if there are any services for the current application.
  22. func (app *App) hasConfiguredServices() bool {
  23. return len(app.configured.Services) > 0
  24. }
  25. func (app *App) validateConfiguredServices() error {
  26. return validateServicesSlice(app.configured.Services)
  27. }
  28. func validateServicesSlice(services []Service) error {
  29. for idx, srv := range services {
  30. if srv == nil {
  31. return fmt.Errorf("fiber: service at index %d is nil", idx)
  32. }
  33. }
  34. return nil
  35. }
  36. // initServices If the app is configured to use services, this function registers
  37. // a post shutdown hook to shutdown them after the server is closed.
  38. // This function panics if there is an error starting the services.
  39. func (app *App) initServices() {
  40. if !app.hasConfiguredServices() {
  41. return
  42. }
  43. if err := app.startServices(app.servicesStartupCtx()); err != nil {
  44. panic(err)
  45. }
  46. }
  47. // servicesStartupCtx Returns the context for the services startup.
  48. // If the ServicesStartupContextProvider is not set, it returns a new background context.
  49. func (app *App) servicesStartupCtx() context.Context {
  50. if app.configured.ServicesStartupContextProvider != nil {
  51. return app.configured.ServicesStartupContextProvider()
  52. }
  53. return context.Background()
  54. }
  55. // servicesShutdownCtx Returns the context for the services shutdown.
  56. // If the ServicesShutdownContextProvider is not set, it returns a new background context.
  57. func (app *App) servicesShutdownCtx() context.Context {
  58. if app.configured.ServicesShutdownContextProvider != nil {
  59. return app.configured.ServicesShutdownContextProvider()
  60. }
  61. return context.Background()
  62. }
  63. // startServices Handles the start process of services for the current application.
  64. // Iterates over all configured services and tries to start them, returning an error if any error occurs.
  65. func (app *App) startServices(ctx context.Context) error {
  66. if !app.hasConfiguredServices() {
  67. return nil
  68. }
  69. var errs []error
  70. for idx, srv := range app.configured.Services {
  71. if srv == nil {
  72. return fmt.Errorf("fiber: service at index %d is nil", idx)
  73. }
  74. if err := ctx.Err(); err != nil {
  75. // Context is canceled, return an error the soonest possible, so that
  76. // the user can see the context cancellation error and act on it.
  77. return fmt.Errorf("context canceled while starting service %s: %w", srv.String(), err)
  78. }
  79. err := srv.Start(ctx)
  80. if err == nil {
  81. // mark the service as started
  82. app.state.setService(srv)
  83. continue
  84. }
  85. if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
  86. return fmt.Errorf("service %s start: %w", srv.String(), err)
  87. }
  88. errs = append(errs, fmt.Errorf("service %s start: %w", srv.String(), err))
  89. }
  90. return errors.Join(errs...)
  91. }
  92. // shutdownServices Handles the shutdown process of services for the current application.
  93. // Iterates over all the started services in reverse order and tries to terminate them,
  94. // returning an error if any error occurs.
  95. func (app *App) shutdownServices(ctx context.Context) error {
  96. if app.state.ServicesLen() == 0 {
  97. return nil
  98. }
  99. var errs []error
  100. for key, srv := range app.state.Services() {
  101. if srv == nil {
  102. return fmt.Errorf("fiber: service %q is nil", key)
  103. }
  104. if err := ctx.Err(); err != nil {
  105. // Context is canceled, do a best effort to terminate the services.
  106. errs = append(errs, fmt.Errorf("service %s terminate: %w", srv.String(), err))
  107. continue
  108. }
  109. err := srv.Terminate(ctx)
  110. if err != nil {
  111. // Best effort to terminate the services.
  112. errs = append(errs, fmt.Errorf("service %s terminate: %w", srv.String(), err))
  113. continue
  114. }
  115. // Remove the service from the State
  116. app.state.deleteService(srv)
  117. }
  118. return errors.Join(errs...)
  119. }
  120. // logServices logs information about services and returns an error
  121. // if any configured service is nil.
  122. func (app *App) logServices(ctx context.Context, out io.Writer, colors *Colors) error {
  123. if !app.hasConfiguredServices() {
  124. return nil
  125. }
  126. scheme := colors
  127. if scheme == nil {
  128. scheme = &DefaultColors
  129. }
  130. fmt.Fprintf(out,
  131. "%sINFO%s Services: \t%s%d%s\n",
  132. scheme.Green, scheme.Reset, scheme.Blue, app.state.ServicesLen(), scheme.Reset)
  133. for key, srv := range app.state.Services() {
  134. if srv == nil {
  135. return fmt.Errorf("fiber: service %q is nil", key)
  136. }
  137. var state string
  138. var stateColor string
  139. state, err := srv.State(ctx)
  140. if err != nil {
  141. state = errString
  142. stateColor = scheme.Red
  143. } else {
  144. stateColor = scheme.Blue
  145. }
  146. fmt.Fprintf(out, "%sINFO%s 🧩 %s[ %s ] %s%s\n", scheme.Green, scheme.Reset, stateColor, utilsstrings.ToUpper(state), srv.String(), scheme.Reset)
  147. }
  148. return nil
  149. }