Skip to content

Commit 2288d41

Browse files
t3dotggclaude
andauthored
fix(web): stop the "requests are slow" warning from firing on every provider update (#5570)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent ddfe45c commit 2288d41

4 files changed

Lines changed: 62 additions & 9 deletions

File tree

apps/web/src/components/SlowRpcRequestToastCoordinator.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import { toastManager } from "./ui/toast";
55

66
function describeSlowRequests(requests: ReadonlyArray<SlowRpcAckRequest>): string {
77
const count = requests.length;
8-
const thresholdSeconds = Math.round((requests[0]?.thresholdMs ?? 0) / 1000);
8+
// Thresholds vary per method, so report the smallest one the batch has passed.
9+
const thresholdSeconds = Math.round(
10+
Math.min(...requests.map((request) => request.thresholdMs)) / 1000,
11+
);
912

1013
return `${count} request${count === 1 ? "" : "s"} waiting longer than ${thresholdSeconds}s.`;
1114
}

apps/web/src/connection/platform.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -590,7 +590,7 @@ const rpcRequestObserverLayer = Layer.succeed(
590590
Effect.sync(() => {
591591
nextObservedRpcRequestId += 1;
592592
const requestId = `${environmentId}:${nextObservedRpcRequestId}`;
593-
trackRpcRequestSent(requestId, `${method} · ${environmentId}`);
593+
trackRpcRequestSent(requestId, method, `${method} · ${environmentId}`);
594594
return Effect.sync(() => {
595595
acknowledgeRpcRequest(requestId);
596596
});

apps/web/src/rpc/requestLatencyState.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
getSlowRpcAckRequests,
77
resetRequestLatencyStateForTests,
88
trackRpcRequestSent,
9+
LONG_RUNNING_RPC_ACK_THRESHOLD_MS,
910
SLOW_RPC_ACK_THRESHOLD_MS,
1011
MAX_TRACKED_RPC_ACK_REQUESTS,
1112
} from "./requestLatencyState";
@@ -58,6 +59,32 @@ describe("requestLatencyState", () => {
5859
expect(getSlowRpcAckRequests()).toEqual([]);
5960
});
6061

62+
it("keeps ignoring untracked methods when a display tag is supplied", () => {
63+
trackRpcRequestSent(
64+
"1",
65+
WS_METHODS.previewAutomationConnect,
66+
`${WS_METHODS.previewAutomationConnect} · env-1`,
67+
);
68+
vi.advanceTimersByTime(SLOW_RPC_ACK_THRESHOLD_MS * 2);
69+
70+
expect(getSlowRpcAckRequests()).toEqual([]);
71+
});
72+
73+
it("gives provider updates a longer threshold before warning", () => {
74+
trackRpcRequestSent("1", WS_METHODS.serverUpdateProvider, "server.updateProvider · env-1");
75+
vi.advanceTimersByTime(LONG_RUNNING_RPC_ACK_THRESHOLD_MS - 1);
76+
expect(getSlowRpcAckRequests()).toEqual([]);
77+
78+
vi.advanceTimersByTime(1);
79+
expect(getSlowRpcAckRequests()).toMatchObject([
80+
{
81+
requestId: "1",
82+
tag: "server.updateProvider · env-1",
83+
thresholdMs: LONG_RUNNING_RPC_ACK_THRESHOLD_MS,
84+
},
85+
]);
86+
});
87+
6188
it("evicts the oldest pending requests once the tracker reaches capacity", () => {
6289
for (let index = 0; index < MAX_TRACKED_RPC_ACK_REQUESTS + 1; index += 1) {
6390
trackRpcRequestSent(String(index), "server.getConfig");

apps/web/src/rpc/requestLatencyState.ts

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ import { Atom } from "effect/unstable/reactivity";
55
import { appAtomRegistry } from "./atomRegistry";
66

77
export const SLOW_RPC_ACK_THRESHOLD_MS = 15_000;
8+
/**
9+
* Some requests are slow by design — they shell out to a package manager on the
10+
* server and only respond once the install finishes. Warning about those after
11+
* 15s is noise, so they get a much longer leash.
12+
*/
13+
export const LONG_RUNNING_RPC_ACK_THRESHOLD_MS = 120_000;
814
export const MAX_TRACKED_RPC_ACK_REQUESTS = 256;
915
let slowRpcAckThresholdMs = SLOW_RPC_ACK_THRESHOLD_MS;
1016

@@ -22,7 +28,12 @@ interface PendingRpcAckRequest {
2228
}
2329

2430
const pendingRpcAckRequests = new Map<string, PendingRpcAckRequest>();
25-
const untrackedRpcAckTags = new Set<string>([WS_METHODS.previewAutomationConnect]);
31+
const untrackedRpcAckMethods = new Set<string>([WS_METHODS.previewAutomationConnect]);
32+
const longRunningRpcAckMethods = new Set<string>([
33+
WS_METHODS.serverUpdateProvider,
34+
WS_METHODS.serverRefreshProviders,
35+
WS_METHODS.serverUpdateServer,
36+
]);
2637

2738
const slowRpcAckRequestsAtom = Atom.make<ReadonlyArray<SlowRpcAckRequest>>([]).pipe(
2839
Atom.keepAlive,
@@ -37,34 +48,46 @@ function getSlowRpcAckRequestsValue(): ReadonlyArray<SlowRpcAckRequest> {
3748
return appAtomRegistry.get(slowRpcAckRequestsAtom);
3849
}
3950

40-
function shouldTrackRpcAck(tag: string): boolean {
41-
return !tag.includes("subscribe") && !untrackedRpcAckTags.has(tag);
51+
function shouldTrackRpcAck(method: string): boolean {
52+
return !method.includes("subscribe") && !untrackedRpcAckMethods.has(method);
53+
}
54+
55+
function rpcAckThresholdMs(method: string): number {
56+
return longRunningRpcAckMethods.has(method)
57+
? Math.max(slowRpcAckThresholdMs, LONG_RUNNING_RPC_ACK_THRESHOLD_MS)
58+
: slowRpcAckThresholdMs;
4259
}
4360

4461
export function getSlowRpcAckRequests(): ReadonlyArray<SlowRpcAckRequest> {
4562
return getSlowRpcAckRequestsValue();
4663
}
4764

48-
export function trackRpcRequestSent(requestId: string, tag: string): void {
49-
if (!shouldTrackRpcAck(tag)) {
65+
/**
66+
* Starts the slow-request timer for one in-flight unary RPC. `method` is the
67+
* bare WS method (used to decide whether and how long to wait); `tag` is the
68+
* human-readable label shown in the toast, which defaults to the method.
69+
*/
70+
export function trackRpcRequestSent(requestId: string, method: string, tag = method): void {
71+
if (!shouldTrackRpcAck(method)) {
5072
return;
5173
}
5274

5375
clearTrackedRpcRequest(requestId);
5476
evictOldestPendingRpcRequestIfNeeded();
5577

5678
const startedAtMs = Date.now();
79+
const thresholdMs = rpcAckThresholdMs(method);
5780
const request: SlowRpcAckRequest = {
5881
requestId,
5982
startedAt: new Date(startedAtMs).toISOString(),
6083
startedAtMs,
6184
tag,
62-
thresholdMs: slowRpcAckThresholdMs,
85+
thresholdMs,
6386
};
6487
const timeoutId = setTimeout(() => {
6588
pendingRpcAckRequests.delete(requestId);
6689
appendSlowRpcAckRequest(request);
67-
}, slowRpcAckThresholdMs);
90+
}, thresholdMs);
6891

6992
pendingRpcAckRequests.set(requestId, {
7093
request,

0 commit comments

Comments
 (0)