diff --git a/Server/ws/export_test.go b/Server/ws/export_test.go index 36b91f39..18e4cfdb 100644 --- a/Server/ws/export_test.go +++ b/Server/ws/export_test.go @@ -6,6 +6,7 @@ import ( "context" "encoding/json" "fmt" + "net/http" "os/exec" "time" @@ -193,6 +194,12 @@ func (p *LiveKitProcess) GenerateConfigForTest() (string, error) { return p.generateConfig() } +// HTTPTransportForTest exposes the health-check client's transport so tests can +// assert it does not share http.DefaultTransport's connection pool. +func (p *LiveKitProcess) HTTPTransportForTest() http.RoundTripper { + return p.httpClient.Transport +} + // SetProcessCmdForTest sets cmd to a non-nil value to simulate "already running". func (p *LiveKitProcess) SetProcessCmdForTest() { p.mu.Lock() diff --git a/Server/ws/livekit_process.go b/Server/ws/livekit_process.go index e20bf86b..f5a513cf 100644 --- a/Server/ws/livekit_process.go +++ b/Server/ws/livekit_process.go @@ -45,6 +45,10 @@ func NewLiveKitProcess(cfg *config.VoiceConfig, tlsCfg *config.TLSConfig, dataDi tlsCfg: tlsCfg, dataDir: dataDir, httpClient: &http.Client{ + // Own the transport rather than inheriting http.DefaultTransport: + // its pool is shared process-wide, and httptest.Server.Close closes + // its idle connections, which severs in-flight health checks. + Transport: http.DefaultTransport.(*http.Transport).Clone(), CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }, diff --git a/Server/ws/livekit_test.go b/Server/ws/livekit_test.go index aa392c01..067ebc8c 100644 --- a/Server/ws/livekit_test.go +++ b/Server/ws/livekit_test.go @@ -982,6 +982,26 @@ func TestHealthCheck_Success(t *testing.T) { } } +// httptest.Server.Close calls CloseIdleConnections on the process-wide +// http.DefaultTransport, so a health-check client that falls back to it can have +// a pooled connection severed mid-request by any unrelated parallel test closing +// its own server ("http: CloseIdleConnections called"). The client must own its +// transport — in production that also keeps the health check off the connection +// pool every other DefaultTransport user in the process shares. +func TestHealthCheckClientOwnsItsTransport(t *testing.T) { + t.Parallel() + + proc := ws.NewLiveKitProcess(&config.VoiceConfig{}, &config.TLSConfig{}, t.TempDir()) + + tr := proc.HTTPTransportForTest() + if tr == nil { + t.Fatal("health-check client has no Transport, so it falls back to http.DefaultTransport") + } + if tr == http.DefaultTransport { + t.Error("health-check client shares http.DefaultTransport's connection pool") + } +} + func TestHealthCheck_ServerDown(t *testing.T) { t.Parallel()