debug server: total send budget + serve-stall telemetry (root cause of 'attract degradation')

The two pre-fix attract-idle 'degradations' (2026-06-10 dumps ...393 and
...707) were observer interference, not guest bugs:
- both wedge dumps show 8/8 main-thread stack samples inside WS2_32!send
  (same call site) — the main-thread-pumped server blocked sending to a
  trickle-draining client, throttling the loop to ~6 fps;
- the 'frame counter reset to 0' was a second process instance (no reset
  path exists for s_frame_count or psx_cycle_count; dump ...707 has lower
  cycles than ...393);
- the 'kernel-only dispatch loop' signature (0xB0/0x650, restore target
  0x80068024, EPC 0x80000048, dispatch_count 0) is the normal Tomba
  title-attract idle pattern — identical in a healthy 60-min post-fix soak.

Fix the loophole and make recurrence self-evident:
- send_all_blocking gains a 15 s TOTAL budget per response (the 2 s
  SO_SNDTIMEO only bounded zero-progress chunks; a slow-but-alive client
  could throttle the emulator indefinitely while heartbeating the
  starvation watchdog). Over budget -> client dropped, runtime unharmed.
- cumulative tcp_send_stall_ms + tcp_clients_dropped exported in the
  freeze heartbeat (top level + per-tick ring 'tcp_ms') and in every
  wedge/fatal dump header, so a TCP-throttled window is attributable at
  a glance. Documented in TCP_COMMANDS.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Matthew Stanley
2026-06-10 14:12:11 -07:00
parent 31377b6d4a
commit aa536b899c
4 changed files with 82 additions and 6 deletions
+7
View File
@@ -87,6 +87,13 @@ void debug_server_init(int port);
* Call once per vblank. */
void debug_server_poll(void);
/* TCP serve-stall telemetry: cumulative main-thread ms spent inside
* bounded TCP sends, and clients dropped for exceeding the send budget.
* Surfaced in the freeze heartbeat / wedge dumps so a TCP-throttled run
* is never misattributed to a guest-side stall. */
uint64_t debug_server_get_tcp_stall_ms(void);
uint32_t debug_server_get_tcp_drops(void);
/* Record the current frame's state into the ring buffer.
* Call after vblank processing. */
void debug_server_record_frame(void);