@@ -19,7 +19,6 @@ import (
1919	"fmt" 
2020	"net/http" 
2121	"strings" 
22- 	"sync" 
2322	"sync/atomic" 
2423	"time" 
2524
@@ -33,16 +32,6 @@ import (
3332	"github.com/bucketeer-io/bucketeer/v2/pkg/token" 
3433)
3534
36- const  (
37- 	// minGracefulTimeout is the minimum timeout for graceful shutdown operations. 
38- 	// This ensures that shutdown operations have at least some time to complete. 
39- 	minGracefulTimeout  =  1  *  time .Second 
40- 
41- 	// httpServerBuffer is the additional time reserved for HTTP server shutdown 
42- 	// after gRPC server completes. This prevents premature HTTP termination. 
43- 	httpServerBuffer  =  1  *  time .Second 
44- )
45- 
4635type  Server  struct  {
4736	certPath          string 
4837	keyPath           string 
@@ -55,7 +44,7 @@ type Server struct {
5544	handlers          []httpHandler 
5645	rpcServer         * grpc.Server 
5746	httpServer        * http.Server 
58- 	grpcWebServer     * grpcweb.WrappedGrpcServer 
47+ 	grpcWebServer     * grpcweb.WrappedGrpcServer   // DEPRECATED: Remove once Node.js SDK migrates away from grpc-web 
5948	readTimeout       time.Duration 
6049	writeTimeout      time.Duration 
6150	idleTimeout       time.Duration 
@@ -157,81 +146,42 @@ func (s *Server) Stop(timeout time.Duration) {
157146		zap .String ("server" , s .name ),
158147		zap .Duration ("timeout" , timeout ))
159148
160- 	var  wg  sync.WaitGroup 
161- 	shutdownErrors  :=  make (chan  error , 2 )
149+ 	// Shutdown order is critical: 
150+ 	// 1. HTTP server first (drains REST/gRPC-Gateway requests) 
151+ 	// 2. gRPC server second (only pure gRPC connections remain) 
152+ 	// 
153+ 	// This ensures HTTP requests that call s.rpcServer.ServeHTTP() can complete 
154+ 	// before we stop the underlying gRPC server. 
162155
163- 	// Shutdown gRPC server 
164- 	if  s .rpcServer  !=  nil  {
165- 		wg .Add (1 )
166- 		go  func () {
167- 			defer  wg .Done ()
168- 			s .logger .Info ("Starting gRPC server graceful shutdown" )
169- 
170- 			// Use a shorter timeout for gRPC to leave time for HTTP 
171- 			grpcTimeout  :=  timeout  -  httpServerBuffer 
172- 			if  grpcTimeout  <  minGracefulTimeout  {
173- 				grpcTimeout  =  minGracefulTimeout 
174- 			}
175- 
176- 			done  :=  make (chan  struct {})
177- 			go  func () {
178- 				s .rpcServer .GracefulStop ()
179- 				close (done )
180- 			}()
181- 
182- 			select  {
183- 			case  <- done :
184- 				s .logger .Info ("gRPC server shutdown completed gracefully" )
185- 			case  <- time .After (grpcTimeout ):
186- 				s .logger .Warn ("gRPC server graceful shutdown timed out, forcing stop" )
187- 				s .rpcServer .Stop ()
188- 			}
189- 		}()
190- 	}
191- 
192- 	// Shutdown HTTP server 
156+ 	// Step 1: Shutdown HTTP server gracefully 
193157	if  s .httpServer  !=  nil  {
194- 		wg .Add (1 )
195- 		go  func () {
196- 			defer  wg .Done ()
197- 			s .logger .Info ("Starting HTTP server graceful shutdown" )
198- 
199- 			ctx , cancel  :=  context .WithTimeout (context .Background (), timeout )
200- 			defer  cancel ()
201- 
202- 			if  err  :=  s .httpServer .Shutdown (ctx ); err  !=  nil  {
203- 				s .logger .Error ("HTTP server failed to shut down gracefully" , zap .Error (err ))
204- 				shutdownErrors  <-  err 
205- 			} else  {
206- 				s .logger .Info ("HTTP server shutdown completed gracefully" )
207- 			}
208- 		}()
209- 	}
158+ 		s .logger .Info ("Starting HTTP server graceful shutdown" )
210159
211- 	// Wait for all shutdowns to complete or timeout 
212- 	done  :=  make (chan  struct {})
213- 	go  func () {
214- 		wg .Wait ()
215- 		close (done )
216- 	}()
217- 
218- 	select  {
219- 	case  <- done :
220- 		s .logger .Info ("Server shutdown completed" ,
221- 			zap .String ("server" , s .name ),
222- 			zap .Duration ("total_duration" , time .Since (shutdownStart )))
223- 	case  <- time .After (timeout ):
224- 		s .logger .Warn ("Server shutdown timed out" ,
225- 			zap .String ("server" , s .name ),
226- 			zap .Duration ("timeout" , timeout ))
160+ 		ctx , cancel  :=  context .WithTimeout (context .Background (), timeout )
161+ 		defer  cancel ()
162+ 
163+ 		if  err  :=  s .httpServer .Shutdown (ctx ); err  !=  nil  {
164+ 			s .logger .Error ("HTTP server failed to shut down gracefully" , zap .Error (err ))
165+ 		} else  {
166+ 			s .logger .Info ("HTTP server shutdown completed gracefully" )
167+ 		}
227168	}
228169
229- 	// Log any shutdown errors 
230- 	close (shutdownErrors )
231- 	for  err  :=  range  shutdownErrors  {
232- 		s .logger .Error ("Shutdown error" , zap .Error (err ))
170+ 	// Step 2: Stop gRPC server 
171+ 	// Note: We use Stop() instead of GracefulStop() because: 
172+ 	// - GracefulStop() panics on HTTP-served connections (serverHandlerTransport.Drain not implemented) 
173+ 	// - HTTP-served connections were already drained in step 1 
174+ 	// - Pure gRPC clients have retry logic and Envoy connection draining to handle this 
175+ 	if  s .rpcServer  !=  nil  {
176+ 		s .logger .Info ("Stopping gRPC server" )
177+ 		s .rpcServer .Stop ()
178+ 		s .logger .Info ("gRPC server stopped" )
233179	}
234180
181+ 	s .logger .Info ("Server shutdown completed" ,
182+ 		zap .String ("server" , s .name ),
183+ 		zap .Duration ("total_duration" , time .Since (shutdownStart )))
184+ 
235185	// Mark shutdown as complete for Envoy coordination 
236186	atomic .StoreInt32 (& s .shutdownComplete , 1 )
237187	s .logger .Info ("Shutdown complete flag set, Envoy can now terminate" )
@@ -260,7 +210,9 @@ func (s *Server) setupRPC() {
260210		service .Register (s .rpcServer )
261211	}
262212
263- 	// Create gRPC-Web wrapper 
213+ 	// DEPRECATED: grpc-web support for legacy Node.js SDK 
214+ 	// TODO: Remove once Node.js SDK migrates to gRPC-Gateway (REST) or pure gRPC 
215+ 	// This is an abandoned library (last updated 2021) with known issues. 
264216	s .grpcWebServer  =  grpcweb .WrapServer (s .rpcServer )
265217}
266218
@@ -287,6 +239,8 @@ func (s *Server) setupHTTP() {
287239
288240	// Wrap the main handler with shutdown tracking middleware 
289241	mainHandler  :=  http .HandlerFunc (func (resp  http.ResponseWriter , req  * http.Request ) {
242+ 		// DEPRECATED: grpc-web support for legacy Node.js SDK 
243+ 		// This check should be removed once Node.js SDK migrates away from grpc-web 
290244		if  s .grpcWebServer .IsGrpcWebRequest (req ) {
291245			s .grpcWebServer .ServeHTTP (resp , req )
292246		} else  if  isRPC (req ) {
0 commit comments