Fix UPnP close-hang, and add mnemonics to every dialog's buttons (Andre)
UPnP close hang: - RouterPortMapper.Stop()/Dispose() does a SYNCHRONOUS DeletePortMap call to the router (plus StopDiscovery). When the router is slow or unresponsive that blocks — and it ran on the UI thread in FormClosing, so enabling UPnP could make RemSound impossible to close (Andre, this morning; his logs show UPnP teardown taking several seconds even on a good run). Moved the router teardown into the same bounded background-task pattern already used for the ASIO audio dispose: UPnP + audio now tear down off the UI thread, in parallel, under one 3s cap. Anything unfinished is reclaimed on process exit, so the window always closes. Mnemonics on dialog buttons: - Andre flagged the "RemSound is already running" dialog (single-instance) having no shortcut keys. Its three TaskDialog buttons had no & mnemonics; added Alt+S / Alt+F / Alt+C. - Swept every dialog. Custom TaskDialog buttons and Form OK/Cancel/Close buttons that lacked mnemonics now have them: single-instance (Switch/Force/Cancel), save-onto-read-only (Save/Cancel), Add EQ band, Change/Manage profile password, Quick profile switch (Close), Rename peer (OK), About (Close). Cancel stays on Esc in the few dialogs where Alt+C is already taken (Rename peer's Clear, Service profile's peers list) — by design, not a miss. - The rest already had mnemonics (Keyboard-shortcut import, Update-install notice, Profile selection, Recording settings). The dialog-accessibility gate (mnemonic uniqueness + names) stays green, so nothing collides. 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
a433645fd4
commit
8fe52047be
@@ -1434,7 +1434,10 @@ public sealed class MainForm : Form
|
||||
try { processSelfMeter.Dispose(); } catch { }
|
||||
try { deviceChangeNotifier?.Dispose(); } catch { }
|
||||
try { powerResumeHandler?.Dispose(); } catch { }
|
||||
try { routerPortMapper?.Dispose(); } catch { }
|
||||
// NOTE: routerPortMapper.Dispose() is NOT called here — it's moved into the bounded
|
||||
// background teardown below. Its Stop() does a SYNCHRONOUS DeletePortMap call to the router,
|
||||
// which blocks (or hangs) when the router is slow/unresponsive; on the UI thread that froze the
|
||||
// close, so enabling UPnP made RemSound impossible to shut (Andre, 2026-07-18).
|
||||
// Reverse every Win32 lever PerformanceMode applied. The kernel would clean
|
||||
// these up on process exit anyway, but doing it explicitly releases the power
|
||||
// request handle and matches our timeBeginPeriod with a timeEndPeriod.
|
||||
@@ -1449,14 +1452,22 @@ public sealed class MainForm : Form
|
||||
// the form-close path run; the OS reclaims any audio resources on process exit.
|
||||
// Worst case the user sees a brief tray-icon stutter; before this they saw a
|
||||
// ~16 s frozen window before the form went away.
|
||||
var audioDispose = Task.Run(() =>
|
||||
// The UPnP router teardown (DeletePortMap — a synchronous call to a possibly-unresponsive
|
||||
// router) belongs here too: like the ASIO dispose it can block for many seconds, and on the UI
|
||||
// thread it froze the close. Run both OFF the UI thread, in parallel, under one hard timeout.
|
||||
var slowTeardown = Task.Run(() =>
|
||||
{
|
||||
try { sender.Dispose(); } catch { /* ignore */ }
|
||||
try { receiver.Dispose(); } catch { /* ignore */ }
|
||||
var upnp = Task.Run(() => { try { routerPortMapper?.Dispose(); } catch { /* ignore */ } });
|
||||
var audio = Task.Run(() =>
|
||||
{
|
||||
try { sender.Dispose(); } catch { /* ignore */ }
|
||||
try { receiver.Dispose(); } catch { /* ignore */ }
|
||||
});
|
||||
try { Task.WaitAll(upnp, audio); } catch { /* ignore */ }
|
||||
});
|
||||
if (!audioDispose.Wait(TimeSpan.FromSeconds(2)))
|
||||
if (!slowTeardown.Wait(TimeSpan.FromSeconds(3)))
|
||||
{
|
||||
try { logFile.Event("close: audio dispose taking >2s; letting process exit reclaim"); } catch { }
|
||||
try { logFile.Event("close: UPnP/audio teardown taking >3s; letting process exit reclaim"); } catch { }
|
||||
}
|
||||
|
||||
hotkeyController.Dispose();
|
||||
@@ -2812,8 +2823,8 @@ public sealed class MainForm : Form
|
||||
private bool ShowSaveOnReadOnlyWarningDialog()
|
||||
{
|
||||
var verification = new TaskDialogVerificationCheckBox("Do not show me this message again");
|
||||
var saveButton = new TaskDialogButton("Save anyway");
|
||||
var cancelButton = new TaskDialogButton("Cancel") { AllowCloseDialog = true };
|
||||
var saveButton = new TaskDialogButton("&Save anyway");
|
||||
var cancelButton = new TaskDialogButton("&Cancel") { AllowCloseDialog = true };
|
||||
var page = new TaskDialogPage
|
||||
{
|
||||
Caption = AppName,
|
||||
|
||||
Reference in New Issue
Block a user