Skip to content

Pin ports in tests#1162

Draft
acroca wants to merge 2 commits into
dapr:mainfrom
acroca:pin-test-ports
Draft

Pin ports in tests#1162
acroca wants to merge 2 commits into
dapr:mainfrom
acroca:pin-test-ports

Conversation

@acroca

@acroca acroca commented Jul 16, 2026

Copy link
Copy Markdown
Member

Description

Fixes the "bind: address already in use" flakes that kill validate CI jobs at sidecar startup. What began as a single root cause turned out to be three distinct mechanisms.

Root cause 1: the Dapr CLI can assign the same random port to two listeners

When dapr run is launched without explicit ports, the CLI picks random free ports for every daprd listener. Its picker can hand the same port to two listeners of the same daprd — every sampled CI failure shows the metrics server successfully binding the exact port that the internal (or API) gRPC server then fatally fails to bind:

level=info  msg="metrics server started on 0.0.0.0:38289"
level=error msg="Failed to listen for gRPC server on TCP address :38289 ... bind: address already in use" scope=dapr.runtime.grpc.internal
level=fatal msg="Fatal error from runtime: failed to start internal gRPC server: could not listen on any endpoint"

Observed in runs 28439709999, 28198520326, 27741369607 (integration, internal gRPC) and 29419165370 (examples, API gRPC).

Root cause 2: teardown only waited for the dapr CLI process

dapr run spawns daprd and the app as siblings of the CLI, which can exit while they are still shutting down gracefully — leaving ports and mDNS registrations alive into the next test module. Pinning made this old race consequential: every module now reuses the exact same ports, so any teardown lag becomes a deterministic startup failure.

Root cause 3: pinned ports inside the OS ephemeral range get stolen

Dapr's conventional 500xx ports sit inside Linux's ephemeral source-port range (32768–60999). Any process's outbound localhost connection — the test runner's gRPC dials, health-check polls, daprd→redis/scheduler connections — can be assigned one of them as its source port, and an established socket blocks daprd from binding a listener there. This hit this PR's own CI twice (validate 3.14, validate 3.10): daprd retries the internal bind for 10×1s and dies fatally; in the second run the CLI's preflight reported Port 50002 is not available more than a minute after every managed process was gone, ruling out any lingering daprd. dapr/dapr#10149 doesn't cover this — it only stops daprd's own outbound dials from stealing its ports, not other processes'.

@codecov

codecov Bot commented Jul 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 82.85%. Comparing base (3457d0a) to head (4f97df7).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1162      +/-   ##
==========================================
+ Coverage   82.78%   82.85%   +0.06%     
==========================================
  Files         123      123              
  Lines       10127    10127              
==========================================
+ Hits         8384     8391       +7     
+ Misses       1743     1736       -7     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR reduces CI flakes caused by Dapr sidecar startup failures where multiple daprd listeners can end up attempting to bind the same randomly-chosen port, leading to bind: address already in use and a fatal sidecar exit. It does this primarily by pinning additional daprd listener ports in the integration test harness and by making the example-test retry detection cover both internal and API gRPC startup failures.

Changes:

  • Pin daprd internal gRPC and metrics ports (in addition to existing gRPC/HTTP pins) for integration test sidecars.
  • Broaden the example-test “port bind failure” detection marker to match both internal and API gRPC fatal-start messages.
  • Update DaprRunner unit tests to validate retry behavior for both internal and API gRPC server startup failures.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
tests/integration/conftest.py Adds pinned internal_grpc_port and metrics_port to the integration sidecar launcher and passes the corresponding dapr run flags.
tests/integration/AGENTS.md Documents the expanded set of pinned ports and the motivation (avoiding daprd listener port-collision flakes).
tests/examples/test_dapr_runner.py Updates unit tests to cover both internal and API gRPC fatal-start variants for retry behavior.
tests/examples/conftest.py Broadens the port-bind failure marker to match the fatal line regardless of whether it references internal or API gRPC.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@acroca acroca marked this pull request as ready for review July 16, 2026 07:03
@acroca acroca requested review from a team as code owners July 16, 2026 07:03
@acroca acroca force-pushed the pin-test-ports branch 3 times, most recently from 19d371d to 21854e9 Compare July 16, 2026 10:23
Signed-off-by: Albert Callarisa <albert@diagrid.io>
@acroca acroca marked this pull request as draft July 16, 2026 10:33
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.

2 participants