From 7de0bdde53953f419933e2d20a3e3b4da6570baa Mon Sep 17 00:00:00 2001 From: Chuang Wang Date: Mon, 27 Jul 2026 00:54:50 -0700 Subject: [PATCH] Add a readyz probe to the benchmark glutton actor ResumeActor returned as soon as the resume workflow finished, not when glutton's port 80 listener was accepting connections. A ping issued right after resume could land in that gap, get ECONNREFUSED, and come back from the router as a 503. ateom already has the gate: internal/readyz.WaitAll blocks RestoreWorkload and RunWorkload until every container that declares a readyz probe returns 200. Containers without a probe are skipped entirely, so glutton was never waited on. This adds /readyz to the HTTP mux -- on the same listener as /ping, since the metrics server starts independently and would answer 200 before /ping is reachable -- and points the ActorTemplate at it. Note that ActorTemplate.spec is immutable, so picking this up needs a delete and recreate of the template, not an apply. --- benchmarking/workloads/manifests/workloads.yaml.tmpl | 9 +++++++++ cmd/benchmarking/glutton/main.go | 12 ++++++++++++ 2 files changed, 21 insertions(+) diff --git a/benchmarking/workloads/manifests/workloads.yaml.tmpl b/benchmarking/workloads/manifests/workloads.yaml.tmpl index 5fa120487..84f2c062f 100644 --- a/benchmarking/workloads/manifests/workloads.yaml.tmpl +++ b/benchmarking/workloads/manifests/workloads.yaml.tmpl @@ -70,6 +70,15 @@ spec: - "--metrics-listen-addr=:9090" - "--data-dir=/tmp/glutton" - "--mode=http" + # Hold ResumeActor open until port 80 actually answers. Starting the + # process is not the same as it accepting connections, and without this + # gate a ping can arrive in the gap and be refused. Probing port 80 rather + # than the metrics port is deliberate: it is the port the router forwards + # to. + readyz: + httpGet: + path: /readyz + port: 80 workerSelector: matchLabels: workload: benchmark-ateom diff --git a/cmd/benchmarking/glutton/main.go b/cmd/benchmarking/glutton/main.go index 90079bb81..085e92c0e 100644 --- a/cmd/benchmarking/glutton/main.go +++ b/cmd/benchmarking/glutton/main.go @@ -136,6 +136,18 @@ func main() { // (re-exposable as additional routes if/when needed). mux := http.NewServeMux() mux.HandleFunc("/ping", httpPingHandler(svc)) + // Readiness gate for the actor lifecycle. ateom blocks RestoreWorkload + // on this returning 200 (internal/readyz), so ResumeActor does not + // report success until the socket the router forwards to is genuinely + // serving. Without it a ping can beat the listener and get + // ECONNREFUSED, which the router turns into a 503. + // + // It must live on this listener rather than the metrics port: the + // metrics server starts independently, so a 200 there would say + // nothing about whether /ping is reachable yet. + mux.HandleFunc("/readyz", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }) // otelhttp at the mux level + per-handler span follows // docs/dev/best-practices/tracing.md: extract incoming context, // then name the span after the operation in each handler.