Skip to content

Commit 03e7404

Browse files
committed
fix: Reuse the same port for media services on reconnect
1 parent 1cf30d0 commit 03e7404

File tree

1 file changed

+44
-17
lines changed

1 file changed

+44
-17
lines changed

server/server.go

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"net/netip"
1010
"net/url"
1111
"strconv"
12+
"sync"
1213

1314
"go.uber.org/zap"
1415

@@ -26,6 +27,8 @@ type Config struct {
2627
}
2728

2829
type Server struct {
30+
lifecycleMu sync.Mutex
31+
2932
listener net.Listener
3033
server *http.Server
3134
logger *zap.Logger
@@ -39,6 +42,10 @@ type Server struct {
3942
// isRunning is true if the server was started and is running
4043
isRunning bool
4144

45+
// cachedPort stores the port from the first successful bind when AddrPort used port 0 (ephemeral).
46+
// Reused on pause/resume so URLs remain valid across ToBackground/ToForeground cycles.
47+
cachedPort int
48+
4249
*timeoutManager
4350
}
4451

@@ -91,14 +98,24 @@ func (s *Server) GetLogger() *zap.Logger {
9198
return s.logger
9299
}
93100

101+
// getBindAddrPort returns the address to bind to. When config used port 0 (ephemeral)
102+
// and we have a cached port from a previous run, reuse it so URLs stay stable across pause/resume.
103+
func (s *Server) getBindAddrPort() netip.AddrPort {
104+
if s.cachedPort != 0 && s.config.AddrPort.Port() == 0 {
105+
return netip.AddrPortFrom(s.config.AddrPort.Addr(), uint16(s.cachedPort))
106+
}
107+
return s.config.AddrPort
108+
}
109+
94110
func (s *Server) createListener() (net.Listener, error) {
111+
addr := s.getBindAddrPort()
95112
if s.config.Cert == nil {
96113
// HTTP mode
97-
return net.Listen("tcp", s.config.AddrPort.String())
114+
return net.Listen("tcp", addr.String())
98115
}
99116

100117
// HTTPS mode
101-
serverName := s.config.AddrPort.Addr().String()
118+
serverName := addr.Addr().String()
102119
if len(s.config.Cert.Leaf.DNSNames) > 0 {
103120
serverName = s.config.Cert.Leaf.DNSNames[0]
104121
}
@@ -108,7 +125,7 @@ func (s *Server) createListener() (net.Listener, error) {
108125
ServerName: serverName,
109126
MinVersion: tls.VersionTLS12,
110127
}
111-
return tls.Listen("tcp", s.config.AddrPort.String(), cfg)
128+
return tls.Listen("tcp", addr.String(), cfg)
112129
}
113130

114131
func (s *Server) listen() error {
@@ -122,6 +139,9 @@ func (s *Server) listen() error {
122139
}
123140

124141
s.address = s.listener.Addr().(*net.TCPAddr)
142+
if s.config.AddrPort.Port() == 0 {
143+
s.cachedPort = s.address.Port
144+
}
125145

126146
s.StartTimeout(func() {
127147
err := s.Stop()
@@ -136,7 +156,6 @@ func (s *Server) listen() error {
136156
func (s *Server) serve() {
137157
defer common.LogOnPanic()
138158

139-
s.isRunning = true
140159
defer func() {
141160
s.isRunning = false
142161
s.address = nil
@@ -171,6 +190,9 @@ func (s *Server) applyHandlers() {
171190
}
172191

173192
func (s *Server) Start() error {
193+
s.lifecycleMu.Lock()
194+
defer s.lifecycleMu.Unlock()
195+
174196
if s.isRunning {
175197
return nil
176198
}
@@ -184,38 +206,43 @@ func (s *Server) Start() error {
184206
return err
185207
}
186208

209+
// Mark running synchronously to avoid pause/play races where ToBackground
210+
// can run before serve() goroutine has a chance to set the state.
211+
s.isRunning = true
187212
go s.serve()
188213
return nil
189214
}
190215

191216
func (s *Server) Stop() error {
217+
s.lifecycleMu.Lock()
218+
defer s.lifecycleMu.Unlock()
219+
192220
s.StopTimeout()
193-
if s.server != nil {
194-
return s.server.Shutdown(context.Background())
221+
if !s.isRunning || s.server == nil {
222+
return nil
195223
}
196224

197-
return nil
225+
// Flip state before shutdown so rapid foreground/background transitions
226+
// don't attempt a concurrent second bind to a cached port.
227+
s.isRunning = false
228+
return s.server.Shutdown(context.Background())
198229
}
199230

200231
func (s *Server) IsRunning() bool {
201232
return s.isRunning
202233
}
203234

204235
func (s *Server) ToForeground() {
205-
if !s.isRunning && (s.server != nil) {
206-
err := s.Start()
207-
if err != nil {
208-
s.logger.Error("server start failed during foreground transition", zap.Error(err))
209-
}
236+
err := s.Start()
237+
if err != nil {
238+
s.logger.Error("server start failed during foreground transition", zap.Error(err))
210239
}
211240
}
212241

213242
func (s *Server) ToBackground() {
214-
if s.isRunning {
215-
err := s.Stop()
216-
if err != nil {
217-
s.logger.Error("server stop failed during background transition", zap.Error(err))
218-
}
243+
err := s.Stop()
244+
if err != nil {
245+
s.logger.Error("server stop failed during background transition", zap.Error(err))
219246
}
220247
}
221248

0 commit comments

Comments
 (0)