Skip to content

Conversation

@jankuca
Copy link
Contributor

@jankuca jankuca commented Oct 5, 2025

Summary by CodeRabbit

  • Bug Fixes
    • Automatically cleans up stray Deepnote processes on startup to prevent conflicts.
    • Improves shutdown reliability with graceful termination, timeouts, and safe force-kill fallback.
    • Coordinates start/stop to wait for in-flight operations, reducing errors and hangs.
    • Enhances visibility with clearer logs during shutdown and failure cases.
  • Refactor
    • Streamlined server lifecycle management with explicit start/stop and disposal for more reliable operation across platforms.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 5, 2025

📝 Walkthrough

Walkthrough

  • DeepnoteServerStarter now injects IAsyncDisposableRegistry and registers itself for async disposal.
  • Constructor performs immediate orphan deepnote-toolkit process cleanup with non-fatal handling.
  • dispose() is now async; it waits for in-flight operations, attempts graceful server shutdown with timeouts and force-kill fallback, then disposes tracked resources.
  • Added private cleanupOrphanedProcesses() to discover and terminate orphaned deepnote-toolkit processes cross-platform.
  • Added orchestration to coordinate pending start/stop operations.
  • Process termination awaits exits and logs progress/errors.
  • IDeepnoteServerStarter interface no longer extends vscode.Disposable; adds getOrStartServer(...) and async dispose(); stopServer(...) unchanged.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Client
  participant Starter as DeepnoteServerStarter
  participant Toolkit as deepnote-toolkit (proc)
  participant Registry as IAsyncDisposableRegistry

  Note over Starter: Constructor
  rect rgba(230,250,255,0.6)
    Starter->>Registry: register(Starter)  [New]
    Starter->>Starter: cleanupOrphanedProcesses()  [New]
  end

  Note over Client,Starter: Get or start server
  rect rgba(240,240,255,0.6)
    Client->>Starter: getOrStartServer(interpreter, uri, token)  [New]
    alt existing server for uri
      Starter-->>Client: DeepnoteServerInfo
    else no server
      Starter->>Starter: track pending start  [Changed]
      Starter->>Toolkit: spawn/start
      Toolkit-->>Starter: ready/port
      Starter->>Starter: record server info
      Starter-->>Client: DeepnoteServerInfo
    end
  end
Loading
sequenceDiagram
  autonumber
  actor Client
  participant Starter as DeepnoteServerStarter
  participant Toolkit as deepnote-toolkit (proc)
  participant Registry as IAsyncDisposableRegistry

  Note over Client,Starter: Stop or Dispose
  rect rgba(255,245,230,0.6)
    alt stop specific server
      Client->>Starter: stopServer(uri)
      Starter->>Starter: wait for related pending ops  [Changed]
      Starter->>Toolkit: graceful terminate
      alt exits within timeout
        Toolkit-->>Starter: exit
      else timeout
        Starter->>Toolkit: force kill  [Changed]
      end
      Starter->>Starter: clear server tracking
      Starter-->>Client: done
    else dispose all
      Client->>Starter: dispose()  [Changed -> async]
      Starter->>Starter: wait for all pending ops  [Changed]
      loop each tracked server
        Starter->>Toolkit: graceful terminate
        alt exits within timeout
          Toolkit-->>Starter: exit
        else timeout
          Starter->>Toolkit: force kill
        end
      end
      Starter->>Starter: dispose internal resources
      Starter-->>Client: Promise<void> resolved
    end
  end
Loading

Pre-merge checks

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly highlights the primary change implemented in this PR, which focuses on automatically terminating orphaned Deepnote Toolkit processes, and it maps directly to the newly added cleanup logic in DeepnoteServerStarter. It is concise, clear, and specific enough for reviewers to understand the main objective at a glance.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

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

Copy link
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: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c35f414 and 4a996ac.

📒 Files selected for processing (2)
  • src/kernels/deepnote/deepnoteServerStarter.node.ts (4 hunks)
  • src/kernels/deepnote/types.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.node.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use *.node.ts for Desktop-specific implementations that require full file system access and Python environments

Files:

  • src/kernels/deepnote/deepnoteServerStarter.node.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Inject interfaces, not concrete classes
Avoid circular dependencies
Use l10n.t() for user-facing strings
Use typed error classes from src/platform/errors/ when throwing or handling errors
Use the ILogger service instead of console.log
Preserve error details while scrubbing PII in messages and telemetry
Include the Microsoft copyright header in source files
Prefer async/await and handle cancellation with CancellationToken

**/*.{ts,tsx}: Order class members (methods, fields, properties) first by accessibility, then alphabetically
Separate third-party imports from local file imports

Files:

  • src/kernels/deepnote/deepnoteServerStarter.node.ts
  • src/kernels/deepnote/types.ts
src/kernels/**/*.ts

📄 CodeRabbit inference engine (.github/instructions/kernel.instructions.md)

src/kernels/**/*.ts: Use event-driven updates (EventEmitter) for state changes
Monitor and dispose pending promises to prevent leaks during teardown
Use WeakRef/weak references for notebook/object backreferences to avoid memory leaks
Respect CancellationToken in all async operations

Files:

  • src/kernels/deepnote/deepnoteServerStarter.node.ts
  • src/kernels/deepnote/types.ts
**/!(*.node|*.web).ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Place shared cross-platform logic in common .ts files (not suffixed with .node or .web)

Files:

  • src/kernels/deepnote/types.ts
🧬 Code graph analysis (1)
src/kernels/deepnote/deepnoteServerStarter.node.ts (2)
src/platform/common/types.ts (4)
  • IHttpClient (164-164)
  • IHttpClient (165-171)
  • IAsyncDisposableRegistry (232-232)
  • IAsyncDisposableRegistry (233-235)
src/platform/logging/index.ts (1)
  • logger (35-48)

Comment on lines +331 to +405
private async cleanupOrphanedProcesses(): Promise<void> {
try {
logger.info('Checking for orphaned deepnote-toolkit processes...');
const processService = await this.processServiceFactory.create(undefined);

// Find all deepnote-toolkit server processes
let command: string;
let args: string[];

if (process.platform === 'win32') {
// Windows: use tasklist and findstr
command = 'tasklist';
args = ['/FI', 'IMAGENAME eq python.exe', '/FO', 'CSV', '/NH'];
} else {
// Unix-like: use ps and grep
command = 'ps';
args = ['aux'];
}

const result = await processService.exec(command, args, { throwOnStdErr: false });

if (result.stdout) {
const lines = result.stdout.split('\n');
const pidsToKill: number[] = [];

for (const line of lines) {
// Look for processes running deepnote_toolkit server
if (line.includes('deepnote_toolkit') && line.includes('server')) {
// Extract PID based on platform
let pid: number | undefined;

if (process.platform === 'win32') {
// Windows CSV format: "python.exe","12345",...
const match = line.match(/"python\.exe","(\d+)"/);
if (match) {
pid = parseInt(match[1], 10);
}
} else {
// Unix format: user PID ...
const parts = line.trim().split(/\s+/);
if (parts.length > 1) {
pid = parseInt(parts[1], 10);
}
}

if (pid && !isNaN(pid)) {
pidsToKill.push(pid);
}
}
}

if (pidsToKill.length > 0) {
logger.info(
`Found ${pidsToKill.length} orphaned deepnote-toolkit processes: ${pidsToKill.join(', ')}`
);
this.outputChannel.appendLine(
`Cleaning up ${pidsToKill.length} orphaned deepnote-toolkit process(es) from previous session...`
);

// Kill each process
for (const pid of pidsToKill) {
try {
if (process.platform === 'win32') {
await processService.exec('taskkill', ['/F', '/T', '/PID', pid.toString()], {
throwOnStdErr: false
});
} else {
await processService.exec('kill', ['-9', pid.toString()], { throwOnStdErr: false });
}
logger.info(`Killed orphaned process ${pid}`);
} catch (ex) {
logger.warn(`Failed to kill process ${pid}: ${ex}`);
}
}

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Don't kill active toolkit servers from other windows

Line 332 kicks off cleanupOrphanedProcesses() and the loop on Lines 356‑404 force-kills every deepnote_toolkit server PID it finds. Launching a second VS Code window will therefore terminate the live server owned by the first window, instantly dropping active notebooks. Please constrain this to genuine orphans (e.g., persist per-window ownership and skip PIDs still claimed, or verify that the parent process is gone) instead of blanket termination.

🤖 Prompt for AI Agents
In src/kernels/deepnote/deepnoteServerStarter.node.ts around lines 331-405, the
cleanup currently force-kills every process matching "deepnote_toolkit server",
which can terminate active servers from other VS Code windows; change the logic
to only kill genuine orphans by (1) persisting ownership when starting a server
(write a small per-window/session lock file in OS temp with server PID and a
unique session id) and (2) before killing a PID, verify ownership: if a lock
file exists for that PID and its session id differs from the current session,
skip it; additionally, verify the PID is actually orphaned by checking its
parent (on Unix use ps -o ppid= -p <pid> or /proc/<pid>/stat, on Windows query
ParentProcessId via wmic/PowerShell) and only kill if the parent is gone or
parent PID is 1 (or otherwise indicates orphaned), and log/skipped decisions;
implement cleanup of stale lock files when you successfully kill the process or
when the server you started exits.

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.

2 participants