Skip to content

fix(floci): select Service Bus host ports below the ephemeral range - #2241

Merged
afscrome merged 4 commits into
CommunityToolkit:mainfrom
thomhurst:floci-servicebus-port-range
Oct 11, 2026
Merged

afscrome merged 4 commits into
CommunityToolkit:mainfrom
thomhurst:floci-servicebus-port-range

Conversation

@thomhurst

@thomhurst thomhurst commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Closes #2240

FlociServiceBusEndpointAllocator used TcpListener(IPAddress.Any, 0) to get an OS-assigned ephemeral port, then released it before Docker bound it. DCP allocates project proxy ports from the same ephemeral range and can receive that port in the meantime:

  • Windows: the DCP proxy's 127.0.0.1 listener shadows Docker's wildcard bind. AMQP clients connecting to localhost:<port> reach a project proxy instead of the broker.
  • Linux: Docker fails with port is already allocated, and the sidecar never starts.

The issue includes evidence from DCP Service objects showing floci-servicebus-amqp and chat-https on the same port.

Change

When no port is configured, SelectPort now picks a random port in 20000-32767. That range is below every OS ephemeral range (Linux starts at 32768; Windows and macOS start at 49152), so DCP never allocates these ports. Ports configured on any app-model endpoint, including the sibling Service Bus endpoint, are excluded without probing. The scan starts at a random offset and covers the whole range, so a free port is always found when one exists; the allocator throws only when none is. A candidate must be free on IPv4 loopback, IPv6 loopback (when the OS supports IPv6) and IPAddress.Any. As before, the Any probe listeners are held until every port is selected, so AMQP and AMQPS stay distinct. Because the start offset is random, concurrent isolated AppHosts still get different ports. Explicitly configured ports are unchanged.

Tests

  • WithServiceBusAllocatesEndpointsBeforeConfiguringTheDataPlane now asserts that selected ports fall within 20000-32767. Explicitly configured ports are still checked for equality.
  • Full CommunityToolkit.Aspire.Hosting.Floci.Tests run on Windows: 84/84 passed.

PR Checklist

  • Created a feature/dev branch in your fork (vs. submitting directly from a commit on main)
  • Based off latest main branch of toolkit
  • PR doesn't include merge commits (always rebase on top of our main, if needed)
  • Tests for the changes have been added (for bug fixes / features) (if applicable)
  • Contains NO breaking changes
  • Every new API (including internal ones) has full XML docs
  • Code follows all style conventions

Other information

The same approach was checked in a downstream AppHost by passing ports in this range explicitly through WithServiceBus(name, amqpPort, amqpTlsPort); aspire start --isolated then reached healthy.

The k3s allocator uses the same TcpListener(IPAddress.Any, 0) pattern and may have the same race. It is left out of this PR, which addresses one issue.

The Service Bus endpoint allocator took OS-assigned ephemeral ports and
released them before Docker bound them. DCP allocates project proxy ports
from the same range and could receive the same port. On Windows the
proxy's loopback listener then shadows Docker's wildcard bind, so AMQP
clients reach the proxy instead of the broker; on Linux Docker's bind
fails and the broker never starts.

Select random free ports in 20000-32767 instead, below every OS
ephemeral range. Explicit ports are unchanged.

Fixes CommunityToolkit#2240

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 10, 2026 10:34
@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/CommunityToolkit/Aspire/main/eng/scripts/dogfood-pr.sh | bash -s -- 2241

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/CommunityToolkit/Aspire/main/eng/scripts/dogfood-pr.ps1) } 2241"

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.

🟡 Changes recommended

Random selection can collide with explicitly configured app-model ports.

1 open finding
What changed in this PR

Updates Floci Service Bus port allocation to avoid DCP ephemeral-port races.

Changes:

  • Selects random ports from 20000–32767 and probes availability.
  • Tests verify automatically selected ports use that range.
File Description
FlociServiceBusEndpointAllocator.cs Implements bounded host-port selection.
AzureServiceBusResourceTests.cs Verifies selected and configured ports.

🧠 Review effort: Balanced


💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/CommunityToolkit.Aspire.Hosting.Floci/FlociServiceBusEndpointAllocator.cs Outdated
@thomhurst
thomhurst enabled auto-merge October 10, 2026 10:42
@thomhurst
thomhurst disabled auto-merge October 10, 2026 10:42
A port configured explicitly on any endpoint (including one of the two
Service Bus endpoints) is bound later, so the availability probe cannot
see it. Exclude every configured app-model port from selection, and scan
the whole range from a random offset instead of a fixed number of random
attempts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

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.

🟡 Changes recommended

Resolve the unbounded scan/documentation mismatch and add IPv6 loopback probing with regression coverage.

1 open finding
1 resolved since last review

🧠 Review effort: Lite

Docker also binds [::]:<port>, and a process listening on ::1 shadows it
for IPv6 localhost connections, as on IPv4 loopback. Probe ::1 too when
the OS supports IPv6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
// Ports configured anywhere in the app model are bound later, so probing cannot see them.
var reservedPorts = @event.Model.Resources
.SelectMany(resource => resource.Annotations.OfType<EndpointAnnotation>())
.Select(endpoint => endpoint.Port)

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.

This probably needs to filter to endpoint.IsAllocated. endpoint allocation can occur asyncronously, so not everythign is guaranteed to have a port allocated at this point, in which case this method may throw or stall.

Either

  1. Guard by only reading port if endpoint.IsAllocated
  2. await endpoint.Property(EndpointProperty.Port).GetValueAsync() to guarantee you get hte port

Probably the former as otherwise you may hang forever.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Neither read blocks here. EndpointAnnotation.Port is the configured value (a plain property), and AllocatedEndpoint returns null until the snapshot is set, without waiting. So an endpoint that is still being allocated never throws or stalls this handler.

I didn't filter to IsAllocated, though. At BeforeStartEvent almost nothing is allocated yet, so that filter would drop the configured ports. Those are the ones this set must hold, because their sockets don't exist yet and probing can't see them.

ef2af1d adds already-allocated ports to the set as well, so it is now configured ports plus allocated ports. I moved the logic into GetReservedPorts and added GetReservedPortsIncludesConfiguredAndAllocatedPortsOnly: a configured port and an allocated port are both reserved, and a pending dynamic endpoint is skipped.

… Bus ports

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@thomhurst
thomhurst requested a review from afscrome October 10, 2026 21:32
@afscrome
afscrome merged commit 1192b73 into CommunityToolkit:main Oct 11, 2026
16 checks passed
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.

[Floci] Service Bus AMQP host ports race with DCP proxy allocation; broker unreachable or fails to bind

3 participants