fix(tui): handled CPU-throttled loop stalls
This commit is contained in:
@@ -32,6 +32,9 @@
|
||||
### Fixed
|
||||
|
||||
- Fixed inline images rendering permanently cropped on Kitty direct-placement terminals (WezTerm, Warp) when an image block straddled the viewport top during streaming: placements are now clipped to the visible slice at write time, and a placement id whose cells reached native scrollback is never re-used ([#8070](https://github.com/can1357/oh-my-pi/pull/8070) by [@voonfoo](https://github.com/voonfoo))
|
||||
### Fixed
|
||||
|
||||
- Fixed long CPU-bound event-loop stalls being misclassified as system sleep and omitted from `ui.loop-blocked` diagnostics ([#8442](https://github.com/can1357/oh-my-pi/pull/8442) by [@Mustaqeem66](https://github.com/Mustaqeem66)).
|
||||
|
||||
## [17.2.12] - 2026-08-08
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ export interface LoopWatchdogOptions {
|
||||
intervalMs?: number;
|
||||
/** A tick later than this past its deadline counts as a block. Default 250. */
|
||||
thresholdMs?: number;
|
||||
/** Overshoot beyond this is suppressed only when the process also burned no CPU. Default 60_000. */
|
||||
/** Overshoot beyond this is suppressed only when the process burned negligible CPU. Default 60_000. */
|
||||
sleepMs?: number;
|
||||
/** Monotonic clock source; injectable for tests. Default `performance.now`. */
|
||||
now?: () => number;
|
||||
@@ -26,13 +26,13 @@ interface LoopWatchdogTimer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fraction of a missed interval that must show up as CPU time for the overshoot
|
||||
* to count as a synchronous stall rather than system sleep. A suspended process
|
||||
* resumes having burned essentially nothing; a wedged one burned the interval on
|
||||
* a core. Half leaves room for a gap that is partly sleep and partly work, which
|
||||
* is still worth reporting.
|
||||
* Fraction of a missed interval that may be process CPU time while the gap is
|
||||
* still treated as system sleep. Keep this near zero: cgroup throttling and
|
||||
* scheduler contention can make a CPU-bound loop consume far less CPU than wall
|
||||
* time. One percent allows a little measurement/background jitter while erring
|
||||
* toward reporting a severe stall instead of hiding it.
|
||||
*/
|
||||
const CPU_BUSY_RATIO = 0.5;
|
||||
const CPU_BUSY_RATIO = 0.01;
|
||||
|
||||
/**
|
||||
* Always-on event-loop lag probe. Each tick is scheduled `intervalMs` ahead of
|
||||
@@ -51,7 +51,8 @@ const CPU_BUSY_RATIO = 0.5;
|
||||
* A long overshoot is classified by CPU time rather than by duration. System
|
||||
* sleep and a CPU-bound wedge both produce an arbitrarily large gap, so duration
|
||||
* alone cannot tell them apart, and suppressing on duration discards exactly the
|
||||
* worst stalls. Only a gap the process did not spend CPU on is treated as sleep.
|
||||
* worst stalls. Only a gap the process spent negligible CPU on is treated as
|
||||
* sleep. CPU accounting is process-wide, so worker activity errs toward logging.
|
||||
*/
|
||||
export class LoopWatchdog {
|
||||
#intervalMs: number;
|
||||
|
||||
@@ -288,12 +288,13 @@ describe("LoopWatchdog long-block classification", () => {
|
||||
expect(warnSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("reports a long block that spent most, but not all, of the gap on CPU", () => {
|
||||
test("reports a long CPU-bound block when the process is CPU throttled", () => {
|
||||
const warnSpy = vi.spyOn(logger, "warn").mockImplementation(() => {});
|
||||
const h = cpuHarness();
|
||||
|
||||
h.wd.start();
|
||||
h.set(120_250, 90_000);
|
||||
// Ten percent CPU is still real work, not suspension.
|
||||
h.set(82_641, 8_200);
|
||||
h.fireTick();
|
||||
|
||||
expect(warnSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
Reference in New Issue
Block a user