Skip to content

Avoid ephemeral executable target port collisions - #19822

Merged
David Negstad (danegsta) merged 1 commit into
mainfrom
danegsta-fix-ephemeral-port-collision
Aug 31, 2026
Merged

David Negstad (danegsta) merged 1 commit into
mainfrom
danegsta-fix-ephemeral-port-collision

Conversation

@danegsta

Copy link
Copy Markdown
Member

Description

DCP dynamically allocated executable target ports by briefly reserving an ephemeral port, releasing it, and later passing it to the child process. Outbound connections could claim that port during the gap, causing workloads such as the Aspire dashboard to fail binding during isolated startup.

This change allocates unspecified target ports for proxied executables from Aspire's existing non-ephemeral port range while leaving DCP-owned dynamic proxy listeners on ephemeral ports. Fixed target ports are excluded from allocation, replicas receive distinct ports, and isolated runs ignore persisted proxyless ports and never write new port assignments to user secrets.

Fixes #19691

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No

Allocate proxied executable target ports from Aspire's non-ephemeral range while preserving dynamic DCP proxy listeners. Ensure isolated runs ignore and never persist proxyless port assignments.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 2d6d0570-377a-4d4c-ba04-4cdbf029090c
Copilot AI balanced review requested due to automatic review settings August 31, 2026 20:10
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 19822

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 19822"

@github-actions github-actions Bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Aug 31, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Tests selector

49 / 99 PR test projects · 3 PR jobs · 2 advisory-only targets, from 5 changed files.

Selected PR test projects (49 / 99)

Aspire.Hosting.Analyzers.Tests, Aspire.Hosting.Azure.Kubernetes.Tests, Aspire.Hosting.Azure.Kusto.Tests, Aspire.Hosting.Azure.Tests, Aspire.Hosting.Blazor.Tests, Aspire.Hosting.Browsers.Tests, Aspire.Hosting.CodeGeneration.Go.Tests, Aspire.Hosting.CodeGeneration.Java.Tests, Aspire.Hosting.CodeGeneration.Python.Tests, Aspire.Hosting.CodeGeneration.Rust.Tests, Aspire.Hosting.CodeGeneration.TypeScript.Tests, Aspire.Hosting.Containers.Tests, Aspire.Hosting.DevTunnels.Tests, Aspire.Hosting.Docker.Tests, Aspire.Hosting.Dotnet.Tests, Aspire.Hosting.DotnetTool.Tests, Aspire.Hosting.EntityFrameworkCore.Tests, Aspire.Hosting.Foundry.Tests, Aspire.Hosting.Garnet.Tests, Aspire.Hosting.GitHub.Models.Tests, Aspire.Hosting.Go.Tests, Aspire.Hosting.Java.Tests, Aspire.Hosting.JavaScript.Tests, Aspire.Hosting.Kafka.Tests, Aspire.Hosting.Keycloak.Tests, Aspire.Hosting.Kubernetes.Tests, Aspire.Hosting.Maui.Tests, Aspire.Hosting.Milvus.Tests, Aspire.Hosting.MongoDB.Tests, Aspire.Hosting.MySql.Tests, Aspire.Hosting.Nats.Tests, Aspire.Hosting.OpenAI.Tests, Aspire.Hosting.Oracle.Tests, Aspire.Hosting.Orleans.Tests, Aspire.Hosting.PostgreSQL.Tests, Aspire.Hosting.Python.Tests, Aspire.Hosting.Qdrant.Tests, Aspire.Hosting.RabbitMQ.Tests, Aspire.Hosting.Radius.Tests, Aspire.Hosting.Redis.Tests, Aspire.Hosting.RemoteHost.Tests, Aspire.Hosting.Rust.Tests, Aspire.Hosting.Seq.Tests, Aspire.Hosting.SqlServer.Tests, Aspire.Hosting.Testing.Tests, Aspire.Hosting.Tests, Aspire.Hosting.Valkey.Tests, Aspire.Hosting.Yarp.Tests, Aspire.Playground.Tests

Selected PR jobs (3)

extension-e2e, polyglot, typescript-api-compat

Advisory workflow impact (2)

  • Aspire.EndToEnd.Tests (outerloop-only)
  • deployment-e2e (schedule/dispatch-only)

How these were chosen — grouped by what changed

⚠️ 45 of the 50 selected test projects come from a single change — src/Aspire.Hosting/Dcp/DcpExecutor.cs.

🔧 src/Aspire.Hosting/Dcp/DcpExecutor.cs (changed source)
→ 45 via the project graph

show 45

Aspire.Hosting.Analyzers.Tests (2 hops), Aspire.Hosting.Azure.Kubernetes.Tests (2 hops), Aspire.Hosting.Azure.Kusto.Tests (2 hops), Aspire.Hosting.Azure.Tests, Aspire.Hosting.Browsers.Tests (2 hops), Aspire.Hosting.CodeGeneration.Go.Tests, Aspire.Hosting.CodeGeneration.Java.Tests, Aspire.Hosting.CodeGeneration.Python.Tests, Aspire.Hosting.CodeGeneration.Rust.Tests, Aspire.Hosting.CodeGeneration.TypeScript.Tests, Aspire.Hosting.Containers.Tests (2 hops), Aspire.Hosting.DevTunnels.Tests (2 hops), Aspire.Hosting.Docker.Tests (2 hops), Aspire.Hosting.DotnetTool.Tests (2 hops), Aspire.Hosting.EntityFrameworkCore.Tests (2 hops), Aspire.Hosting.Foundry.Tests (2 hops), Aspire.Hosting.Garnet.Tests (2 hops), Aspire.Hosting.GitHub.Models.Tests (2 hops), Aspire.Hosting.Go.Tests (2 hops), Aspire.Hosting.Java.Tests (2 hops), Aspire.Hosting.JavaScript.Tests (2 hops), Aspire.Hosting.Kafka.Tests (2 hops), Aspire.Hosting.Keycloak.Tests (2 hops), Aspire.Hosting.Kubernetes.Tests (2 hops), Aspire.Hosting.Maui.Tests, Aspire.Hosting.Milvus.Tests (2 hops), Aspire.Hosting.MongoDB.Tests (2 hops), Aspire.Hosting.MySql.Tests (2 hops), Aspire.Hosting.Nats.Tests (2 hops), Aspire.Hosting.OpenAI.Tests (2 hops), Aspire.Hosting.Oracle.Tests (2 hops), Aspire.Hosting.Orleans.Tests (2 hops), Aspire.Hosting.PostgreSQL.Tests (2 hops), Aspire.Hosting.Python.Tests (2 hops), Aspire.Hosting.Qdrant.Tests (2 hops), Aspire.Hosting.RabbitMQ.Tests (2 hops), Aspire.Hosting.Redis.Tests (2 hops), Aspire.Hosting.RemoteHost.Tests, Aspire.Hosting.Rust.Tests (2 hops), Aspire.Hosting.Seq.Tests (2 hops), Aspire.Hosting.SqlServer.Tests (2 hops), Aspire.Hosting.Testing.Tests (2 hops), Aspire.Hosting.Valkey.Tests (2 hops), Aspire.Hosting.Yarp.Tests (2 hops), Aspire.Playground.Tests

🧪 tests/Aspire.Hosting.Tests/Dcp/DcpExecutorTests.cs (changed test)
→ 1 directly: Aspire.Hosting.Tests
→ 3 via the project graph: Aspire.Hosting.Blazor.Tests, Aspire.Hosting.Dotnet.Tests, Aspire.Hosting.Radius.Tests

📦 affected project Aspire.Hosting
→ 1 test: Aspire.EndToEnd.Tests

🧪 tests/Aspire.Hosting.Tests/Dcp/ProxylessEndpointPortAllocatorTests.cs (changed test)
→ 1 directly: Aspire.Hosting.Tests

Job reasons

Job Triggered by
deployment-e2e affected project Aspire.Hosting.Azure
extension-e2e • src/Aspire.Hosting/Dcp/DcpExecutor.cs, src/Aspire.Hosting/Dcp/DcpOptions.cs, src/Aspire.Hosting/Dcp/ProxylessEndpointPortAllocator.cs
• affected project Aspire.Hosting
polyglot affected project Aspire.Hosting.Python
typescript-api-compat affected project Aspire.Hosting

Selection computed for commit 78fe9a2.

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

Prevents executable target ports from colliding with ephemeral ports during DCP startup.

Changes:

  • Allocates proxied executable target ports from Aspire’s non-ephemeral range.
  • Excludes fixed ports and assigns unique ports to replicas.
  • Avoids persisted proxyless ports during isolated runs.

Reviewed changes

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

Show a summary per file
File Description
src/Aspire.Hosting/Dcp/DcpExecutor.cs Adds target-port allocation and isolated persistence behavior.
src/Aspire.Hosting/Dcp/DcpOptions.cs Documents expanded port-range usage.
src/Aspire.Hosting/Dcp/ProxylessEndpointPortAllocator.cs Supports allocation without an endpoint identity.
tests/Aspire.Hosting.Tests/Dcp/DcpExecutorTests.cs Covers allocation, exclusions, replicas, and isolation.
tests/Aspire.Hosting.Tests/Dcp/ProxylessEndpointPortAllocatorTests.cs Verifies unique protocol-based allocations.

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

@danegsta David Negstad (danegsta) added area-app-model Issues pertaining to the APIs in Aspire.Hosting, e.g. DistributedApplication area-orchestrator and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels Aug 31, 2026
@danegsta

Copy link
Copy Markdown
Member Author

PR Testing Report

PR Information

Artifact Version Verification

  • Expected Commit: 78fe9a2b2768acaeaef2b05be8fa1a6ed04bc3c1
  • Installed Version: 13.6.0-pr.19822.g78fe9a2b
  • Status: Verified

Changes Analyzed

Files Changed

  • src/Aspire.Hosting/Dcp/DcpExecutor.cs
  • src/Aspire.Hosting/Dcp/DcpOptions.cs
  • src/Aspire.Hosting/Dcp/ProxylessEndpointPortAllocator.cs
  • tests/Aspire.Hosting.Tests/Dcp/DcpExecutorTests.cs
  • tests/Aspire.Hosting.Tests/Dcp/ProxylessEndpointPortAllocatorTests.cs

Change Categories

  • CLI changes
  • Hosting changes
  • Dashboard UI changes
  • Template changes
  • Client/component changes
  • VS Code extension changes
  • Test changes
  • CI infrastructure changes

Test Scenarios Executed

Scenario 1: PR artifact identity

Objective: Confirm that testing uses the CLI artifact built from the current PR head.

Coverage Type: Artifact verification

Status: Passed

Evidence:

  • <temp>/install.log
  • <temp>/version.txt

Observations:

  • The installed CLI reported 13.6.0-pr.19822.g78fe9a2b, matching the PR head.

Scenario 2: Single isolated dashboard

Objective: Verify an isolated AppHost keeps its dashboard healthy while assigning executable target ports outside the ephemeral range.

Coverage Type: Happy path

Status: Passed

Steps:

  1. Created a fresh C# aspire-empty project from the PR hive.
  2. Started it with aspire start --isolated.
  3. Waited for aspire-dashboard to reach the running state.
  4. Described the hidden dashboard resource and requested its login URL.

Evidence:

  • <temp>/start-one.jsonl
  • <temp>/wait-one.log
  • <temp>/dashboard-one.jsonl

Observations:

  • Dashboard state was Running and health was Healthy.
  • Dashboard target ports were 20266, 20267, and 20268, all within Aspire's 10000-32767 non-ephemeral range.
  • DCP-owned proxy ports remained dynamically allocated at 49377, 49378, and 49379.
  • The dashboard login URL returned HTTP 200.

Scenario 3: Concurrent isolated AppHosts

Objective: Verify concurrent isolated AppHosts receive distinct non-ephemeral dashboard target ports without collisions.

Coverage Type: Concurrency/boundary

Status: Passed

Steps:

  1. Kept the first isolated AppHost running.
  2. Created and started a second fresh isolated AppHost.
  3. Waited for both dashboards and described both resources.
  4. Validated target port ranges, uniqueness, health, and login URLs.

Evidence:

  • <temp>/start-two.jsonl
  • <temp>/wait-two.log
  • <temp>/dashboard-one-concurrent.json
  • <temp>/dashboard-two.json
  • <temp>/concurrent-validation.json

Observations:

  • Both dashboards were Running and Healthy.
  • The first dashboard used target ports 20266-20268.
  • The second dashboard used target ports 14343-14345.
  • All six target ports were unique and within 10000-32767.
  • Both dashboard login URLs returned HTTP 200.

Scenario 4: Isolated persistent port handling

Objective: Verify isolated mode ignores a persisted proxyless port and does not write the newly selected port to either user-secret store.

Coverage Type: Unhappy path/boundary

Status: Passed

Steps:

  1. Created a fresh C# aspire-empty project from the PR hive.
  2. Added a persistent proxyless executable with a dynamic HTTP endpoint.
  3. Set Resources:persistent:http:port to 45000 in its user secrets.
  4. Started the AppHost with --isolated.
  5. Inspected the running resource, original secrets, and isolated secrets.
  6. Stopped the AppHost and verified isolated-secret cleanup.

Evidence:

  • <temp>/file-secrets-before.txt
  • <temp>/start-persistence-only.jsonl
  • <temp>/wait-persistence-only.log
  • <temp>/persistent-only-resource.jsonl
  • <temp>/persistence-validation.json
  • <temp>/file-secrets-after.txt
  • <temp>/stop-persistence.log

Observations:

  • The persisted port 45000 was ignored.
  • The process received port 22893, within 10000-32767.
  • Port 22893 was absent from both the original and isolated secret stores.
  • The persisted value remained 45000 in both stores throughout the run.
  • The executable endpoint returned HTTP 200.
  • The isolated secret store was removed after shutdown.

Expected Unhappy-Path Outcome: Isolated mode must select a fresh port without overwriting or reusing the persisted value.


Scenario 5: Hosting regression tests

Objective: Exercise DCP executor behavior and the non-ephemeral allocator directly.

Coverage Type: Unit/integration regression

Status: Passed

Command:

dotnet test --project tests/Aspire.Hosting.Tests/Aspire.Hosting.Tests.csproj --no-launch-profile -- \
  --filter-class "*.DcpExecutorTests" \
  --filter-class "*.ProxylessEndpointPortAllocatorTests" \
  --filter-not-trait "quarantined=true" \
  --filter-not-trait "outerloop=true"

Evidence:

  • <temp>/hosting-port-tests.log

Observations:

  • 282 tests ran: 281 passed and one Linux-only test was skipped on macOS.

Summary

Scenario Status Notes
PR artifact identity Passed Installed version matched the PR head
Single isolated dashboard Passed Healthy with non-ephemeral target ports
Concurrent isolated AppHosts Passed Six distinct non-ephemeral target ports
Isolated persistent port handling Passed Persisted port ignored; selected port not persisted
Hosting regression tests Passed 281 passed, one expected platform skip

Overall Result

PR VERIFIED

@danegsta
David Negstad (danegsta) merged commit f6a2d49 into main Aug 31, 2026
202 checks passed
@danegsta
David Negstad (danegsta) deleted the danegsta-fix-ephemeral-port-collision branch August 31, 2026 21:30
@github-actions github-actions Bot added this to the 13.6 milestone Aug 31, 2026
@aspire-repo-bot

Copy link
Copy Markdown
Contributor

✅ No documentation update needed.

Step 5 branch taken: docs_optional -> internal_refactor

Triggered signals: none (signal_count: 0, recommendation: docs_optional).

Why internal_refactor fits: All 3 non-test files changed (src/Aspire.Hosting/Dcp/DcpExecutor.cs, src/Aspire.Hosting/Dcp/DcpOptions.cs, src/Aspire.Hosting/Dcp/ProxylessEndpointPortAllocator.cs) are internal DCP orchestration implementation details. Per the PR checklist, no public API was added. The change fixes an internal race condition in how DCP allocates ephemeral/proxyless target ports for executable resources before handing them to the child process/DCP proxy - it does not introduce or change any documented public type, method, CLI option, configuration key, environment variable, or user-facing string. The remaining 2 changed files are test-only (tests/Aspire.Hosting.Tests/Dcp/DcpExecutorTests.cs, tests/Aspire.Hosting.Tests/Dcp/ProxylessEndpointPortAllocatorTests.cs).

No user-visible behavior, default, error message, environment variable, or config key that the docs site describes is changed by this PR - it is a correctness fix inside DCP's internal port-allocation bookkeeping. No docs PR is warranted.

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

Labels

area-app-model Issues pertaining to the APIs in Aspire.Hosting, e.g. DistributedApplication area-orchestrator

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dashboard project can collide with its randomized OTLP port under aspire start --isolated

3 participants