From 6f43f81e0e248ee3f77d7cea21d1e9daa6c65387 Mon Sep 17 00:00:00 2001 From: Arber Avdullahu Date: Sun, 3 May 2020 10:57:21 +0200 Subject: [PATCH 01/11] shutdown gracefully --- modern/mdservice/service.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/modern/mdservice/service.go b/modern/mdservice/service.go index 2ca296d..c0438a5 100644 --- a/modern/mdservice/service.go +++ b/modern/mdservice/service.go @@ -2,6 +2,9 @@ package mdservice import ( "fmt" + "os" + "os/signal" + "syscall" "github.com/short-d/app/fw" ) @@ -25,6 +28,7 @@ func (s Service) Start(port int) { } func (s Service) StartAndWait(port int) { + go s.shutdownGracefully() s.Start(port) select {} } @@ -38,6 +42,19 @@ func (s Service) Stop() { } } +func (s Service) shutdownGracefully() { + signalChan := make(chan os.Signal, 1) + signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) + + sgn := <-signalChan + s.logger.Info(fmt.Sprintf("Handling %s ...\n", sgn)) + + err := s.server.Shutdown() + if err != nil { + s.logger.Fatal(fmt.Sprintf("graceful server shutdown failed with %v", err)) + } +} + func New(name string, server fw.Server, logger fw.Logger) Service { return Service{ name: name, From 4887aed9100033f59f83b626c028e72df02ca45f Mon Sep 17 00:00:00 2001 From: Arber Avdullahu Date: Wed, 27 May 2020 23:14:22 +0200 Subject: [PATCH 02/11] shutdown gracefully --- fw/service/graphql.go | 5 +++-- fw/service/grpc.go | 5 +++-- fw/service/routing.go | 5 +++-- fw/service/service.go | 4 +++- fw/web/server.go | 2 +- 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/fw/service/graphql.go b/fw/service/graphql.go index 2116e53..3ca6b67 100644 --- a/fw/service/graphql.go +++ b/fw/service/graphql.go @@ -1,6 +1,7 @@ package service import ( + "context" "fmt" "github.com/short-d/app/fw/graphql" @@ -29,10 +30,10 @@ func (g GraphQL) StartAsync(port int) { }() } -func (g GraphQL) Stop() { +func (g GraphQL) Stop(ctx context.Context) { defer g.logger.Info("GraphQL service stopped") - err := g.webServer.Shutdown() + err := g.webServer.Shutdown(ctx) if err != nil { g.logger.Error(err) } diff --git a/fw/service/grpc.go b/fw/service/grpc.go index 23337d6..b7fb5f9 100644 --- a/fw/service/grpc.go +++ b/fw/service/grpc.go @@ -1,6 +1,7 @@ package service import ( + "context" "fmt" "net" @@ -19,8 +20,8 @@ type GRPC struct { logger logger.Logger } -func (g GRPC) Stop() { - g.gRPCServer.Stop() +func (g GRPC) Stop(context.Context) { + g.gRPCServer.GracefulStop() } func (g GRPC) StartAsync(port int) { diff --git a/fw/service/routing.go b/fw/service/routing.go index 3ddf37a..0b8f05c 100644 --- a/fw/service/routing.go +++ b/fw/service/routing.go @@ -1,6 +1,7 @@ package service import ( + "context" "fmt" "github.com/short-d/app/fw/logger" @@ -28,10 +29,10 @@ func (r Routing) StartAsync(port int) { }() } -func (r Routing) Stop() { +func (r Routing) Stop(ctx context.Context) { defer r.logger.Info("Routing service stopped") - err := r.webServer.Shutdown() + err := r.webServer.Shutdown(ctx) if err != nil { r.logger.Error(err) } diff --git a/fw/service/service.go b/fw/service/service.go index a7214e0..c45350b 100644 --- a/fw/service/service.go +++ b/fw/service/service.go @@ -1,8 +1,10 @@ package service +import "context" + // TODO(issue#67): support graceful shutdown. type Service interface { - Stop() + Stop(ctx context.Context) StartAsync(port int) StartAndWait(port int) } diff --git a/fw/web/server.go b/fw/web/server.go index 642c1f2..fabd93c 100644 --- a/fw/web/server.go +++ b/fw/web/server.go @@ -28,7 +28,7 @@ func (s *Server) ListenAndServe(port int) error { return err } -func (s Server) Shutdown() error { +func (s Server) Shutdown(ctx context.Context) error { return s.server.Shutdown(context.Background()) } From d24dfa23a42f271d6b581dc386c6d10188825dc6 Mon Sep 17 00:00:00 2001 From: Arber Avdullahu Date: Wed, 27 May 2020 23:15:29 +0200 Subject: [PATCH 03/11] server shutdown --- fw/web/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fw/web/server.go b/fw/web/server.go index fabd93c..68eb8e5 100644 --- a/fw/web/server.go +++ b/fw/web/server.go @@ -29,7 +29,7 @@ func (s *Server) ListenAndServe(port int) error { } func (s Server) Shutdown(ctx context.Context) error { - return s.server.Shutdown(context.Background()) + return s.server.Shutdown(ctx) } func (s Server) HandleFunc(pattern string, handler http.Handler) { From 00b729fa1aac675765ddeb217ffa92d48fd8215d Mon Sep 17 00:00:00 2001 From: Arber Avdullahu Date: Wed, 27 May 2020 23:15:58 +0200 Subject: [PATCH 04/11] remove todo --- fw/service/service.go | 1 - 1 file changed, 1 deletion(-) diff --git a/fw/service/service.go b/fw/service/service.go index c45350b..4456b13 100644 --- a/fw/service/service.go +++ b/fw/service/service.go @@ -2,7 +2,6 @@ package service import "context" -// TODO(issue#67): support graceful shutdown. type Service interface { Stop(ctx context.Context) StartAsync(port int) From 4aa8addb0bcee0cd48d868fd77671d7872c4a0ce Mon Sep 17 00:00:00 2001 From: Arber Avdullahu Date: Thu, 28 May 2020 20:42:03 +0200 Subject: [PATCH 05/11] on shutdown func --- fw/service/graphql.go | 16 +++++++++++++--- fw/service/grpc.go | 23 +++++++++++++++-------- fw/service/routing.go | 25 +++++++++++++++++-------- fw/service/service.go | 19 +++++++++++++++++-- 4 files changed, 62 insertions(+), 21 deletions(-) diff --git a/fw/service/graphql.go b/fw/service/graphql.go index 3ca6b67..ccde5d9 100644 --- a/fw/service/graphql.go +++ b/fw/service/graphql.go @@ -15,6 +15,7 @@ type GraphQL struct { logger logger.Logger graphQLPath string webServer *web.Server + onShutdown func() } func (g GraphQL) StartAsync(port int) { @@ -30,8 +31,14 @@ func (g GraphQL) StartAsync(port int) { }() } -func (g GraphQL) Stop(ctx context.Context) { +func (g GraphQL) Stop(ctx context.Context, cancel context.CancelFunc) { defer g.logger.Info("GraphQL service stopped") + defer func() { + if g.onShutdown != nil { + g.onShutdown() + } + cancel() + }() err := g.webServer.Shutdown(ctx) if err != nil { @@ -41,13 +48,15 @@ func (g GraphQL) Stop(ctx context.Context) { func (g GraphQL) StartAndWait(port int) { g.StartAsync(port) - select {} + + listenForSignals(g) } func NewGraphQL( logger logger.Logger, graphQLPath string, handler graphql.Handler, + onShutdown func(), ) GraphQL { server := web.NewServer(logger) server.HandleFunc(graphQLPath, handler) @@ -56,6 +65,7 @@ func NewGraphQL( logger: logger, graphQLPath: graphQLPath, webServer: &server, + onShutdown: onShutdown, } } @@ -81,7 +91,7 @@ func (g GraphQLBuilder) Build() GraphQL { Resolver: g.resolver, } handler := graphql.NewGraphGopherHandler(api) - return NewGraphQL(g.logger, "/graphql", handler) + return NewGraphQL(g.logger, "/graphql", handler, nil) } func NewGraphQLBuilder(name string) *GraphQLBuilder { diff --git a/fw/service/grpc.go b/fw/service/grpc.go index b7fb5f9..08ed392 100644 --- a/fw/service/grpc.go +++ b/fw/service/grpc.go @@ -18,9 +18,18 @@ type GRPC struct { gRPCServer *grpc.Server gRPCApi rpc.API logger logger.Logger + onShutdown func() } -func (g GRPC) Stop(context.Context) { +func (g GRPC) Stop(ctx context.Context, cancel context.CancelFunc) { + defer g.logger.Info("gRPC service stopped") + defer func() { + if g.onShutdown != nil { + g.onShutdown() + } + cancel() + }() + g.gRPCServer.GracefulStop() } @@ -43,14 +52,11 @@ func (g GRPC) StartAsync(port int) { func (g GRPC) StartAndWait(port int) { g.StartAsync(port) - select {} + + listenForSignals(g) } -func NewGRPC( - logger logger.Logger, - rpcAPI rpc.API, - securityPolicy security.Policy, -) (GRPC, error) { +func NewGRPC(logger logger.Logger, rpcAPI rpc.API, securityPolicy security.Policy, onShutdown func()) (GRPC, error) { server := grpc.NewServer() if !securityPolicy.IsEncrypted { return GRPC{ @@ -72,6 +78,7 @@ func NewGRPC( gRPCServer: grpc.NewServer(grpc.Creds(cred)), gRPCApi: rpcAPI, logger: logger, + onShutdown: onShutdown, }, nil } @@ -114,7 +121,7 @@ func (g *GRPCBuilder) Build() (GRPC, error) { CertificateFilePath: g.certPath, KeyFilePath: g.keyPath, } - return NewGRPC(g.logger, rpcAPI, policy) + return NewGRPC(g.logger, rpcAPI, policy, nil) } func NewGRPCBuilder(name string) *GRPCBuilder { diff --git a/fw/service/routing.go b/fw/service/routing.go index 0b8f05c..c9b6935 100644 --- a/fw/service/routing.go +++ b/fw/service/routing.go @@ -12,8 +12,9 @@ import ( var _ Service = (*Routing)(nil) type Routing struct { - logger logger.Logger - webServer *web.Server + logger logger.Logger + webServer *web.Server + onShutdown func() } func (r Routing) StartAsync(port int) { @@ -29,8 +30,14 @@ func (r Routing) StartAsync(port int) { }() } -func (r Routing) Stop(ctx context.Context) { +func (r Routing) Stop(ctx context.Context, cancel context.CancelFunc) { defer r.logger.Info("Routing service stopped") + defer func() { + if r.onShutdown != nil { + r.onShutdown() + } + cancel() + }() err := r.webServer.Shutdown(ctx) if err != nil { @@ -40,10 +47,11 @@ func (r Routing) Stop(ctx context.Context) { func (r Routing) StartAndWait(port int) { r.StartAsync(port) - select {} + + listenForSignals(r) } -func NewRouting(logger logger.Logger, routes []router.Route) Routing { +func NewRouting(logger logger.Logger, routes []router.Route, onShutdown func()) Routing { httpRouter := router.NewHTTPHandler() for _, route := range routes { @@ -62,8 +70,9 @@ func NewRouting(logger logger.Logger, routes []router.Route) Routing { server.HandleFunc("/", &httpRouter) return Routing{ - logger: logger, - webServer: &server, + logger: logger, + webServer: &server, + onShutdown: onShutdown, } } @@ -78,7 +87,7 @@ func (r *RoutingBuilder) Routes(routes []router.Route) *RoutingBuilder { } func (r RoutingBuilder) Build() Routing { - return NewRouting(r.logger, r.routes) + return NewRouting(r.logger, r.routes, nil) } func NewRoutingBuilder(name string) *RoutingBuilder { diff --git a/fw/service/service.go b/fw/service/service.go index 4456b13..86c5c68 100644 --- a/fw/service/service.go +++ b/fw/service/service.go @@ -1,9 +1,24 @@ package service -import "context" +import ( + "context" + "os" + "os/signal" + "syscall" + "time" +) type Service interface { - Stop(ctx context.Context) + Stop(ctx context.Context, cancel context.CancelFunc) StartAsync(port int) StartAndWait(port int) } + +func listenForSignals(s Service) { + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) + <-sigCh + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + s.Stop(ctx, cancel) +} From 6b66a99d6fe2f455138c4fe45ce42a555eae516a Mon Sep 17 00:00:00 2001 From: Arber Avdullahu Date: Thu, 28 May 2020 20:46:04 +0200 Subject: [PATCH 06/11] builder grpc --- example/graphql/main.go | 2 +- fw/service/graphql.go | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/example/graphql/main.go b/example/graphql/main.go index efc3f33..6343df3 100644 --- a/example/graphql/main.go +++ b/example/graphql/main.go @@ -36,7 +36,7 @@ type Query { } graphQLService := service. - NewGraphQLBuilder("Example"). + NewGraphQLBuilder("Example", nil). Schema(schema). Resolver(&res). Build() diff --git a/fw/service/graphql.go b/fw/service/graphql.go index ccde5d9..ba53318 100644 --- a/fw/service/graphql.go +++ b/fw/service/graphql.go @@ -70,9 +70,10 @@ func NewGraphQL( } type GraphQLBuilder struct { - logger logger.Logger - schema string - resolver graphql.Resolver + logger logger.Logger + schema string + resolver graphql.Resolver + onShutdown func() } func (g *GraphQLBuilder) Schema(schema string) *GraphQLBuilder { @@ -91,14 +92,15 @@ func (g GraphQLBuilder) Build() GraphQL { Resolver: g.resolver, } handler := graphql.NewGraphGopherHandler(api) - return NewGraphQL(g.logger, "/graphql", handler, nil) + return NewGraphQL(g.logger, "/graphql", handler, g.onShutdown) } -func NewGraphQLBuilder(name string) *GraphQLBuilder { +func NewGraphQLBuilder(name string, onShutdown func()) *GraphQLBuilder { lg := newDefaultLogger(name) return &GraphQLBuilder{ - logger: lg, - schema: "", - resolver: nil, + logger: lg, + schema: "", + resolver: nil, + onShutdown: onShutdown, } } From fc59381719180accb50052f89aea0c955aab74b2 Mon Sep 17 00:00:00 2001 From: Arber Avdullahu Date: Thu, 28 May 2020 20:47:53 +0200 Subject: [PATCH 07/11] format --- fw/service/grpc.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fw/service/grpc.go b/fw/service/grpc.go index 08ed392..a610940 100644 --- a/fw/service/grpc.go +++ b/fw/service/grpc.go @@ -56,7 +56,11 @@ func (g GRPC) StartAndWait(port int) { listenForSignals(g) } -func NewGRPC(logger logger.Logger, rpcAPI rpc.API, securityPolicy security.Policy, onShutdown func()) (GRPC, error) { +func NewGRPC(logger logger.Logger, + rpcAPI rpc.API, + securityPolicy security.Policy, + onShutdown func(), +) (GRPC, error) { server := grpc.NewServer() if !securityPolicy.IsEncrypted { return GRPC{ From 5d551c745a18cbd3c1f77d7fe78fbd7542047a76 Mon Sep 17 00:00:00 2001 From: Arber Avdullahu Date: Thu, 28 May 2020 20:48:22 +0200 Subject: [PATCH 08/11] format --- fw/service/grpc.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fw/service/grpc.go b/fw/service/grpc.go index a610940..b2143fb 100644 --- a/fw/service/grpc.go +++ b/fw/service/grpc.go @@ -56,7 +56,8 @@ func (g GRPC) StartAndWait(port int) { listenForSignals(g) } -func NewGRPC(logger logger.Logger, +func NewGRPC( + logger logger.Logger, rpcAPI rpc.API, securityPolicy security.Policy, onShutdown func(), From 346beb406a26c766c91309458955c5c0c649ba89 Mon Sep 17 00:00:00 2001 From: Arber Avdullahu Date: Thu, 28 May 2020 20:52:46 +0200 Subject: [PATCH 09/11] grpc builder --- fw/service/grpc.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fw/service/grpc.go b/fw/service/grpc.go index b2143fb..dc830a3 100644 --- a/fw/service/grpc.go +++ b/fw/service/grpc.go @@ -105,6 +105,7 @@ type GRPCBuilder struct { certPath string keyPath string registerHandler registerHandler + onShutdown func() } func (g *GRPCBuilder) EnableTLS(certPath string, keyPath string) *GRPCBuilder { @@ -126,10 +127,10 @@ func (g *GRPCBuilder) Build() (GRPC, error) { CertificateFilePath: g.certPath, KeyFilePath: g.keyPath, } - return NewGRPC(g.logger, rpcAPI, policy, nil) + return NewGRPC(g.logger, rpcAPI, policy, g.onShutdown) } -func NewGRPCBuilder(name string) *GRPCBuilder { +func NewGRPCBuilder(name string, onShutdown func()) *GRPCBuilder { lg := newDefaultLogger(name) builder := GRPCBuilder{ logger: lg, @@ -137,6 +138,7 @@ func NewGRPCBuilder(name string) *GRPCBuilder { certPath: "", keyPath: "", registerHandler: func(server *grpc.Server) {}, + onShutdown: onShutdown, } return &builder } From 4799998c4363d479c6ed7023ac6286a7ee475373 Mon Sep 17 00:00:00 2001 From: Arber Avdullahu Date: Thu, 28 May 2020 20:54:27 +0200 Subject: [PATCH 10/11] route builder --- fw/service/routing.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/fw/service/routing.go b/fw/service/routing.go index c9b6935..6285eae 100644 --- a/fw/service/routing.go +++ b/fw/service/routing.go @@ -77,8 +77,9 @@ func NewRouting(logger logger.Logger, routes []router.Route, onShutdown func()) } type RoutingBuilder struct { - logger logger.Logger - routes []router.Route + logger logger.Logger + routes []router.Route + onShutdown func() } func (r *RoutingBuilder) Routes(routes []router.Route) *RoutingBuilder { @@ -87,13 +88,14 @@ func (r *RoutingBuilder) Routes(routes []router.Route) *RoutingBuilder { } func (r RoutingBuilder) Build() Routing { - return NewRouting(r.logger, r.routes, nil) + return NewRouting(r.logger, r.routes, r.onShutdown) } -func NewRoutingBuilder(name string) *RoutingBuilder { +func NewRoutingBuilder(name string, onShutdown func()) *RoutingBuilder { lg := newDefaultLogger(name) return &RoutingBuilder{ - logger: lg, - routes: make([]router.Route, 0), + logger: lg, + routes: make([]router.Route, 0), + onShutdown: onShutdown, } } From 2c9ef807b5e44c257403d395c78ea63689190745 Mon Sep 17 00:00:00 2001 From: Arber Avdullahu Date: Thu, 28 May 2020 20:55:50 +0200 Subject: [PATCH 11/11] example --- example/grpc/server/main.go | 2 +- example/routing/main.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/example/grpc/server/main.go b/example/grpc/server/main.go index 31560f0..181f6ee 100644 --- a/example/grpc/server/main.go +++ b/example/grpc/server/main.go @@ -22,7 +22,7 @@ func (h helloServer) Hello(ctx context.Context, request *proto.HelloRequest) (*p func main() { gRPCService, err := service. - NewGRPCBuilder("Example"). + NewGRPCBuilder("Example", nil). RegisterHandler(func(server *grpc.Server) { hs := helloServer{} proto.RegisterHelloServer(server, hs) diff --git a/example/routing/main.go b/example/routing/main.go index 123a8ef..bf82ebb 100644 --- a/example/routing/main.go +++ b/example/routing/main.go @@ -23,7 +23,7 @@ func main() { } routingService := service. - NewRoutingBuilder("Example"). + NewRoutingBuilder("Example", nil). Routes(routes). Build() routingService.StartAndWait(8080)