From cf89d7b25e5cf52eea87674abbb3d69982664aaa Mon Sep 17 00:00:00 2001 From: grantmartin2002-oss Date: Thu, 9 Apr 2026 18:59:47 -0500 Subject: [PATCH 1/2] server/grpc: add configurable graceful shutdown timeout (v4 & v5) The gRPC server previously had a hardcoded 1-second timeout after calling GracefulStop() before forcefully terminating connections with Stop(). This is insufficient for applications with long-lived gRPC streams that need time to drain during shutdown. Add a GracefulStopTimeout option that allows users to configure how long the server waits for in-flight requests to complete. Defaults to 1 second for backwards compatibility. Fixes #162 Co-Authored-By: Claude Opus 4.6 --- v4/server/grpc/grpc.go | 7 ++++++- v4/server/grpc/options.go | 9 +++++++++ v5/server/grpc/grpc.go | 7 ++++++- v5/server/grpc/options.go | 9 +++++++++ 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/v4/server/grpc/grpc.go b/v4/server/grpc/grpc.go index edb85d73..3ae4cdb2 100644 --- a/v4/server/grpc/grpc.go +++ b/v4/server/grpc/grpc.go @@ -983,9 +983,14 @@ func (g *grpcServer) Start() error { close(exit) }() + gracefulTimeout := time.Second + if v, ok := g.opts.Context.Value(gracefulStopTimeoutKey{}).(time.Duration); ok && v > 0 { + gracefulTimeout = v + } + select { case <-exit: - case <-time.After(time.Second): + case <-time.After(gracefulTimeout): g.srv.Stop() } diff --git a/v4/server/grpc/options.go b/v4/server/grpc/options.go index ca0c199a..1a59fa50 100644 --- a/v4/server/grpc/options.go +++ b/v4/server/grpc/options.go @@ -4,6 +4,7 @@ import ( "context" "crypto/tls" "net" + "time" "go-micro.dev/v4/broker" "go-micro.dev/v4/codec" @@ -22,6 +23,7 @@ type maxMsgSizeKey struct{} type maxConnKey struct{} type tlsAuth struct{} type grpcServerKey struct{} +type gracefulStopTimeoutKey struct{} // gRPC Codec to be used to encode/decode requests for a given content type. func Codec(contentType string, c encoding.Codec) server.Option { @@ -72,6 +74,13 @@ func MaxMsgSize(s int) server.Option { return setServerOption(maxMsgSizeKey{}, s) } +// GracefulStopTimeout sets the maximum duration the server will wait for +// in-flight requests to complete during graceful shutdown before forcefully +// stopping. Default is 1 second. +func GracefulStopTimeout(d time.Duration) server.Option { + return setServerOption(gracefulStopTimeoutKey{}, d) +} + func newOptions(opt ...server.Option) server.Options { opts := server.Options{ Codecs: make(map[string]codec.NewCodec), diff --git a/v5/server/grpc/grpc.go b/v5/server/grpc/grpc.go index e050ae1a..1bd472f6 100644 --- a/v5/server/grpc/grpc.go +++ b/v5/server/grpc/grpc.go @@ -983,9 +983,14 @@ func (g *grpcServer) Start() error { close(exit) }() + gracefulTimeout := time.Second + if v, ok := g.opts.Context.Value(gracefulStopTimeoutKey{}).(time.Duration); ok && v > 0 { + gracefulTimeout = v + } + select { case <-exit: - case <-time.After(time.Second): + case <-time.After(gracefulTimeout): g.srv.Stop() } diff --git a/v5/server/grpc/options.go b/v5/server/grpc/options.go index e9a48a3a..6ff91cdd 100644 --- a/v5/server/grpc/options.go +++ b/v5/server/grpc/options.go @@ -4,6 +4,7 @@ import ( "context" "crypto/tls" "net" + "time" "go-micro.dev/v5/broker" "go-micro.dev/v5/codec" @@ -22,6 +23,7 @@ type maxMsgSizeKey struct{} type maxConnKey struct{} type tlsAuth struct{} type grpcServerKey struct{} +type gracefulStopTimeoutKey struct{} // gRPC Codec to be used to encode/decode requests for a given content type. func Codec(contentType string, c encoding.Codec) server.Option { @@ -72,6 +74,13 @@ func MaxMsgSize(s int) server.Option { return setServerOption(maxMsgSizeKey{}, s) } +// GracefulStopTimeout sets the maximum duration the server will wait for +// in-flight requests to complete during graceful shutdown before forcefully +// stopping. Default is 1 second. +func GracefulStopTimeout(d time.Duration) server.Option { + return setServerOption(gracefulStopTimeoutKey{}, d) +} + func newOptions(opt ...server.Option) server.Options { opts := server.Options{ Codecs: make(map[string]codec.NewCodec), From 554e442106ed74afe1ae93124aa778e9dee72338 Mon Sep 17 00:00:00 2001 From: grantmartin2002-oss Date: Thu, 9 Apr 2026 19:08:43 -0500 Subject: [PATCH 2/2] style: shorten GracefulStopTimeout comment to match existing style Co-Authored-By: Claude Opus 4.6 --- v4/server/grpc/options.go | 5 ++--- v5/server/grpc/options.go | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/v4/server/grpc/options.go b/v4/server/grpc/options.go index 1a59fa50..a9f40e47 100644 --- a/v4/server/grpc/options.go +++ b/v4/server/grpc/options.go @@ -74,9 +74,8 @@ func MaxMsgSize(s int) server.Option { return setServerOption(maxMsgSizeKey{}, s) } -// GracefulStopTimeout sets the maximum duration the server will wait for -// in-flight requests to complete during graceful shutdown before forcefully -// stopping. Default is 1 second. +// GracefulStopTimeout set the timeout for the server to stop gracefully +// before forcefully stopping. Default is 1 second. func GracefulStopTimeout(d time.Duration) server.Option { return setServerOption(gracefulStopTimeoutKey{}, d) } diff --git a/v5/server/grpc/options.go b/v5/server/grpc/options.go index 6ff91cdd..224b5d42 100644 --- a/v5/server/grpc/options.go +++ b/v5/server/grpc/options.go @@ -74,9 +74,8 @@ func MaxMsgSize(s int) server.Option { return setServerOption(maxMsgSizeKey{}, s) } -// GracefulStopTimeout sets the maximum duration the server will wait for -// in-flight requests to complete during graceful shutdown before forcefully -// stopping. Default is 1 second. +// GracefulStopTimeout set the timeout for the server to stop gracefully +// before forcefully stopping. Default is 1 second. func GracefulStopTimeout(d time.Duration) server.Option { return setServerOption(gracefulStopTimeoutKey{}, d) }