From a4913e67601d38e248bf2fd6563c06261033c42a Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Thu, 23 Apr 2026 00:14:40 +0200 Subject: [PATCH] Fix memory leaks in dashboard frontend when using Chromium-based browsers (including web view on Windows) * Remove heartbeat (browsers no longer allow windows to be closed anyway) * Prevent parallel requests pertaining to the executions queue Both measures eliminate parallel requests (which, apparently, can lead to memory leaks in Chromium-based browsers; no such observations in Firefox). Fixes #1389 --- CHANGELOG.md | 2 + src/serena/resources/dashboard/dashboard.js | 69 +++++++++------------ 2 files changed, 31 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 836f2a23..1a4840b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,8 @@ Status of the `main` branch. Changes prior to the next official version change w - Fix: `rename_symbol` for Vue files now correctly propagates edits to the TypeScript server, enabling cross-file renames in `.vue` files - Fix: Lean4 stale cache — empty document symbol responses (returned before `lake build` completes) are no longer persisted, preventing symbols from being permanently hidden #1356 +Dashboard: + - Fix: Memory leaks in frontend when using Chromium-based browsers/Windows webview #1389 # v1.1.2 (2026-04-14) diff --git a/src/serena/resources/dashboard/dashboard.js b/src/serena/resources/dashboard/dashboard.js index b41d22ff..e48ff3c2 100644 --- a/src/serena/resources/dashboard/dashboard.js +++ b/src/serena/resources/dashboard/dashboard.js @@ -489,27 +489,6 @@ class Dashboard { // start on overview page self.navigateToPage("overview"); }); - // Initialize heartbeat interval - setInterval(this.heartbeat.bind(this), 250); - } - - heartbeat() { - let self = this; - $.ajax({ - url: '/heartbeat', - type: 'GET', - success: function (response) { - self.heartbeatFailureCount = 0; - }, - error: function (xhr, status, error) { - self.heartbeatFailureCount++; - console.error('Heartbeat failure; count = ', self.heartbeatFailureCount); - if (self.heartbeatFailureCount >= 1) { - console.log('Server appears to be down, closing tab'); - window.close(); - } - }, - }); } toggleMenu() { @@ -539,7 +518,6 @@ class Dashboard { // Start appropriate polling for the page if (page === 'overview') { this.loadNews(); - this.loadConfigOverview(); this.startConfigPolling(); this.startExecutionsPolling(); } else if (page === 'logs') { @@ -597,7 +575,8 @@ class Dashboard { } else { console.log('Config unchanged, skipping display update'); } - }, error: function (xhr, status, error) { + }, + error: function (xhr, status, error) { console.error('Error loading config overview:', error); self.$configDisplay.html('
Error loading configuration
'); self.$basicStatsDisplay.html('
Error loading stats
'); @@ -605,24 +584,23 @@ class Dashboard { self.$availableToolsDisplay.html('
Error loading tools
'); self.$availableModesDisplay.html('
Error loading modes
'); self.$availableContextsDisplay.html('
Error loading contexts
'); - }, complete: function () { + }, + complete: function () { self.waitingForConfigPollingResult = false; } }); } startConfigPolling() { + this.loadConfigOverview(); this.configPollInterval = setInterval(this.loadConfigOverview.bind(this), 1000); } startExecutionsPolling() { + this.loadExecutions() // Poll every 1 second for executions (independent of config polling) // This ensures stuck executions can still be cancelled even if config polling is blocked - this.loadExecutions() - this.executionsPollInterval = setInterval(() => { - this.loadQueuedExecutions(); - this.loadLastExecution(); - }, 1000); + this.executionsPollInterval = setInterval(this.loadExecutions.bind(this), 1000); } displayConfig(config) { @@ -903,26 +881,30 @@ class Dashboard { // ===== Executions Methods ===== - loadQueuedExecutions() { + loadQueuedExecutions(onComplete) { let self = this; $.ajax({ - url: '/queued_task_executions', type: 'GET', success: function (response) { + url: '/queued_task_executions', type: 'GET', + success: function (response) { if (response.status === 'success') { self.displayActiveExecutionsQueue(response.queued_executions || []); } else { console.error('Error loading executions:', response.message); } - }, error: function (xhr, status, error) { + }, + error: function (xhr, status, error) { console.error('Error loading executions:', error); self.$activeExecutionQueueDisplay.html('
Error loading executions
'); - } + }, + complete: onComplete }); } - loadLastExecution() { + loadLastExecution(onComplete) { let self = this; $.ajax({ - url: '/last_execution', type: 'GET', success: function (response) { + url: '/last_execution', type: 'GET', + success: function (response) { if (response.status === 'success') { if (response.last_execution !== null && response.last_execution.logged) { self.displayLastExecution(response.last_execution); @@ -930,21 +912,28 @@ class Dashboard { } else { console.error('Error loading last execution:', response.message); } - }, error: function (xhr, status, error) { + }, + error: function (xhr, status, error) { console.error('Error loading last execution:', error); self.$lastExecutionDisplay.html('
Error loading last execution
'); - } + }, + complete: onComplete }); } loadExecutions() { + const self = this; if (this.waitingForExecutionsPollingResult) { console.log('Still waiting for previous executions poll result, skipping this poll'); - } else { + } + else { this.waitingForExecutionsPollingResult = true; console.log('Polling for executions...'); - this.loadQueuedExecutions(); - this.loadLastExecution(); + this.loadQueuedExecutions(function() { + self.loadLastExecution(function() { + self.waitingForExecutionsPollingResult = false; + }); + }); } }