Skip to content

perf(desktop): batch port scans per interval#1134

Merged
Kitenite merged 5 commits into
superset-sh:mainfrom
Kitenite:perf/desktop-port-scan-batch
Feb 2, 2026
Merged

perf(desktop): batch port scans per interval#1134
Kitenite merged 5 commits into
superset-sh:mainfrom
Kitenite:perf/desktop-port-scan-batch

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Feb 2, 2026

Summary\n- batch port scans into a single lsof/netstat call per interval\n- map port results back to panes via pid ownership\n- preserve existing hint-driven and per-pane update behavior\n\n## Testing\n- not run (not requested)\n

Summary by CodeRabbit

  • Performance Improvements

    • Port scanning now runs as aggregated batches across terminal panes, improving speed and responsiveness and reducing redundant scans.
  • New Features

    • Tracks PID ownership and builds pane-to-port mappings so listening ports are more accurately associated with panes; hint scans are now scheduled for coordinated updates.
  • Bug Fixes

    • Self-healing removal of stale or orphaned ports and cleanup of empty panes keeps displayed port lists accurate.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 2, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Replaces per-pane incremental port scans with a batched global scan: collect PIDs from regular and daemon sessions, query listening ports once for all PIDs, map results back to panes, and apply self‑healing and scheduled hint-scan logic.

Changes

Cohort / File(s) Summary
Batch Port Scanning
apps/desktop/src/main/lib/terminal/port-manager.ts
Consolidates per-pane scans into a single batched flow. Adds ScanState, pidOwnerMap, allPids set, emptyTreePanes, and helpers to collect PIDs (regular + daemon), call getListeningPortsForPids(allPids) returning PortInfo[], build portsByPane, distribute port updates, schedule/clear hint scans, and perform cleanup/self-healing. Preserves public API (kill, session registration) with internal refactors.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant PM as PortManager
  participant RS as RegularSessions
  participant DS as DaemonSessions
  participant PS as PortScanner
  participant Pane as PaneManager

  rect rgba(100,149,237,0.5)
    RS->>PM: report child PIDs
    DS->>PM: report daemon PIDs
  end

  PM->>PM: build allPids set\nand pidOwnerMap
  PM->>PS: getListeningPortsForPids(allPids)
  PS-->>PM: return PortInfo[] (batched)
  PM->>PM: build portsByPane from PortInfo[]\nusing pidOwnerMap
  PM->>Pane: update panes with portsByPane
  Pane-->>PM: acknowledge updates
  alt pane has empty process tree
    PM->>Pane: remove ports (self-heal)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • AviPeltz

Poem

🐰 I hop through PID fields with nimble paws,
I gather ports and mend their tiny flaws,
One batch, one call — the mapping springs to life,
I tidy panes and calm the scanning strife,
🥕 A rabbit’s fix — quick, gentle, no claws.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description is largely incomplete. While it provides a summary of changes, it lacks required sections like Related Issues, Type of Change (which should indicate this is a Refactor), Testing details (despite saying 'not requested'), and Additional Notes. Add the missing template sections: explicitly select 'Refactor' for Type of Change, provide Related Issues links if applicable, expand Testing section, and add Additional Notes if relevant.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main performance optimization: batching port scans per interval instead of per-pane scanning.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@apps/desktop/src/main/lib/terminal/port-manager.ts`:
- Around line 247-270: The catch in collectPidTree silently swallows errors;
change it to log the error and context instead of doing nothing: when
getProcessTree(pid) or subsequent steps throw, call the project logger (e.g.,
processLogger or an existing logger) to record an informative message including
paneId, workspaceId, pid and the error object, while preserving the current
behavior for known non-fatal cases (session-exited). Update collectPidTree to
log the exception before returning, so failures in getProcessTree, scanState
updates, or addPanePids are visible for debugging.
- Around line 149-160: In scheduleHintScan, don't swallow errors from scanPane
with an empty catch; instead catch the error and call the appropriate logger (or
processLogger) to record the failure along with context (paneId and
HINT_SCAN_DELAY_MS or that it was from scheduleHintScan). Update the setTimeout
handler in scheduleHintScan to use .catch(err => { /* log error with paneId and
context */ }) referencing the existing scanPane(paneId) call and
pendingHintScans so failures are visible in logs.
- Around line 162-185: The console.error call in scanPidTreeAndUpdate does not
follow the required "[domain/operation] message" log prefix; replace the current
console.error(`[PortManager] Error scanning ${errorContext}:`, error) with a
prefixed error log that uses the pattern "[port-manager/scanPidTree] ..." (or
similar domain/operation name) and include the error and errorContext in the
message so it matches the project's logging convention; update the call inside
the catch block of scanPidTreeAndUpdate accordingly.

Comment on lines +149 to +160
private scheduleHintScan(paneId: string): void {
this.clearPendingHintScan(paneId);

const timeout = setTimeout(() => {
this.pendingHintScans.delete(paneId);
this.scanPane(paneId).catch(() => {});
}, HINT_SCAN_DELAY_MS);
// Don't keep Electron alive just for port scanning
timeout.unref();

this.pendingHintScans.set(paneId, timeout);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don’t swallow hint-scan failures silently.
The empty catch hides scan failures; please log with context.

Suggested fix
-			this.scanPane(paneId).catch(() => {});
+			this.scanPane(paneId).catch((error) => {
+				console.error(
+					`[port-manager/hint-scan] Failed to scan pane ${paneId}:`,
+					error,
+				);
+			});

As per coding guidelines: Never silently swallow errors with catch(() => {}) or catch(e) { return null }.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private scheduleHintScan(paneId: string): void {
this.clearPendingHintScan(paneId);
const timeout = setTimeout(() => {
this.pendingHintScans.delete(paneId);
this.scanPane(paneId).catch(() => {});
}, HINT_SCAN_DELAY_MS);
// Don't keep Electron alive just for port scanning
timeout.unref();
this.pendingHintScans.set(paneId, timeout);
}
private scheduleHintScan(paneId: string): void {
this.clearPendingHintScan(paneId);
const timeout = setTimeout(() => {
this.pendingHintScans.delete(paneId);
this.scanPane(paneId).catch((error) => {
console.error(
`[port-manager/hint-scan] Failed to scan pane ${paneId}:`,
error,
);
});
}, HINT_SCAN_DELAY_MS);
// Don't keep Electron alive just for port scanning
timeout.unref();
this.pendingHintScans.set(paneId, timeout);
}
🤖 Prompt for AI Agents
In `@apps/desktop/src/main/lib/terminal/port-manager.ts` around lines 149 - 160,
In scheduleHintScan, don't swallow errors from scanPane with an empty catch;
instead catch the error and call the appropriate logger (or processLogger) to
record the failure along with context (paneId and HINT_SCAN_DELAY_MS or that it
was from scheduleHintScan). Update the setTimeout handler in scheduleHintScan to
use .catch(err => { /* log error with paneId and context */ }) referencing the
existing scanPane(paneId) call and pendingHintScans so failures are visible in
logs.

Comment on lines +162 to +185
private async scanPidTreeAndUpdate({
paneId,
workspaceId,
pid,
errorContext,
}: {
paneId: string;
workspaceId: string;
pid: number;
errorContext: string;
}): Promise<void> {
try {
const pids = await getProcessTree(pid);
if (pids.length === 0) {
this.removePortsForPane(paneId);
return;
}

const portInfos = await getListeningPortsForPids(pids);
this.updatePortsForPane({ paneId, workspaceId, portInfos });
} catch (error) {
console.error(`[PortManager] Error scanning ${errorContext}:`, error);
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use the required log prefix format here.
The current message doesn’t follow the [domain/operation] message pattern.

Suggested fix
-			console.error(`[PortManager] Error scanning ${errorContext}:`, error);
+			console.error(
+				`[port-manager/scanPidTreeAndUpdate] Error scanning ${errorContext}:`,
+				error,
+			);

As per coding guidelines: Use prefixed console logging with pattern [domain/operation] message for all logging.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private async scanPidTreeAndUpdate({
paneId,
workspaceId,
pid,
errorContext,
}: {
paneId: string;
workspaceId: string;
pid: number;
errorContext: string;
}): Promise<void> {
try {
const pids = await getProcessTree(pid);
if (pids.length === 0) {
this.removePortsForPane(paneId);
return;
}
const portInfos = await getListeningPortsForPids(pids);
this.updatePortsForPane({ paneId, workspaceId, portInfos });
} catch (error) {
console.error(`[PortManager] Error scanning ${errorContext}:`, error);
}
}
private async scanPidTreeAndUpdate({
paneId,
workspaceId,
pid,
errorContext,
}: {
paneId: string;
workspaceId: string;
pid: number;
errorContext: string;
}): Promise<void> {
try {
const pids = await getProcessTree(pid);
if (pids.length === 0) {
this.removePortsForPane(paneId);
return;
}
const portInfos = await getListeningPortsForPids(pids);
this.updatePortsForPane({ paneId, workspaceId, portInfos });
} catch (error) {
console.error(
`[port-manager/scanPidTreeAndUpdate] Error scanning ${errorContext}:`,
error,
);
}
}
🤖 Prompt for AI Agents
In `@apps/desktop/src/main/lib/terminal/port-manager.ts` around lines 162 - 185,
The console.error call in scanPidTreeAndUpdate does not follow the required
"[domain/operation] message" log prefix; replace the current
console.error(`[PortManager] Error scanning ${errorContext}:`, error) with a
prefixed error log that uses the pattern "[port-manager/scanPidTree] ..." (or
similar domain/operation name) and include the error and errorContext in the
message so it matches the project's logging convention; update the call inside
the catch block of scanPidTreeAndUpdate accordingly.

Comment on lines +247 to +270
private async collectPidTree({
paneId,
workspaceId,
pid,
scanState,
}: {
paneId: string;
workspaceId: string;
pid: number;
scanState: ScanState;
}): Promise<void> {
try {
const panePortMap = new Map<
string,
{ workspaceId: string; pids: number[] }
>();
// Track panes with empty process trees for self-healing
const emptyTreePanes = new Set<string>();

// Scan regular sessions
for (const [paneId, { session, workspaceId }] of this.sessions) {
if (!session.isAlive) continue;

try {
const pid = session.pty.pid;
const pids = await getProcessTree(pid);
if (pids.length > 0) {
panePortMap.set(paneId, { workspaceId, pids });
} else {
// Process tree is gone - mark for self-healing
emptyTreePanes.add(paneId);
}
} catch {
// Session may have exited
}
const pids = await getProcessTree(pid);
if (pids.length === 0) {
scanState.emptyTreePanes.add(paneId);
return;
}

// Scan daemon sessions
for (const [paneId, { workspaceId, pid }] of this.daemonSessions) {
// Skip if PID not yet available
if (pid === null) continue;

try {
const pids = await getProcessTree(pid);
if (pids.length > 0) {
panePortMap.set(paneId, { workspaceId, pids });
} else {
// Process tree is gone - mark for self-healing
emptyTreePanes.add(paneId);
}
} catch {
// Session may have exited
}
}
scanState.panePortMap.set(paneId, { workspaceId, pids });
this.addPanePids({ paneId, workspaceId, pids, scanState });
} catch {
// Session may have exited
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Log PID-tree collection failures instead of swallowing.
The empty catch hides errors and makes diagnosing scan gaps difficult.

Suggested fix
-		} catch {
-			// Session may have exited
-		}
+		} catch (error) {
+			console.error(
+				`[port-manager/collectPidTree] Failed to collect PID tree for pane ${paneId} (pid ${pid}):`,
+				error,
+			);
+		}

As per coding guidelines: Never silently swallow errors with catch(() => {}) or catch(e) { return null }.

🤖 Prompt for AI Agents
In `@apps/desktop/src/main/lib/terminal/port-manager.ts` around lines 247 - 270,
The catch in collectPidTree silently swallows errors; change it to log the error
and context instead of doing nothing: when getProcessTree(pid) or subsequent
steps throw, call the project logger (e.g., processLogger or an existing logger)
to record an informative message including paneId, workspaceId, pid and the
error object, while preserving the current behavior for known non-fatal cases
(session-exited). Update collectPidTree to log the exception before returning,
so failures in getProcessTree, scanState updates, or addPanePids are visible for
debugging.

@Kitenite Kitenite merged commit 5a83c69 into superset-sh:main Feb 2, 2026
4 of 5 checks passed
@Kitenite Kitenite deleted the perf/desktop-port-scan-batch branch February 2, 2026 07:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant