-
Notifications
You must be signed in to change notification settings - Fork 2.3k
perf: batch and throttle IPC app output to prevent log flooding #3035
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -70,12 +70,22 @@ export function useAppOutputSubscription() { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error("Failed to respond to app input:", error); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; // Don't add to regular output | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return null; // Don't add to regular output | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Add to console entries | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Use "server" type for stdout/stderr to match the backend log store | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // (app_handlers.ts stores these as type: "server") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Handle HMR updates | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| output.message.includes("hmr update") && | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| output.message.includes("[vite]") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onHotModuleReload(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Process proxy server output | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| processProxyServerOutput(output); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Only send client-error logs to central store | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Server logs (stdout/stderr) are already stored in the main process | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const logEntry = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| level: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| output.type === "stderr" || output.type === "client-error" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -87,39 +97,52 @@ export function useAppOutputSubscription() { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| timestamp: output.timestamp ?? Date.now(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Only send client-error logs to central store | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Server logs (stdout/stderr) are already stored in the main process | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (output.type === "client-error") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ipc.misc.addLog(logEntry); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Also update UI state | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setConsoleEntries((prev) => [...prev, logEntry]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Process proxy server output | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| processProxyServerOutput(output); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return logEntry; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [setConsoleEntries, processProxyServerOutput], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [processProxyServerOutput, onHotModuleReload], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Subscribe to app output events from main process | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Subscribe to immediate app output events (input-requested) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const unsubscribe = ipc.events.misc.onAppOutput((output) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Only process events for the currently selected app | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (appId !== null && output.appId === appId) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Handle HMR updates | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| output.message.includes("hmr update") && | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| output.message.includes("[vite]") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onHotModuleReload(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const entry = processAppOutput(output); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (entry) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setConsoleEntries((prev) => [...prev, entry]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return unsubscribe; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [appId, processAppOutput, setConsoleEntries]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Subscribe to batched app output events (stdout/stderr) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const unsubscribe = ipc.events.misc.onAppOutputBatch((outputs) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const newEntries: ReturnType<typeof processAppOutput>[] = []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const output of outputs) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (appId !== null && output.appId === appId) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const entry = processAppOutput(output); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (entry) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| newEntries.push(entry); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| processAppOutput(output); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (newEntries.length > 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setConsoleEntries((prev) => [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...prev, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...(newEntries as NonNullable<(typeof newEntries)[number]>[]), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+126
to
+140
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To improve readability and type safety, we can explicitly type To do this, you'll also need to import the import type { ConsoleEntry } from "@/ipc/types";
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return unsubscribe; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [appId, processAppOutput, onHotModuleReload]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [appId, processAppOutput, setConsoleEntries]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export function useRunApp() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -50,6 +50,7 @@ import { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gitRenameBranch, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from "../utils/git_utils"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { safeSend } from "../utils/safe_sender"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { AppOutput } from "../types/misc"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { normalizePath } from "../../../shared/normalizePath"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isServerFunction, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -278,6 +279,43 @@ Details: ${details || "n/a"} | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ============================================================================= | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // App Output Batcher | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ============================================================================= | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Batches stdout/stderr IPC messages to avoid flooding the renderer when apps | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // emit high-volume logs. Messages are buffered and flushed every 100ms. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const APP_OUTPUT_FLUSH_INTERVAL_MS = 100; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const pendingOutputs = new Map<Electron.WebContents, AppOutput[]>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let flushTimer: ReturnType<typeof setTimeout> | null = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function enqueueAppOutput( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sender: Electron.WebContents, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| output: AppOutput, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): void { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let queue = pendingOutputs.get(sender); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!queue) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| queue = []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pendingOutputs.set(sender, queue); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| queue.push(output); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!flushTimer) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| flushTimer = setTimeout(flushAllAppOutputs, APP_OUTPUT_FLUSH_INTERVAL_MS); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+304
to
+305
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Because Useful? React with 👍 / 👎. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function flushAllAppOutputs(): void { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| flushTimer = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+309
to
+310
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Missing
Suggested change
Was this helpful? React with 👍 or 👎 to provide feedback. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const [sender, outputs] of pendingOutputs) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+309
to
+311
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (outputs.length > 0) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| safeSend(sender, "app:output-batch", outputs); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+309
to
+313
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If the user clicks Clear logs while an app is still producing output, Useful? React with 👍 / 👎. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pendingOutputs.clear(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+309
to
+317
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This breaks the 100ms batching guarantee for the new process. The fix is to cancel the pending timer before resetting:
Suggested change
Prompt To Fix With AIThis is a comment left during a code review.
Path: src/ipc/handlers/app_handlers.ts
Line: 309-317
Comment:
**Missing `clearTimeout` before manual flush on process close**
`flushAllAppOutputs()` sets `flushTimer = null` but never calls `clearTimeout` on the handle, so the originally-scheduled timer is still alive. When it fires ~100ms later it calls `flushAllAppOutputs()` again. In isolation that's benign (the map is already empty), but in a race where a **new** app process starts within those 100ms:
1. Close fires → manual `flushAllAppOutputs()` → `flushTimer = null`, map cleared.
2. New process starts → `enqueueAppOutput` sees `flushTimer === null`, schedules a **new** timer.
3. The old stale timer fires → calls `flushAllAppOutputs()` → drains the new process's messages earlier than intended.
4. The new timer fires → no-op (map is empty).
This breaks the 100ms batching guarantee for the new process. The fix is to cancel the pending timer before resetting:
```suggestion
function flushAllAppOutputs(): void {
if (flushTimer !== null) {
clearTimeout(flushTimer);
}
flushTimer = null;
for (const [sender, outputs] of pendingOutputs) {
if (outputs.length > 0) {
safeSend(sender, "app:output-batch", outputs);
}
}
pendingOutputs.clear();
}
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function flushAllAppOutputs(): void { | |
| flushTimer = null; | |
| for (const [sender, outputs] of pendingOutputs) { | |
| if (outputs.length > 0) { | |
| safeSend(sender, "app:output-batch", outputs); | |
| } | |
| } | |
| pendingOutputs.clear(); | |
| } | |
| function flushAllAppOutputs(): void { | |
| flushTimer = null; | |
| const outputsToFlush = new Map(pendingOutputs); | |
| pendingOutputs.clear(); | |
| for (const [sender, outputs] of outputsToFlush) { | |
| if (outputs.length > 0) { | |
| safeSend(sender, "app:output-batch", outputs); | |
| } | |
| } | |
| } |
cubic-dev-ai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 MEDIUM | performance-feel / latency
Proxy server start message is batched but is latency-sensitive
The [dyad-proxy-server]started= message triggers showing the preview panel in the renderer. By routing it through enqueueAppOutput instead of safeSend, it's now delayed by up to 100ms. This message is functionally similar to input-requested — it drives a UI state transition that the user is actively waiting for (the preview panel appearing after the dev server starts).
While 100ms is usually below perception threshold, this is a latency-sensitive signal. If the process exits quickly after the proxy starts, there's also a theoretical window where the onStarted callback fires after the close handler has already flushed and cleared the map, leaving this message to sit until the next flush timer (which may never come if no other process is running).
💡 Suggestion: Consider sending proxy-server-started messages immediately via safeSend (like input-requested), since they trigger a user-visible UI transition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Process close flushes all apps' buffered output
Low Severity
flushAllAppOutputs() is a global operation that drains the pendingOutputs buffer for every WebContents entry. Calling it inside a single app's "close" event handler means that when app A's process exits, it also prematurely flushes any buffered messages still accumulating for app B (and clears app B's queue via pendingOutputs.clear()). Those messages are sent to the renderer before the normal 100ms timer fires, which is harmless on its own, but it also cancels the in-flight timer — the flushTimer is set to null inside flushAllAppOutputs, so any subsequent messages from app B will start a brand-new timer. This breaks the intended batching window for unrelated apps and can cause the timer to be abandoned while pendingOutputs still has entries if the close event races with a setTimeout callback.


Uh oh!
There was an error while loading. Please reload this page.