Fix the service install hang (pipe deadlock) + stop it from ever freezing the app
Ed uninstalled then reinstalled the service from the app and the app "kind of crashed": its audio froze while the connection stayed alive. The logs told the whole story - the app logged "install requested" then never "install finished"; the elevated helper (child of the app) was still running 40+ minutes later, and the app sat blocked on it, UI thread frozen, so streaming died but the background heartbeat kept ticking. Root cause - a classic pipe deadlock in the elevated installer. GrantUsersWriteToBin ran `icacls /T` over the service bin (100+ files) and read STDERR to end, THEN stdout. icacls floods stdout far past the ~4 KB pipe buffer, so it blocked writing stdout while we blocked reading stderr - forever. That hung DoInstall, which hung the app waiting on it. Fixes (root cause + defence in depth, so a stuck helper can never freeze the app again): - RunProcessCaptured: one safe process runner that drains stdout AND stderr concurrently (async), bounded by a timeout, and kills the child (whole tree) if it overruns. RunSc, RunScCapture and GrantUsersWriteToBin all go through it now. This kills the deadlock. - RunElevated now waits with a 120s cap and returns ElevatedTimedOut instead of blocking forever. - ServiceAction runs the elevated helper OFF the UI thread and reports the result back, so even a slow/stuck helper can't stall the window or its audio. New "timed out" message. - Program.cs Environment.Exit()s after a one-shot service verb, so a helper that finished its work can never linger (non-background thread) with the app waiting on it. Test: "Elevated helper: no pipe deadlock on flooded output" - RunProcessCaptured against a child that floods both pipes with ~260 KB (a big dir listing + a failing dir); it must return promptly with the full output. The pre-fix order would have hung. Gate 46/46. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
5fc1b5bd75
commit
aa5707f8e1
@@ -2380,14 +2380,32 @@ public sealed class MainForm : Form
|
||||
}
|
||||
logFile.Event($"service: {label} requested (elevated)");
|
||||
ServiceStore.AppendServiceEvent($"{label} requested (elevated)");
|
||||
var rc = ServiceControl.RunElevated(verb);
|
||||
var outcome = rc == 0 ? "success" : rc == -1 ? "cancelled/declined" : "failed";
|
||||
// Run the elevated helper OFF the UI thread. A slow or stuck helper must never freeze the window or
|
||||
// its audio — that was the install hang: the UI thread sat in WaitForExit while the pipe-deadlocked
|
||||
// installer never returned, so the app locked up and streaming died. The UI stays live; we report
|
||||
// the outcome (on the UI thread) when the helper comes back or the wait times out.
|
||||
Task.Run(() =>
|
||||
{
|
||||
var rc = ServiceControl.RunElevated(verb);
|
||||
if (IsDisposed) return;
|
||||
try { BeginInvoke(new Action(() => ReportServiceActionResult(label, rc))); } catch { /* window closing */ }
|
||||
});
|
||||
}
|
||||
|
||||
private void ReportServiceActionResult(string label, int rc)
|
||||
{
|
||||
var outcome = rc == 0 ? "success"
|
||||
: rc == -1 ? "cancelled/declined"
|
||||
: rc == ServiceControl.ElevatedTimedOut ? "timed out"
|
||||
: "failed";
|
||||
logFile.Event($"service: {label} finished with code {rc} ({outcome})");
|
||||
ServiceStore.AppendServiceEvent($"{label} finished: code {rc} ({outcome})");
|
||||
if (rc == 0)
|
||||
MessageBox.Show(this, $"Service {label} succeeded.", AppName, MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
else if (rc == -1)
|
||||
MessageBox.Show(this, $"Service {label} was cancelled, or administrator rights were declined.", AppName, MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
else if (rc == ServiceControl.ElevatedTimedOut)
|
||||
MessageBox.Show(this, $"Service {label} is taking longer than expected and hasn't finished yet. It may still complete on its own — check the Service menu status in a moment.", AppName, MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
else
|
||||
MessageBox.Show(this, $"Service {label} failed (code {rc}).", AppName, MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user