Skip to content

Commit 26f0373

Browse files
authored
fix: Reduce unnecessary IPC calls causing UI unresponsiveness (#1542)
## Problem Excessive IPC calls during boot and redundant re-renders cause the app to become unresponsive. Closes #1348 ## Changes 1. Add opt-in IPC timing middleware to log call durations during first 15s of boot 2. Fix markAsViewed re-firing on every render by stabilizing the dependency to a string key instead of a new array reference 3. Dedupe recordActivity mutation calls per task run using a module-level Set ## How did you test this? Manually
1 parent 7a42ee6 commit 26f0373

3 files changed

Lines changed: 53 additions & 23 deletions

File tree

apps/code/src/main/trpc/trpc.ts

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { initTRPC } from "@trpc/server";
2+
import log from "electron-log/main";
23

34
const trpc = initTRPC.create({
45
isServer: true,
@@ -9,28 +10,49 @@ const CALL_RATE_THRESHOLD = 50;
910

1011
const callCounts: Record<string, number[]> = {};
1112

12-
const callRateMonitor = trpc.middleware(async ({ path, next }) => {
13-
if (process.env.NODE_ENV !== "development") {
14-
return next();
15-
}
13+
const ipcTimingEnabled = process.env.IPC_TIMINGS === "true";
14+
const ipcTimingBootMs = 15_000;
15+
const bootTime = Date.now();
1616

17-
const now = Date.now();
18-
if (!callCounts[path]) {
19-
callCounts[path] = [];
20-
}
17+
const callRateMonitor = trpc.middleware(async ({ path, next, type }) => {
18+
const shouldTime =
19+
ipcTimingEnabled && Date.now() - bootTime < ipcTimingBootMs;
20+
const t = shouldTime ? performance.now() : 0;
2121

22-
const timestamps = callCounts[path];
23-
timestamps.push(now);
22+
if (shouldTime) {
23+
log.info(`[ipc-timing] >> ${type} ${path}`);
24+
}
2425

25-
const cutoff = now - CALL_RATE_WINDOW_MS;
26-
while (timestamps.length > 0 && timestamps[0] < cutoff) {
27-
timestamps.shift();
26+
if (process.env.NODE_ENV === "development") {
27+
const now = Date.now();
28+
if (!callCounts[path]) {
29+
callCounts[path] = [];
30+
}
31+
32+
const timestamps = callCounts[path];
33+
timestamps.push(now);
34+
35+
const cutoff = now - CALL_RATE_WINDOW_MS;
36+
while (timestamps.length > 0 && timestamps[0] < cutoff) {
37+
timestamps.shift();
38+
}
39+
40+
if (timestamps.length >= CALL_RATE_THRESHOLD) {
41+
log.warn(
42+
`[ipc-rate] ${type} ${path} called ${timestamps.length} times in ${CALL_RATE_WINDOW_MS}ms`,
43+
);
44+
}
2845
}
2946

30-
if (timestamps.length >= CALL_RATE_THRESHOLD) {
47+
const result = await next();
48+
49+
if (shouldTime) {
50+
log.info(
51+
`[ipc-timing] << ${type} ${path}: ${(performance.now() - t).toFixed(0)}ms`,
52+
);
3153
}
3254

33-
return next();
55+
return result;
3456
});
3557

3658
export const router = trpc.router;

apps/code/src/renderer/features/command-center/components/CommandCenterView.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,17 @@ export function CommandCenterView() {
1313
const { cells, summary } = useCommandCenterData();
1414
const { markAsViewed } = useTaskViewed();
1515

16-
const visibleTaskIds = useMemo(
17-
() => cells.map((c) => c.taskId).filter((id): id is string => id != null),
18-
[cells],
19-
);
16+
const visibleTaskIdsKey = cells
17+
.map((c) => c.taskId)
18+
.filter(Boolean)
19+
.join(",");
2020

2121
useEffect(() => {
22-
for (const taskId of visibleTaskIds) {
22+
if (!visibleTaskIdsKey) return;
23+
for (const taskId of visibleTaskIdsKey.split(",")) {
2324
markAsViewed(taskId);
2425
}
25-
}, [visibleTaskIds, markAsViewed]);
26+
}, [visibleTaskIdsKey, markAsViewed]);
2627

2728
const headerContent = useMemo(
2829
() => (

apps/code/src/renderer/features/sessions/hooks/useSessionConnection.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { useChatTitleGenerator } from "./useChatTitleGenerator";
1111
const log = logger.scope("session-connection");
1212

1313
const connectingTasks = new Set<string>();
14+
const activityRecorded = new Set<string>();
1415

1516
interface UseSessionConnectionOptions {
1617
taskId: string;
@@ -37,14 +38,20 @@ export function useSessionConnection({
3738
useEffect(() => {
3839
const taskRunId = session?.taskRunId;
3940
if (!taskRunId) return;
40-
trpcClient.agent.recordActivity.mutate({ taskRunId }).catch(() => {});
41+
if (!activityRecorded.has(taskRunId)) {
42+
activityRecorded.add(taskRunId);
43+
trpcClient.agent.recordActivity.mutate({ taskRunId }).catch(() => {});
44+
}
4145
const heartbeat = setInterval(
4246
() => {
4347
trpcClient.agent.recordActivity.mutate({ taskRunId }).catch(() => {});
4448
},
4549
5 * 60 * 1000,
4650
);
47-
return () => clearInterval(heartbeat);
51+
return () => {
52+
clearInterval(heartbeat);
53+
activityRecorded.delete(taskRunId);
54+
};
4855
}, [session?.taskRunId]);
4956

5057
useEffect(() => {

0 commit comments

Comments
 (0)