fix(host/windows): DuplicateOutput1 retry wait 200ms (Apollo's value), env-tunable

The old-dup kernel teardown takes ~200ms (Apollo waits exactly that), so the
previous 2-16ms retries were too short and still fell through to the churning
legacy dup. Bump to PUNKTFUNK_DUP_RETRY_MS (default 200) x PUNKTFUNK_DUP_RETRY_N
(default 6) so the robust DuplicateOutput1 dup wins the race. Env-tunable for
on-box dialing without a rebuild.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-16 16:07:52 +00:00
parent eb451d8bc6
commit ce84861e3a
+17 -6
View File
@@ -194,21 +194,32 @@ unsafe fn duplicate_output(
// which "succeeds" into a fragile dup that churns ACCESS_LOST/MODE_CHANGE every few ms on this // which "succeeds" into a fragile dup that churns ACCESS_LOST/MODE_CHANGE every few ms on this
// cross-GPU IDD. (This is why DuplicateOutput1 failed but the legacy call a beat later // cross-GPU IDD. (This is why DuplicateOutput1 failed but the legacy call a beat later
// succeeded — pure timing. Apollo retries DuplicateOutput1 2x/200ms for the same reason.) // succeeded — pure timing. Apollo retries DuplicateOutput1 2x/200ms for the same reason.)
// Apollo waits 200 ms between DuplicateOutput1 attempts — the kernel-side teardown of the
// just-released duplication takes that long, so short (ms) waits aren't enough. Env-tunable so
// we can dial it without a rebuild: PUNKTFUNK_DUP_RETRY_MS (per-wait, default 200) ×
// PUNKTFUNK_DUP_RETRY_N (attempts, default 6) → ~1 s worst case before the legacy fallback.
let retry_ms: u64 = std::env::var("PUNKTFUNK_DUP_RETRY_MS")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(200);
let attempts: u64 = std::env::var("PUNKTFUNK_DUP_RETRY_N")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(6)
.max(1);
let mut last_err = None; let mut last_err = None;
for attempt in 0..5u64 { for attempt in 0..attempts {
match output5.DuplicateOutput1(device, 0, &formats) { match output5.DuplicateOutput1(device, 0, &formats) {
Ok(d) => { Ok(d) => {
if attempt > 0 { if attempt > 0 {
tracing::info!(attempt, "DuplicateOutput1 succeeded on retry (raced old-dup teardown)"); tracing::info!(attempt, "DuplicateOutput1 succeeded on retry (rode out old-dup teardown race)");
} }
return Ok(d); return Ok(d);
} }
Err(e) => { Err(e) => {
last_err = Some(e); last_err = Some(e);
// Escalating brief waits: 2,4,8,16 ms (skip after the last attempt). Bounded so a if attempt + 1 < attempts {
// GENUINE failure still falls back to legacy quickly (~30 ms worst case). std::thread::sleep(Duration::from_millis(retry_ms));
if attempt < 4 {
std::thread::sleep(Duration::from_millis(2u64 << attempt));
} }
} }
} }