-
Notifications
You must be signed in to change notification settings - Fork 2
Fix the graceful shutdown #272
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| Bugfix: Fix the graceful shutdown | ||
|
|
||
| Fix the graceful shutdown. | ||
|
|
||
| https://github.com/owncloud/reva/pull/272 | ||
| https://github.com/owncloud/ocis/issues/11170 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,178 @@ | ||
| package runtime | ||
|
|
||
| import ( | ||
| "errors" | ||
| "fmt" | ||
| "net" | ||
| "net/http" | ||
| "os" | ||
| "time" | ||
|
|
||
| "github.com/owncloud/reva/v2/pkg/registry" | ||
| "github.com/rs/zerolog" | ||
| ) | ||
|
|
||
| const ( | ||
| HTTP = iota | ||
| GRPC | ||
| ) | ||
|
|
||
| // RevaDrivenServer is an interface that defines the methods for starting and stopping reva HTTP/GRPC services. | ||
| type RevaDrivenServer interface { | ||
| Start() error | ||
| Stop() error | ||
| } | ||
|
|
||
| // revaServer is an interface that defines the methods for starting and stopping a reva server. | ||
| type revaServer interface { | ||
| Start(ln net.Listener) error | ||
| Stop() error | ||
| GracefulStop() error | ||
| Network() string | ||
| Address() string | ||
| } | ||
|
|
||
| // sever represents a generic reva server that implements the RevaDrivenServer interface. | ||
| type server struct { | ||
| srv revaServer | ||
| log *zerolog.Logger | ||
| gracefulShutdownTimeout time.Duration | ||
| protocol string | ||
| } | ||
|
|
||
| // NewDrivenHTTPServerWithOptions runs a revad server w/o watcher with the given config file and options. | ||
| // Use it in cases where you want to run a revad server without the need for a watcher and the os signal handling as a part of another runtime. | ||
| // Returns nil if no http server is configured in the config file. | ||
| // The GracefulShutdownTimeout set to default 20 seconds and can be overridden in the core config. | ||
| // Logging a fatal error and exit with code 1 if the http server cannot be created. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The "exit with code 1" is misleading because the function never does it. As it is written, it seems there is a If, as a result of this function returning nil, the main app decides to exit with code 1, that's fine, but it's something this function doesn't need to care about. Whatever the caller does with the result of this function isn't this function's business. |
||
| func NewDrivenHTTPServerWithOptions(mainConf map[string]interface{}, opts ...Option) RevaDrivenServer { | ||
| if !isEnabledHTTP(mainConf) { | ||
| return nil | ||
| } | ||
| options := newOptions(opts...) | ||
| if srv := newServer(HTTP, mainConf, options); srv != nil { | ||
| return srv | ||
| } | ||
| options.Logger.Fatal().Msg("nothing to do, no http enabled_services declared in config") | ||
| return nil | ||
| } | ||
|
|
||
| // NewDrivenGRPCServerWithOptions runs a revad server w/o watcher with the given config file and options. | ||
| // Use it in cases where you want to run a revad server without the need for a watcher and the os signal handling as a part of another runtime. | ||
| // Returns nil if no grpc server is configured in the config file. | ||
| // The GracefulShutdownTimeout set to default 20 seconds and can be overridden in the core config. | ||
| // Logging a fatal error and exit with code 1 if the grpc server cannot be created. | ||
| func NewDrivenGRPCServerWithOptions(mainConf map[string]interface{}, opts ...Option) RevaDrivenServer { | ||
| if !isEnabledGRPC(mainConf) { | ||
| return nil | ||
| } | ||
| options := newOptions(opts...) | ||
| if srv := newServer(GRPC, mainConf, options); srv != nil { | ||
| return srv | ||
| } | ||
| options.Logger.Fatal().Msg("nothing to do, no grpc enabled_services declared in config") | ||
| return nil | ||
| } | ||
|
|
||
| // Start starts the reva server, listening on the configured address and network. | ||
| func (s *server) Start() error { | ||
| if s.srv == nil { | ||
| err := fmt.Errorf("reva %s server not initialized", s.protocol) | ||
| s.log.Fatal().Err(err).Send() | ||
jvillafanez marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return err | ||
| } | ||
| ln, err := net.Listen(s.srv.Network(), s.srv.Address()) | ||
| if err != nil { | ||
| s.log.Fatal().Err(err).Send() | ||
| return err | ||
| } | ||
| if err = s.srv.Start(ln); err != nil { | ||
| if !errors.Is(err, http.ErrServerClosed) { | ||
| s.log.Error().Err(err).Msgf("reva %s server error", s.protocol) | ||
| } | ||
| return err | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // Stop gracefully stops the reva server, waiting for the graceful shutdown timeout. | ||
| func (s *server) Stop() error { | ||
| if s.srv == nil { | ||
| return nil | ||
| } | ||
| done := make(chan struct{}) | ||
| go func() { | ||
| s.log.Info().Msgf("gracefully stopping %s:%s reva %s server", s.srv.Network(), s.srv.Address(), s.protocol) | ||
| if err := s.srv.GracefulStop(); err != nil { | ||
| s.log.Error().Err(err).Msgf("error gracefully stopping reva %s server", s.protocol) | ||
| s.srv.Stop() | ||
| } | ||
| close(done) | ||
| }() | ||
|
|
||
| select { | ||
| case <-time.After(s.gracefulShutdownTimeout): | ||
| s.log.Info().Msg("graceful shutdown timeout reached. running hard shutdown") | ||
| err := s.srv.Stop() | ||
| if err != nil { | ||
| s.log.Error().Err(err).Msgf("error stopping reva %s server", s.protocol) | ||
| } | ||
| return nil | ||
| case <-done: | ||
| s.log.Info().Msgf("reva %s server gracefully stopped", s.protocol) | ||
| return nil | ||
| } | ||
| } | ||
|
|
||
| // newServer runs a revad server w/o watcher with the given config file and options. | ||
| func newServer(protocol int, mainConf map[string]interface{}, options Options) RevaDrivenServer { | ||
| parseSharedConfOrDie(mainConf["shared"]) | ||
| coreConf := parseCoreConfOrDie(mainConf["core"]) | ||
| log := options.Logger | ||
|
|
||
| if err := registry.Init(options.Registry); err != nil { | ||
| log.Fatal().Err(err).Msg("failed to initialize registry client") | ||
| return nil | ||
| } | ||
|
|
||
| host, _ := os.Hostname() | ||
| log.Info().Msgf("host info: %s", host) | ||
|
|
||
| // Only initialize tracing if we didn't get a tracer provider. | ||
| if options.TraceProvider == nil { | ||
| log.Debug().Msg("no pre-existing tracer given, initializing tracing") | ||
| options.TraceProvider = initTracing(coreConf) | ||
| } | ||
| initCPUCount(coreConf, log) | ||
|
|
||
| gracefulShutdownTimeout := 20 * time.Second | ||
| if coreConf.GracefulShutdownTimeout > 0 { | ||
| gracefulShutdownTimeout = time.Duration(coreConf.GracefulShutdownTimeout) * time.Second | ||
| } | ||
|
|
||
| srv := &server{ | ||
| log: options.Logger, | ||
| gracefulShutdownTimeout: gracefulShutdownTimeout, | ||
| } | ||
| switch protocol { | ||
| case HTTP: | ||
| s, err := getHTTPServer(mainConf["http"], options.Logger, options.TraceProvider) | ||
| if err != nil { | ||
| options.Logger.Fatal().Err(err).Msg("error creating http server") | ||
| return nil | ||
| } | ||
| srv.srv = s | ||
| srv.protocol = "http" | ||
| return srv | ||
| case GRPC: | ||
| s, err := getGRPCServer(mainConf["grpc"], options.Logger, options.TraceProvider) | ||
| if err != nil { | ||
| options.Logger.Fatal().Err(err).Msg("error creating grpc server") | ||
| return nil | ||
| } | ||
| srv.srv = s | ||
| srv.protocol = "grpc" | ||
| return srv | ||
| } | ||
| return nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,6 +25,8 @@ import ( | |
| "net" | ||
| "sort" | ||
|
|
||
| grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" | ||
| "github.com/mitchellh/mapstructure" | ||
| "github.com/owncloud/reva/v2/internal/grpc/interceptors/appctx" | ||
| "github.com/owncloud/reva/v2/internal/grpc/interceptors/auth" | ||
| "github.com/owncloud/reva/v2/internal/grpc/interceptors/log" | ||
|
|
@@ -33,8 +35,6 @@ import ( | |
| "github.com/owncloud/reva/v2/internal/grpc/interceptors/useragent" | ||
| "github.com/owncloud/reva/v2/pkg/sharedconf" | ||
| rtrace "github.com/owncloud/reva/v2/pkg/trace" | ||
| grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" | ||
| "github.com/mitchellh/mapstructure" | ||
| "github.com/pkg/errors" | ||
| "github.com/rs/zerolog" | ||
| mtls "go-micro.dev/v4/util/tls" | ||
|
|
@@ -279,15 +279,15 @@ func (s *Server) cleanupServices() { | |
|
|
||
| // Stop stops the server. | ||
| func (s *Server) Stop() error { | ||
| s.cleanupServices() | ||
| s.s.Stop() | ||
| s.cleanupServices() | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jvillafanez I assumed that we should stop the Listen first and then clean the services. Could you check if it makes sense?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As far as I know, this |
||
| return nil | ||
| } | ||
|
|
||
| // GracefulStop gracefully stops the server. | ||
| func (s *Server) GracefulStop() error { | ||
| s.cleanupServices() | ||
| s.s.GracefulStop() | ||
| s.cleanupServices() | ||
| return nil | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.