Skip to content

Improve port conflict handling for proxy and API ports - #2030

Open
waldekmastykarz wants to merge 4 commits into
dotnet:mainfrom
waldekmastykarz:port-conflict-handling
Open

waldekmastykarz wants to merge 4 commits into
dotnet:mainfrom
waldekmastykarz:port-conflict-handling

Conversation

@waldekmastykarz

Copy link
Copy Markdown
Collaborator

Closes #1273

Summary

Dev Proxy now shows a clear, actionable error when the proxy port or the API port is already in use. Previously it printed a Kestrel AddressInUseException stack trace.

  • Check before startup: in DevProxyCommand.InvokeAsync, after the config is resolved, both ports are checked (PortConflict helper).
    • If the owner is another Dev Proxy instance (matched via state files):
      Port 8000 is already in use by another Dev Proxy instance (PID: 1234).
      Stop the existing instance with 'devproxy stop --pid 1234' or use a different port:
        devproxy --port 0  (lets the OS assign a free port)
      
    • If the owner is another application:
      API port 8897 is already in use by another application.
      Use a different port:
        devproxy --api-port 0  (lets the OS assign a free port)
      You can also set 'apiPort' in your devproxyrc.json config file.
      
  • Race fallback: if a port is taken between the check and the bind:
    • KestrelProxyEngine records the failure and stops the host cleanly instead of crashing with a stack trace.
    • The API-port bind exception is caught and replaced with the same message.
  • Exit code fix: a taken proxy port previously exited with code 0. It now exits with 1.
  • Detached mode:
    • The existing conflict check before startup uses the shared messages and respects --output json.
    • When the background process exits early, its log (where its console output goes) is shown, so the reason for the failure is visible.

Notes

  • Instances are detected via state files instead of an HTTP probe. The API requires a bearer token, so an unauthenticated probe can't tell Dev Proxy apart from another server.
  • The suggested alternative port is 0 (assigned by the OS).
  • In the rare API-port race, ASP.NET Core's own "Hosting failed to start" error still prints before the friendly message. I didn't change the global logging filters to hide it.

Testing

  • New PortConflictTests (10 tests). DevProxy.Tests and DevProxy.Proxy.Kestrel.Tests pass.
  • Manually verified, with both text and JSON output:
    • Foreground and detached runs, with an unrelated process or another Dev Proxy instance holding the proxy port or the API port.
    • The race paths, with the check before startup disabled.

Check whether the proxy and API ports are available before starting and
show an actionable error instead of a Kestrel AddressInUseException stack
trace. Identify conflicts with other Dev Proxy instances via their state
files and suggest 'devproxy stop --pid' or '--port 0'/'--api-port 0'.

Also handle bind races without stack traces, exit with code 1 when the
proxy port is taken (previously 0), and surface the daemon's failure
reason when starting in detached mode.

Closes dotnet#1273

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 31, 2026 09:06
@waldekmastykarz
waldekmastykarz requested a review from a team as a code owner October 31, 2026 09:06

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

A proxy-bind failure can still publish a ready state, causing detached startup to report success while the child is shutting down.

5 open issues
What changed in this PR

Improves Dev Proxy’s startup diagnostics for proxy and API port conflicts, addressing #1273 with actionable guidance instead of raw exceptions.

Changes:

  • Adds shared port checks, instance lookup, and messages suggesting how to stop an instance or select another port.
  • Handles bind-time conflicts and returns a failure exit code for proxy-port conflicts.
  • Surfaces detached-process failure logs and adds focused helper tests.
File Description
DevProxy/​Proxy/​PortConflict.cs Adds conflict detection, owner lookup, and shared messages.
DevProxy/​Program.cs Reuses conflict messages and forwards detached startup logs.
DevProxy/​Commands/​DevProxyCommand.cs Checks ports before startup and handles bind failures.
DevProxy.Tests/​PortConflictTests.cs Tests port checks, owner matching, messages, and exception detection.
DevProxy.Proxy.Kestrel/​KestrelProxyEngine.cs Records proxy bind conflicts and requests host shutdown.

Review effort: Balanced

Comment thread DevProxy/Commands/DevProxyCommand.cs Resolved
Comment thread DevProxy/Program.cs Resolved
Comment thread DevProxy/Proxy/PortConflict.cs Resolved
Comment thread DevProxy/Proxy/PortConflict.cs Resolved
Comment thread DevProxy/Proxy/PortConflict.cs Resolved
- Publish instance state only after the proxy listener has started
- Keep detached JSON output valid by wrapping non-JSON log lines
- Match ownership across proxy/API ports and require overlapping bind address
- Use a non-pruning state lookup for port conflict diagnostics

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.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

Unresolved startup and port-conflict handling issues can bypass friendly bind-failure messages, misidentify listener ownership, and block valid launches or restarts.

2 open issues
2 previously missed issues

In code that hasn't changed since last review

Medium severity Match Kestrel's reuse-address option in Unix bind probes

DevProxy/​Proxy/​PortConflict.cs:44

On Unix, this probe uses default socket options while Kestrel enables SO_REUSEADDR. After traffic and shutdown, connections in TIME_WAIT can make the probe report a conflict even though Kestrel could rebind, preventing valid restarts or hot reloads. Enable ReuseAddress on the probe on non-Windows platforms to match Kestrel.

Medium severity Make wildcard overlap checks address-family-aware

DevProxy/​Proxy/​PortConflict.cs:147

An IPv4 wildcard does not overlap an IPv6-only listener: [::1]:8000 and 0.0.0.0:8000 can coexist. This expression nevertheless reports an owner, causing the detached launcher's ownership-only check to reject a valid startup. Make wildcard matching address-family-aware while preserving cross-family overlap for IPv6-any dual-mode binds.

5 resolved since last review

Review effort: Balanced

Comment thread DevProxy/Commands/DevProxyCommand.cs Resolved
Comment thread DevProxy/Proxy/PortConflict.cs Resolved
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.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.

🔵 Needs a closer look

Port-conflict checks can misidentify listeners and block valid launches or restarts, while out-of-range ports bypass normal startup error handling and break JSONL output.

No open issues 🎉

4 previously missed issues

In code that hasn't changed since last review

Medium severity Check conflicts against config-resolved endpoints

DevProxy/​Program.cs:105

This lookup uses CLI-only options and hard-coded defaults, but now also matches the other listener kind. With a non-system-proxy instance on proxy port 8897/API port 8898, a detached run whose config specifies port: 8001 and apiPort: 8899 is newly rejected as an API-port-8897 conflict, even though its actual endpoints are free. Defer this check to the child's existing config-resolved CheckAsync calls so it checks the endpoints that will actually be used.

Medium severity Skip out-of-range ports in the listener probe

DevProxy/​Proxy/​PortConflict.cs:31

Ports above 65535 reach the TcpListener constructor, which throws ArgumentOutOfRangeException outside the socket catches and InvokeAsync's startup try. Consequently, devproxy --output json --port 65536 now reaches Program.cs's plain-text unhandled-exception handler and breaks JSONL output. Skip out-of-range ports here, just as negative ports are skipped, so the normal startup error handling can report them.

Medium severity Enable SO_REUSEADDR for Unix port probes

DevProxy/​Proxy/​PortConflict.cs:44

On Unix, this probe does not enable SO_REUSEADDR as Kestrel does, so a port with a connection in TIME_WAIT can fail the probe even though Kestrel could bind it. This blocks otherwise valid restarts and hot reloads and incorrectly reports another application. Match Kestrel's Unix socket reuse setting before starting the probe.

Medium severity Make IPv4 wildcard matching address-family-aware

DevProxy/​Proxy/​PortConflict.cs:147

An IPv4 wildcard does not overlap IPv6 loopback, so this predicate can identify a Dev Proxy instance on ::1:8000 as the owner of another application's IPv4 listener and recommend a stop command that will not free the port. The detached precheck can also reject this combination when the IPv4 port is free. Make IPv4 wildcard matching address-family-aware while preserving dual-stack IPv6 wildcard overlap.

2 resolved since last review

Review effort: Balanced

- Drop the detached launcher's CLI-only conflict pre-check; the daemon's
  config-aware check reports conflicts instead
- Skip the listener probe for ports above 65535 and validate --port and
  --api-port to the 0-65535 range
- Treat 0.0.0.0 as overlapping IPv4 addresses only; :: stays dual-stack

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

waldekmastykarz commented Oct 31, 2026 •

Copy link
Copy Markdown
Collaborator Author

Addressing the 4 "previously missed issues" from Copilot's review of 7d5fcb0 (fixed in 446b9fc):

  • Check conflicts against config-resolved endpoints (Program.cs): Fixed. I removed the detached launcher's pre-check, which only looked at CLI ports. The daemon's config-aware CheckAsync reports conflicts now; the launcher prints that output, and it stays valid JSONL with --output json. Verified: a detached launch that conflicts with a running instance reports that instance's PID and the stop hint.
  • Skip out-of-range ports in the listener probe: Fixed. The probe skips ports above 65535, the same way it already skipped ports ≤ 0, and --port/--api-port now only accept 0–65535 (exit code 2). Tests added. In --output json mode the validation error is still plain text, but every option validator (for example --timeout) already behaves that way, so that's outside this PR's scope.
  • Make IPv4 wildcard matching address-family-aware: Fixed. 0.0.0.0 now overlaps IPv4 addresses only, while :: is still treated as dual-stack. Tests added for 0.0.0.0 vs [::1] and the reverse.
  • Enable SO_REUSEADDR for Unix port probes: Not changing this. Hot reload waits for the host to shut down before the next startup runs the probe. We haven't reproduced a false conflict from TIME_WAIT. If someone reports a false positive, we'll revisit.

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.

🔵 Needs a closer look

Human assessment is needed because platform-specific port probing and startup/shutdown sequencing can falsely report conflicts, bypass intended validation, or stall shutdown during certificate-trust prompts.

No open issues 🎉

Review effort: Balanced

@waldekmastykarz

Copy link
Copy Markdown
Collaborator Author

Re: Copilot's review of 446b9fc ("Needs a closer look"). It didn't flag any specific lines; here's my assessment of the three concerns it mentions:

  • Platform-specific port probing could report false conflicts: this is the SO_REUSEADDR item covered in my previous comment. Not changing it unless someone reproduces a false positive.
  • Shutdown could stall during the certificate-trust prompt: not a regression. EnsureTrusted runs synchronously in KestrelProxyEngine.ExecuteAsync before its first await, and hosted services start one at a time by default. So the prompt blocks _app.StartAsync exactly as it does on main, and it has already been answered by the time InvokeAsync waits on Started.
  • Validation can be bypassed: --port/--api-port are now limited to 0–65535. Ports set in the config file aren't covered by that CLI validator. Out-of-range config values skip the conflict probe, and Kestrel reports them the same way it does on main, so this PR doesn't change that path.

This branch has not been deployed

No deployments
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.

Improve Dev Proxy API port conflict handling

2 participants