Skip to content

Restore system proxy after a crashed instance#1756

Open
waldekmastykarz wants to merge 1 commit into
dotnet:mainfrom
waldekmastykarz:waldekmastykarz-restore-system-proxy-after-crash
Open

Restore system proxy after a crashed instance#1756
waldekmastykarz wants to merge 1 commit into
dotnet:mainfrom
waldekmastykarz:waldekmastykarz-restore-system-proxy-after-crash

Conversation

@waldekmastykarz

Copy link
Copy Markdown
Collaborator

Summary

When a detached Dev Proxy instance running with --as-system-proxy true is terminated uncleanly (crash, kill -9, OOM, power loss), the OS proxy is left pointing at the now-dead port and devproxy stop --force could not recover it — it printed Dev Proxy is not running. and exited 1, leaving the machine without working network access.

Root cause: StateManager prunes any state file whose PID is no longer alive before the stop command reads it, so a crashed instance's record is already gone by the time stop --force runs ⇒ no instance is found ⇒ SystemProxyManager.Disable() is never invoked. --force therefore only worked for a hung-but-alive instance, not the dead one it's meant to recover.

The fix decouples "restore the OS proxy" from "find a live instance."

Fixes #1731.

Changes

  • SystemProxyManager.cs (new) — cross-platform, idempotent Disable() (Windows WinINET via ProxyServer.DisableAllSystemProxies(), macOS toggle-proxy.sh off, Linux no-op) plus ReconcileOrphanedSystemProxiesAsync(). Reconciliation is guarded: the global OS proxy is only disabled when no live instance still owns it — otherwise only the stale record is removed.
  • StateManager.cs — added GetOrphanedSystemProxyStatesAsync() which reads records for dead PIDs with asSystemProxy: true without pruning them first, and extracted a non-pruning ReadStateFromFileAsync helper.
  • StopCommand.csdevproxy stop and devproxy stop --pid now reconcile crashed system-proxy orphans (restore the OS proxy + clean up the record) in addition to stopping live instances. Gated on Dev Proxy's own asSystemProxy record, so a proxy Dev Proxy didn't set is never touched.
  • Program.cs / ProxyEngine.cs — startup self-heal so a stale registration is recovered on the next run. The reconciliation runs at the very top of the detached launcher (before its state-loading port-conflict check, which would otherwise prune the orphan without restoring the proxy) and in ProxyEngine for foreground runs.

Safety

  • Disabling the system proxy is idempotent and cross-platform, so restoring it is low-risk.
  • Reconciliation only ever acts on a stale state record that Dev Proxy itself wrote with asSystemProxy: true — it never disables a corporate/third-party proxy.
  • The global OS proxy is only disabled when no live instance currently owns it.

Testing (macOS)

Verified with the built binary (system proxy was off, so Disable() was a safe no-op):

  • Crashed system-proxy orphan → stop restores it, deletes the record, exit 0
  • Nothing running → Dev Proxy is not running., exit 1
  • Non-system-proxy dead orphan → not treated as a restore
  • stop --pid on a crashed orphan → restored
  • Live instance + orphan present → graceful stop and orphan reconcile both succeed
  • Detached startup self-heal → Recovered system proxy left by a crashed Dev Proxy instance logged, orphan cleaned up

Full solution builds clean (0 warnings, 0 errors).

Notes

  • The Windows WinINET disable path compiles but was not exercised at runtime (tested on macOS) — worth a quick manual check on Windows before release.
  • The detached-instance model keys liveness on PID alone, which is vulnerable to PID reuse. That pre-existing caveat is intentionally out of scope here and tracked in State/liveness keyed on PID alone is vulnerable to PID reuse #1755.

When a detached instance running as system proxy is terminated uncleanly,
the OS proxy was left pointing at a dead port and 'devproxy stop --force'
could not recover it: StateManager prunes state files for dead PIDs before
the stop command reads them, so no instance was found and the system proxy
was never disabled.

Reconcile orphaned system-proxy registrations independently of finding a
live instance:
- Add StateManager.GetOrphanedSystemProxyStatesAsync (reads records for dead
  PIDs with asSystemProxy=true without pruning them first).
- Add cross-platform, idempotent SystemProxyManager.Disable (Windows WinINET
  via ProxyServer.DisableAllSystemProxies, macOS toggle-proxy.sh off) and
  ReconcileOrphanedSystemProxiesAsync, guarded so the OS proxy is only
  disabled when no live instance still owns it.
- devproxy stop / stop --pid now restore the system proxy for crashed
  instances; self-heal also runs at startup (detached launcher and
  ProxyEngine) so a stale registration doesn't linger across runs.

Refs dotnet#1731. PID-reuse hardening tracked in dotnet#1755.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0b22cd4a-15a3-4a8c-8248-fe776339aa45
Copilot AI review requested due to automatic review settings July 11, 2026 10:33
@waldekmastykarz waldekmastykarz requested a review from a team as a code owner July 11, 2026 10:33

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 fixes a failure mode where a crashed detached Dev Proxy instance that enabled the OS system proxy could leave the machine’s proxy settings pointing at a dead port, and devproxy stop --force could not recover because stale state was pruned before stop logic could restore the proxy.

Changes:

  • Add orphan detection in StateManager to surface stale asSystemProxy records without pruning them first.
  • Introduce SystemProxyManager to disable the OS proxy (best-effort, cross-platform) and reconcile orphaned system-proxy registrations.
  • Update stop and startup paths to reconcile and clean up orphaned system-proxy state before liveness-pruning state loads run.

Reviewed changes

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

Show a summary per file
File Description
DevProxy/State/StateManager.cs Adds non-pruning state read helper and an API to enumerate orphaned system-proxy state records.
DevProxy/Proxy/SystemProxyManager.cs New manager to disable OS proxy and reconcile orphaned system-proxy registrations safely.
DevProxy/Proxy/ProxyEngine.cs Adds foreground startup self-heal by reconciling orphaned system-proxy registrations.
DevProxy/Program.cs Adds detached launcher self-heal before state-loading/pruning operations.
DevProxy/Commands/StopCommand.cs Reconciles orphaned system-proxy registrations as part of stop flows and gates force disable by AsSystemProxy.

Comment thread DevProxy/Program.cs
Comment on lines +58 to +70
var reconciliation = await SystemProxyManager.ReconcileOrphanedSystemProxiesAsync();
foreach (var orphan in reconciliation.Orphans)
{
var message = $"Recovered system proxy left by a crashed Dev Proxy instance (PID: {orphan.Pid}).";
if (isJsonOutput)
{
await Console.Out.WriteLineAsync(FormatJsonLogEntry("info", message));
}
else
{
await Console.Out.WriteLineAsync(message);
}
}
Comment on lines +162 to +171
// Recover from a system-proxy registration left behind by a previous
// instance that crashed before restoring the OS proxy, so a stale
// registration doesn't linger across runs.
var reconciliation = await SystemProxyManager.ReconcileOrphanedSystemProxiesAsync(stoppingToken);
foreach (var orphan in reconciliation.Orphans)
{
_logger.LogInformation(
"Recovered system proxy left by a crashed Dev Proxy instance (PID: {Pid}).",
orphan.Pid);
}
Comment on lines +109 to +116
var startInfo = new ProcessStartInfo
{
FileName = "/bin/bash",
Arguments = $"{bashScriptPath} off",
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
};
@waldekmastykarz waldekmastykarz added the pr-bugfix Fixes a bug label Jul 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr-bugfix Fixes a bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

devproxy stop --force cannot restore the system proxy after a crashed instance

2 participants