@@ -30,6 +30,7 @@ import (
3030
3131 "github.com/zilliztech/woodpecker/common/channel"
3232 "github.com/zilliztech/woodpecker/common/config"
33+ "github.com/zilliztech/woodpecker/common/funcutil"
3334 "github.com/zilliztech/woodpecker/common/logger"
3435 "github.com/zilliztech/woodpecker/common/membership"
3536 wpNet "github.com/zilliztech/woodpecker/common/net"
@@ -42,7 +43,7 @@ type Server struct {
4243 cfg * config.Configuration
4344 serverNode * membership.ServerNode
4445 serverConfig * membership.ServerConfig // Configuration to be used for creating server node
45- seeds []string // Seeds for cluster joining
46+ gossipSeeds []string // Seeds for cluster joining
4647 logStore LogStore
4748 grpcWG sync.WaitGroup
4849 grpcErrChan chan error
@@ -54,7 +55,7 @@ type Server struct {
5455}
5556
5657// NewServer creates a new server instance with same bind/advertise ip/port
57- func NewServer (ctx context.Context , configuration * config.Configuration , bindPort int , servicePort int , seeds []string ) * Server {
58+ func NewServer (ctx context.Context , configuration * config.Configuration , bindPort int , servicePort int , gossipSeeds []string ) * Server {
5859 return NewServerWithConfig (ctx , configuration , & membership.ServerConfig {
5960 NodeID : "" , // Will be set in Prepare()
6061 BindPort : bindPort ,
@@ -64,11 +65,11 @@ func NewServer(ctx context.Context, configuration *config.Configuration, bindPor
6465 ResourceGroup : "default" , // Default resource group
6566 AZ : "default" , // Default availability zone
6667 Tags : map [string ]string {"role" : "logstore" },
67- }, seeds )
68+ }, gossipSeeds )
6869}
6970
7071// NewServerWithConfig creates a new server instance with custom configuration
71- func NewServerWithConfig (ctx context.Context , configuration * config.Configuration , serverConfig * membership.ServerConfig , seeds []string ) * Server {
72+ func NewServerWithConfig (ctx context.Context , configuration * config.Configuration , serverConfig * membership.ServerConfig , gossipSeeds []string ) * Server {
7273 ctx , cancel := context .WithCancel (context .Background ())
7374 var storageCli storageclient.ObjectStorage
7475 if configuration .Woodpecker .Storage .IsStorageMinio () || configuration .Woodpecker .Storage .IsStorageService () {
@@ -87,7 +88,7 @@ func NewServerWithConfig(ctx context.Context, configuration *config.Configuratio
8788 s .logStore = NewLogStore (ctx , configuration , storageCli )
8889 // Store the server config and seeds for later use in Prepare()
8990 s .serverConfig = serverConfig
90- s .seeds = seeds
91+ s .gossipSeeds = gossipSeeds
9192 return s
9293}
9394
@@ -101,8 +102,8 @@ func (s *Server) Prepare() error {
101102 s .logStore .SetAddress (s .listener .Addr ().String ())
102103
103104 // Start async join if seeds are provided
104- if len (s .seeds ) > 0 {
105- go s .asyncStartAndJoinSeeds (s .ctx , s .seeds )
105+ if len (s .gossipSeeds ) > 0 {
106+ go s .asyncStartAndJoinSeeds (s .ctx , s .gossipSeeds )
106107 }
107108
108109 return nil
@@ -142,9 +143,13 @@ func (s *Server) startGrpcLoop() {
142143 }
143144 s .grpcServer = grpc .NewServer (grpcOpts ... )
144145 proto .RegisterLogStoreServer (s .grpcServer , s )
146+ funcutil .CheckGrpcReady (s .ctx , s .grpcErrChan )
147+ logger .Ctx (s .ctx ).Info ("start grpc server" , zap .String ("nodeID" , s .serverConfig .NodeID ), zap .String ("address" , s .listener .Addr ().String ()))
145148 if err := s .grpcServer .Serve (s .listener ); err != nil {
149+ logger .Ctx (s .ctx ).Error ("grpc server failed" , zap .Error (err ))
146150 s .grpcErrChan <- err
147151 }
152+ logger .Ctx (s .ctx ).Info ("grpc server stopped" , zap .String ("nodeID" , s .serverConfig .NodeID ), zap .String ("address" , s .logStore .GetAddress ()))
148153}
149154
150155func (s * Server ) start () error {
@@ -157,18 +162,39 @@ func (s *Server) start() error {
157162}
158163
159164func (s * Server ) Stop () error {
165+ // First, stop accepting new connections by closing the listener
166+ if s .listener != nil {
167+ if err := s .listener .Close (); err != nil {
168+ logger .Ctx (s .ctx ).Warn ("failed to close listener" , zap .Error (err ))
169+ }
170+ }
171+
172+ // Leave and shutdown the gossip cluster
160173 if s .serverNode != nil {
174+ // First, notify other nodes we're leaving
161175 leaveErr := s .serverNode .Leave ()
162176 if leaveErr != nil {
163177 logger .Ctx (s .ctx ).Error ("server node leave failed" , zap .Error (leaveErr ))
164178 }
179+ // Then shutdown the memberlist to release ports immediately
180+ shutdownErr := s .serverNode .Shutdown ()
181+ if shutdownErr != nil {
182+ logger .Ctx (s .ctx ).Error ("server node shutdown failed" , zap .Error (shutdownErr ))
183+ }
165184 }
185+
186+ // Stop the log store
166187 stopErr := s .logStore .Stop ()
167188 if stopErr != nil {
168189 logger .Ctx (s .ctx ).Error ("log store stop failed" , zap .Error (stopErr ))
169190 }
191+
192+ // Gracefully stop the gRPC server (wait for in-flight requests)
170193 s .grpcServer .GracefulStop ()
194+
195+ // Cancel the context
171196 s .cancel ()
197+
172198 logger .Ctx (s .ctx ).Info ("server stopped" , zap .String ("nodeID" , s .serverConfig .NodeID ), zap .String ("address" , s .logStore .GetAddress ()))
173199 return nil
174200}
@@ -190,7 +216,8 @@ func (s *Server) AddEntry(request *proto.AddEntryRequest, serverStream grpc.Serv
190216 sendErr := serverStream .Send (& proto.AddEntryResponse {
191217 State : proto .AddEntryState_Failed ,
192218 EntryId : bufferedId ,
193- Status : werr .Status (err )},
219+ Status : werr .Status (err ),
220+ },
194221 )
195222 if sendErr != nil {
196223 logger .Ctx (streamCtx ).Warn ("failed to send error response" , zap .Error (sendErr ))
@@ -202,7 +229,8 @@ func (s *Server) AddEntry(request *proto.AddEntryRequest, serverStream grpc.Serv
202229 sendErr := serverStream .Send (& proto.AddEntryResponse {
203230 State : proto .AddEntryState_Buffered ,
204231 EntryId : bufferedId ,
205- Status : werr .Success ()},
232+ Status : werr .Success (),
233+ },
206234 )
207235 if sendErr != nil {
208236 logger .Ctx (streamCtx ).Warn ("failed to send buffered response" , zap .Error (sendErr ))
@@ -218,15 +246,17 @@ func (s *Server) AddEntry(request *proto.AddEntryRequest, serverStream grpc.Serv
218246 sendErr = serverStream .Send (& proto.AddEntryResponse {
219247 State : proto .AddEntryState_Failed ,
220248 EntryId : id ,
221- Status : werr .Status (err )},
249+ Status : werr .Status (err ),
250+ },
222251 )
223252 return sendErr
224253 }
225254 // persist added entry success
226255 sendErr = serverStream .Send (& proto.AddEntryResponse {
227256 State : proto .AddEntryState_Synced ,
228257 EntryId : result .SyncedId ,
229- Status : werr .Success ()},
258+ Status : werr .Success (),
259+ },
230260 )
231261 return sendErr // Return nil for normal closure, not the context error
232262}
@@ -372,16 +402,7 @@ func (s *Server) GetServiceAdvertiseAddrPort(ctx context.Context) string {
372402
373403// GetAdvertiseAddrPort Use for test only
374404func (s * Server ) GetAdvertiseAddrPort (ctx context.Context ) string {
375- if s .serverNode == nil {
376- return ""
377- }
378- // Get the actual gossip advertise address from the memberlist configuration
379- ml := s .serverNode .GetMemberlist ()
380- config := ml .LocalNode ()
381- // Use the actual advertise address from memberlist, fall back to bind address if not set
382- addr := config .Addr
383- port := config .Port
384- return fmt .Sprintf ("%s:%d" , addr , port )
405+ return fmt .Sprintf ("%s:%d" , s .serverConfig .AdvertiseAddr , s .serverConfig .AdvertisePort )
385406}
386407
387408// asyncJoinSeeds continuously monitors and joins missing seed nodes with adaptive backoff [[memory:3527742]]
0 commit comments