Skip to content

fix(service): don't report READY before the endpoint accepts connections - #184

Draft
gonzaloserrano wants to merge 4 commits into
mainfrom
gsr/fix-service-create-readiness-probe
Draft

fix(service): don't report READY before the endpoint accepts connections#184
gonzaloserrano wants to merge 4 commits into
mainfrom
gsr/fix-service-create-readiness-probe

Conversation

@gonzaloserrano

@gonzaloserrano gonzaloserrano commented Aug 5, 2026

Copy link
Copy Markdown

service create waits on the control-plane status field. READY gates on in-cluster signals (leader pod serving, post-deploy runners), never on the customer-facing endpoint, so callers connecting immediately get ECONNREFUSED while the allocated port, DNS and LB catch up. internal/common/ready.go already documents READY as "accepting connections".

Prod repro in timescale/migration-smoke-test (run 30977861902): READY 20.1s after create, refused 91ms later. Twice in 40 runs. start and fork share the pattern.

WaitForConnectable polls the endpoint after the status wait, capped at 2 minutes. Any Postgres protocol error counts as serving: 28P01 proves the server is up, just refusing our credentials, so no password is needed. 57P03 is the exception and keeps waiting.

Best-effort: unverifiable endpoints warn rather than fail; a VPC-only service is healthy but unreachable from here.

A server-side gate is worth doing separately, but cannot replace this: readiness is a property of the path between a client and the service, not of the service alone.

For review: do start/fork belong here, and should the budget be a flag? Call sites covered only by compilation (create tests use --no-wait).

service create/start/fork waited only on the control-plane status field,
which flips to READY before Postgres binds its port. ready.go already
documents READY as "accepting connections", so callers that connect
immediately raced it and got ECONNREFUSED.

Add WaitForConnectable, which probes the endpoint until it answers, and
run it after the status wait. Any Postgres protocol error counts as
serving so the probe works without credentials; 57P03 keeps waiting.
Best-effort: an unverified endpoint warns rather than failing, since a
VPC-only or allowlisted service is legitimately unreachable from the CLI
host while being healthy.
The original comment said READY flips before Postgres binds its port.
Tracing savannah-deployer shows binding is already gated: leaderStatus
Reconciler requires podIsServing plus passing post-deploy runners. Both
are in-cluster checks, and the post-deploy runners connect to the pod
(directly or via port-forward), never to the customer endpoint. The
ungated gap is the external path: allocated port, DNS, load balancer.
main gained *config.Config on the connection helpers and moved command
wiring to App.GetAll (#183), which lands as a silent break here: the
merge is textually clean but the call sites stop compiling.

Thread Config through ConnectableWaitArgs, take the client as
api.ClientWithResponsesInterface to match WaitForServiceArgs, and read
client/projectID from App.GetAll at the three call sites.
@gonzaloserrano

Copy link
Copy Markdown
Author

@nathanjcochran PTAL, in case you think this is a real issue (I’m not familiar with the codebase or API semantics) I’ll mark it as ready for review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant