a73x

Recover from Vulkan fence/acquire timeout + device-lost

closed   by a73x

Labels: backlog

[claude 2026-04-18] Diagnosed during today's debugging of a terminal freeze reported by the user.

## Observed symptom

Running waystty as a daily driver, window became visible-but-unresponsive: no keystrokes accepted, resize events ignored, last frame still on screen. Process (PID 2268004) had been up 11h52m, 74,470 voluntary context switches.

## Root cause

Main thread wedged in `vkWaitForFences(..., timeout=UINT64_MAX)` inside the NVIDIA 595.58.03 driver. Full backtrace:

```
main.runTerminal
  renderer.Context.drawCells
    vk.DeviceWrapper.waitForFences(..., timeout=18446744073709551615)
      libnvidia-glcore internal poll() ← wedged here
```

The NVIDIA driver dropped a fence-signal for `in_flight_fence`. GPU work likely completed, but the kernel syncobj / sync fd the driver's userspace was polling never got notified. `vkWaitForFences` has no timeout, so the main loop is permanently blocked — no input dispatch, no resize handling, no exit.

Not a waystty logic bug in itself — a Vulkan-tutorial antipattern (`UINT64_MAX`) meeting a real-world driver flake.

## Offending sites (all use `std.math.maxInt(u64)`)

- `src/renderer.zig:1275` — `drawClear` fence wait
- `src/renderer.zig:1282` — `drawClear` acquireNextImageKHR
- `src/renderer.zig:1478` — atlas transfer fence wait
- `src/renderer.zig:1710` — `drawCells` fence wait (the one wedged today)
- `src/renderer.zig:1721` — `drawCells` acquireNextImageKHR
- `src/renderer.zig:1818` — capture path fence wait
- `src/renderer.zig:1824` — capture fence wait

## Proposed scope

Full device-lost recovery:

1. **Bounded timeouts** on every `waitForFences` and `acquireNextImageKHR` call. Suggested: 2s for fence waits, 100ms for acquireNextImage.
2. **Classify errors**: `VK_TIMEOUT`, `VK_ERROR_OUT_OF_DATE_KHR`, `VK_ERROR_DEVICE_LOST`.
3. **On timeout** (first occurrence): log, skip the frame, retry on the next iteration. Don't spam.
4. **On repeated timeout (N consecutive) or `VK_ERROR_DEVICE_LOST`**: tear down and recreate:
   - Swapchain (reuse current code path)
   - All fences (`in_flight_fence`, `atlas_transfer_fence`, `capture_fence`)
   - Pipeline + render pass (if device-lost)
   - Atlas GPU image + instance buffer (CPU-side glyph cache survives; reupload on demand)
   - Possibly the `VkDevice` itself on true device-lost
5. **State preservation**: `vt.Terminal` grid lives in CPU memory and must not be touched by recovery. Font atlas CPU cache also survives — only GPU-side resources get rebuilt. After recovery, mark the terminal fully dirty and re-render from scratch.
6. **User-visible signal**: a one-line warning on stderr when recovery fires, so a repeated driver flake is observable instead of invisible.

## Non-goals (for v1)

- Exotic swapchain-lost paths (surface lost, full-screen exclusive flicker, etc.) — handle only the fence-timeout and `DEVICE_LOST` cases that match the real-world symptom.
- Persisting scrollback across a device-lost event — stays in RAM, survives naturally.

## Related

- #7b6053b0 — opt-in file logging for freeze/debug diagnosis. Complementary: logging helps diagnose *future* wedges, this ticket is about surviving them.

## Next step

Needs a design (brainstorm → spec → plan). The recovery path touches renderer ownership + lifecycle of every GPU resource; not a one-file change.

Close reason: [claude 2026-04-18] Duplicate of ab6c92f0 (older by ~1 minute, identical body). Closing this one; work tracked on ab6c92f0.