Skip to content

Commit b5370e3

Browse files
authored
fix(proxy/test): eliminate startup race in TestChangeResponseHeader (#2443)
The test started internal/masked HTTP servers via http.Server.ListenAndServe in goroutines without waiting for the listener to come up, then fired a request through the proxy. If the proxy connected to the internal server before its goroutine had finished binding to the port, the proxy would return a non-2xx error and the retry loop (which only retries on ECONNREFUSED of the *outer* request to the proxy) would not retry it, producing intermittent CI failures on slower runners. It also used hardcoded ports 30090/30091/30092 which can collide with anything else on the box. Switch the internal and masked servers to the same Listen(":0") + Serve pattern the rest of this file already uses, and derive maskedHost from the listener address. The proxy itself still uses ListenAndServe with a fixed port (its constructor takes a port number), so the existing ECONNREFUSED retry on the outer request is preserved. No behavioural change to the proxy or to what the test asserts.
1 parent 04dfdf1 commit b5370e3

1 file changed

Lines changed: 49 additions & 32 deletions

File tree

packages/shared/pkg/proxy/proxy_test.go

Lines changed: 49 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -886,19 +886,33 @@ type data struct {
886886
// should return the "internal" server and not "masked" server.
887887
func TestChangeResponseHeader(t *testing.T) {
888888
t.Parallel()
889-
proxyPort := uint16(30092)
890-
internalPort := uint64(30090)
891-
maskedPort := uint16(30091)
892889

893-
client := &http.Client{}
890+
var lisCfg net.ListenConfig
894891

895-
proxyURL, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", proxyPort))
892+
// Pre-listen on ephemeral ports so we know the servers are accepting
893+
// connections before the test starts firing requests, and to avoid
894+
// hardcoded-port collisions between parallel test runs.
895+
internalListener, err := lisCfg.Listen(t.Context(), "tcp", "127.0.0.1:0")
896896
require.NoError(t, err)
897-
maskedHost := fmt.Sprintf("127.0.0.1:%d", maskedPort)
898-
internalURL, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", internalPort))
897+
internalAddr := internalListener.Addr().(*net.TCPAddr)
898+
internalPort := uint64(internalAddr.Port) //nolint:gosec // test-only port number always fits
899+
internalURL, err := url.Parse(fmt.Sprintf("http://%s", internalAddr.String()))
899900
require.NoError(t, err)
900901

901-
// start proxy
902+
maskedListener, err := lisCfg.Listen(t.Context(), "tcp", "127.0.0.1:0")
903+
require.NoError(t, err)
904+
maskedAddr := maskedListener.Addr().(*net.TCPAddr)
905+
maskedHost := maskedAddr.String()
906+
907+
proxyListener, err := lisCfg.Listen(t.Context(), "tcp", "127.0.0.1:0")
908+
require.NoError(t, err)
909+
proxyAddr := proxyListener.Addr().(*net.TCPAddr)
910+
proxyPort := uint16(proxyAddr.Port) //nolint:gosec // test-only port number always fits
911+
proxyURL, err := url.Parse(fmt.Sprintf("http://%s", proxyAddr.String()))
912+
require.NoError(t, err)
913+
914+
client := &http.Client{}
915+
902916
proxy := New(proxyPort, 1, time.Second, func(_ *http.Request) (*pool.Destination, error) {
903917
return &pool.Destination{
904918
Url: internalURL,
@@ -913,7 +927,7 @@ func TestChangeResponseHeader(t *testing.T) {
913927
}, nil, false)
914928

915929
go func() {
916-
err = proxy.ListenAndServe(t.Context())
930+
err := proxy.Serve(proxyListener)
917931
assert.ErrorIs(t, err, http.ErrServerClosed)
918932
}()
919933

@@ -922,54 +936,57 @@ func TestChangeResponseHeader(t *testing.T) {
922936
assert.NoError(t, err)
923937
})
924938

925-
// start internal server
926-
internalServer := http.Server{
927-
Addr: fmt.Sprintf("127.0.0.1:%d", internalPort),
939+
internalServer := &http.Server{
928940
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
929941
w.WriteHeader(http.StatusOK)
930-
err = json.NewEncoder(w).Encode(data{"internal", r.Host, r.Header})
931-
assert.NoError(t, err)
942+
encErr := json.NewEncoder(w).Encode(data{"internal", r.Host, r.Header})
943+
assert.NoError(t, encErr)
932944
}),
933945
}
934946
go func() {
935-
err = internalServer.ListenAndServe()
936-
assert.NoError(t, err)
947+
serveErr := internalServer.Serve(internalListener)
948+
assert.ErrorIs(t, serveErr, http.ErrServerClosed)
937949
}()
950+
t.Cleanup(func() {
951+
assert.NoError(t, internalServer.Close())
952+
})
938953

939-
// start fake server
940-
maskedServer := http.Server{
941-
Addr: fmt.Sprintf("127.0.0.1:%d", maskedPort),
954+
maskedServer := &http.Server{
942955
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
943956
w.WriteHeader(http.StatusOK)
944-
err = json.NewEncoder(w).Encode(data{"masked", r.Host, r.Header})
945-
assert.NoError(t, err)
957+
encErr := json.NewEncoder(w).Encode(data{"masked", r.Host, r.Header})
958+
assert.NoError(t, encErr)
946959
}),
947960
}
948961
go func() {
949-
err = maskedServer.ListenAndServe()
950-
assert.NoError(t, err)
962+
serveErr := maskedServer.Serve(maskedListener)
963+
assert.ErrorIs(t, serveErr, http.ErrServerClosed)
951964
}()
965+
t.Cleanup(func() {
966+
assert.NoError(t, maskedServer.Close())
967+
})
952968

953-
// create request
954969
req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, proxyURL.String(), nil)
955-
req.Header.Set("Host", fmt.Sprintf("localhost:%d", proxyPort))
956-
req.Header.Set("e2b-testing", "test123")
957970
require.NoError(t, err)
971+
req.Host = fmt.Sprintf("localhost:%d", proxyPort)
972+
req.Header.Set("e2b-testing", "test123")
958973

974+
// Retry while the proxy goroutine is still binding to the port. Once the
975+
// proxy is up, the internal server is guaranteed listening (we pre-bound
976+
// its listener above), so we don't need to retry on bad status.
959977
var rsp *http.Response
960-
for range 10 {
978+
for range 50 {
961979
rsp, err = client.Do(req)
962980
if err == nil {
963981
t.Cleanup(func() {
964-
err = rsp.Body.Close()
965-
assert.NoError(t, err)
982+
assert.NoError(t, rsp.Body.Close())
966983
})
967984

968985
break
969986
}
970987

971988
if errors.Is(err, syscall.ECONNREFUSED) {
972-
time.Sleep(100 * time.Millisecond)
989+
time.Sleep(20 * time.Millisecond)
973990

974991
continue
975992
}
@@ -987,9 +1004,9 @@ func TestChangeResponseHeader(t *testing.T) {
9871004
require.NoError(t, err)
9881005

9891006
assert.Equal(t, "internal", data.Tag)
990-
assert.Equal(t, fmt.Sprintf("127.0.0.1:%d", maskedPort), data.Host)
1007+
assert.Equal(t, maskedHost, data.Host)
9911008
assert.Equal(t, "test123", data.Headers.Get("E2b-Testing"))
992-
assert.Equal(t, fmt.Sprintf("127.0.0.1:%d", proxyPort), data.Headers.Get("X-Forwarded-Host"))
1009+
assert.Equal(t, fmt.Sprintf("localhost:%d", proxyPort), data.Headers.Get("X-Forwarded-Host"))
9931010
}
9941011

9951012
func TestConnectionLimitBlocksExcessConnections(t *testing.T) {

0 commit comments

Comments
 (0)