@@ -10,6 +10,8 @@ import (
1010 "net/url"
1111 "strconv"
1212 "sync"
13+ "syscall"
14+ "time"
1315
1416 "go.uber.org/zap"
1517
@@ -153,15 +155,22 @@ func (s *Server) listen() error {
153155 return nil
154156}
155157
156- func (s * Server ) serve () {
158+ func (s * Server ) serve (currentServer * http. Server , currentListener net. Listener ) {
157159 defer common .LogOnPanic ()
158160
159161 defer func () {
160- s .isRunning = false
161- s .address = nil
162+ s .lifecycleMu .Lock ()
163+ defer s .lifecycleMu .Unlock ()
164+
165+ // If a newer Start() already replaced server/listener, do not clobber
166+ // the latest running instance state.
167+ if s .server == currentServer && s .listener == currentListener {
168+ s .isRunning = false
169+ s .address = nil
170+ }
162171 }()
163172
164- err := s . server . Serve (s . listener )
173+ err := currentServer . Serve (currentListener )
165174 if errors .Is (err , http .ErrServerClosed ) {
166175 return
167176 }
@@ -209,23 +218,26 @@ func (s *Server) Start() error {
209218 // Mark running synchronously to avoid pause/play races where ToBackground
210219 // can run before serve() goroutine has a chance to set the state.
211220 s .isRunning = true
212- go s .serve ()
221+ go s .serve (s . server , s . listener )
213222 return nil
214223}
215224
216225func (s * Server ) Stop () error {
217226 s .lifecycleMu .Lock ()
218- defer s .lifecycleMu .Unlock ()
219-
220227 s .StopTimeout ()
221228 if ! s .isRunning || s .server == nil {
229+ s .lifecycleMu .Unlock ()
222230 return nil
223231 }
224232
225- // Flip state before shutdown so rapid foreground/background transitions
226- // don't attempt a concurrent second bind to a cached port.
233+ // Capture the current instance and release the lock before Shutdown.
234+ // Shutdown waits for Serve() to return, and Serve() may update state in
235+ // its defer path under the same mutex.
236+ currentServer := s .server
227237 s .isRunning = false
228- return s .server .Shutdown (context .Background ())
238+ s .lifecycleMu .Unlock ()
239+
240+ return currentServer .Shutdown (context .Background ())
229241}
230242
231243func (s * Server ) IsRunning () bool {
@@ -234,9 +246,26 @@ func (s *Server) IsRunning() bool {
234246
235247func (s * Server ) ToForeground () {
236248 err := s .Start ()
237- if err != nil {
238- s .logger .Error ("server start failed during foreground transition" , zap .Error (err ))
249+ if err == nil {
250+ return
251+ }
252+
253+ // On rapid pause/resume cycles with ephemeral ports, the previous listener
254+ // close can lag briefly and return EADDRINUSE for the cached port.
255+ // Retry a few times to preserve stable URL reuse semantics.
256+ if s .config != nil && s .config .AddrPort .Port () == 0 && s .cachedPort != 0 && errors .Is (err , syscall .EADDRINUSE ) {
257+ for i := 0 ; i < 10 ; i ++ {
258+ time .Sleep (20 * time .Millisecond )
259+ err = s .Start ()
260+ if err == nil {
261+ return
262+ }
263+ if ! errors .Is (err , syscall .EADDRINUSE ) {
264+ break
265+ }
266+ }
239267 }
268+ s .logger .Error ("server start failed during foreground transition" , zap .Error (err ))
240269}
241270
242271func (s * Server ) ToBackground () {
0 commit comments