diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc index 86e5d6513..495582325 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc @@ -1023,6 +1023,55 @@ uint32_t xeKeWaitForSingleObject(void* object_ptr, uint32_t wait_reason, // self-selecting instead: a healthy wait never reaches the threshold and logs // nothing at all, while a stuck one names itself within seconds. if (cvars::log_stuck_waits) { + // v2 (2026-08-24): the streak counter below caught NOTHING during a real + // freeze — the healthy-run baseline and not one new (thread, object) pair, + // while the process burned 1255 ticks per 10 s with two guest threads + // sitting in this function. That refuted the "loops on timeouts against one + // object" reading and left two blind spots, both of which this second + // counter covers: + // + // * the thread rotates over DIFFERENT objects, so a same-object streak + // never builds — hence the distinct-object count; + // * the waits SUCCEED rather than time out, so a timeout counter has + // nothing to count — hence counting every call and logging the result. + // + // A rate counter is self-selecting the same way: the healthy poller runs at + // ~33 calls/s (a 30 ms timeout), so a 500/s floor is silent on a good run. + { + static thread_local uint64_t window_start_ms = 0; + static thread_local uint32_t window_calls = 0; + static thread_local void* window_objects[8] = {}; + static thread_local uint32_t window_object_count = 0; + const uint64_t now_ms = Clock::QueryHostUptimeMillis(); + if (now_ms - window_start_ms >= 1000) { + if (window_calls > 500) { + XELOGW( + "[wait-rate] thread {:08X} called KeWaitForSingleObject {} times " + "in {} ms over {} distinct object(s); last object {:08X}, last " + "result {:08X}", + XThread::GetCurrentThreadHandle(), window_calls, + now_ms - window_start_ms, window_object_count, + kernel_state()->memory()->HostToGuestVirtual(object_ptr), result); + } + window_start_ms = now_ms; + window_calls = 0; + window_object_count = 0; + } + ++window_calls; + bool seen = false; + for (uint32_t i = 0; i < window_object_count; ++i) { + if (window_objects[i] == object_ptr) { + seen = true; + break; + } + } + if (!seen && window_object_count < 8) { + window_objects[window_object_count++] = object_ptr; + } else if (!seen) { + ++window_object_count; // more than 8: count them, stop recording + } + } + static thread_local void* last_object = nullptr; static thread_local uint32_t timeout_streak = 0; if (result == X_STATUS_TIMEOUT && object_ptr == last_object) {