From 36fb19cd9397957d8b2657eb32a92db5058e1071 Mon Sep 17 00:00:00 2001 From: AdaAibaby Date: Mon, 31 Aug 2026 14:56:31 +0800 Subject: [PATCH] fix(envd): bind HTTP listener before sync init chain to eliminate startup race MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move net.Listen to the top of run() so port :49983 is reachable from process start. Incoming connections are queued in the kernel accept backlog and dispatched once s.Serve(ln) begins — no change in HTTP semantics. Previously s.ListenAndServe() was called last, after a long synchronous chain (logger → filesystem service → cgroup manager → process service → api → port scanner/forwarder). Any blocking call in that chain — most plausibly writeCgroupProp writing memory.high/memory.max under host memory pressure — left the port returning ECONNREFUSED for the entire init duration while the process appeared alive. Orchestrators and health checks that TCP-dial :49983 could not distinguish this from a crash. Also add stderr phase logs around createCgroupManager (the most likely blocking point) so a hung init surfaces in logs rather than a silent stall. --- packages/envd/main.go | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/envd/main.go b/packages/envd/main.go index 20014da7ea..c6f67e5dd6 100644 --- a/packages/envd/main.go +++ b/packages/envd/main.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "log" + "net" "net/http" "os" "path/filepath" @@ -182,6 +183,20 @@ func run(stop <-chan struct{}) error { ctx, cancel := context.WithCancel(context.Background()) defer cancel() + // Bind the listener before any blocking initializer so the TCP port is + // reachable from process start. Incoming connections are queued in the + // kernel's accept backlog; nothing is dispatched until s.Serve(ln) below. + // Without this, a slow or hanging initializer (e.g. writeCgroupProp under + // memory pressure) produces a window where the process is alive but the port + // returns ECONNREFUSED — indistinguishable from a crashed envd to any + // TCP-based health check or orchestrator readiness probe. + ln, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", port)) + if err != nil { + return fmt.Errorf("bind envd listener: %w", err) + } + defer ln.Close() + fmt.Fprintf(os.Stderr, "envd: HTTP listener bound on :%d\n", port) + if err := os.MkdirAll(host.E2BRunDir, 0o755); err != nil { fmt.Fprintf(os.Stderr, "error creating E2B run directory: %v\n", err) } @@ -217,7 +232,9 @@ func run(stop <-chan struct{}) error { fsLogger := l.With().Str("logger", "filesystem").Logger() filesystemService := filesystemRpc.Handle(m, &fsLogger, defaults) + fmt.Fprintf(os.Stderr, "envd startup: creating cgroup manager\n") cgroupManager := createCgroupManager() + fmt.Fprintf(os.Stderr, "envd startup: cgroup manager ready\n") defer func() { err := cgroupManager.Close() if err != nil { @@ -476,7 +493,7 @@ func run(stop <-chan struct{}) error { } }) - // A stop request closes the listener, which makes ListenAndServe return. + // A stop request closes the listener, which makes Serve return. stopped := make(chan struct{}) defer close(stopped) go func() { @@ -487,7 +504,8 @@ func run(stop <-chan struct{}) error { } }() - err := s.ListenAndServe() + // err is already declared by net.Listen at the top of run(). + err = s.Serve(ln) if errors.Is(err, http.ErrServerClosed) { select { case <-stop: