Skip to content

Commit

Permalink
Implement restart (#11668)
Browse files Browse the repository at this point in the history
* Support debug "restart" #7670

* More debug refactoring, set up IW for restart

* Hook up Restart for rbl, via a custom Restart request implementation.
Move commands out of DebuggingManager, and expose methods on that class for use by the rbl controller

* Hook up restart for "debug cell"

* Clean up dumped cell files in disconnect, so it can be awaited

* Fix comments in controller

* formatting

* Fix PR comments

* Wait for debugger to be totally set up before executing cell in IW

* Add test for restarting debug.
Also fix the restart request handler to avoid cancelling the restart request on accident.
  • Loading branch information
roblourens committed Oct 21, 2022
1 parent a9dd309 commit 251bcc7
Show file tree
Hide file tree
Showing 21 changed files with 682 additions and 545 deletions.
12 changes: 0 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -288,13 +288,6 @@
"icon": "$(debug-start)",
"enablement": "jupyter.development && notebookType == jupyter-notebook && isWorkspaceTrusted && jupyter.replayLogLoaded && !jupyter.webExtension"
},
{
"command": "jupyter.debugNotebook",
"title": "%jupyter.command.jupyter.debug.title%",
"icon": "$(bug)",
"category": "Jupyter",
"enablement": "notebookKernelCount > 0 && resource not in jupyter.notebookeditor.runByLineDocuments"
},
{
"command": "jupyter.filterKernels",
"title": "%jupyter.command.jupyter.filterKernels.title%",
Expand Down Expand Up @@ -1099,11 +1092,6 @@
"title": "%jupyter.commandPalette.jupyter.replayPylanceLog.title%",
"when": "jupyter.development && isWorkspaceTrusted"
},
{
"command": "jupyter.debugNotebook",
"title": "%jupyter.command.jupyter.debug.title%",
"when": "false"
},
{
"command": "jupyter.interactive.copyCell",
"when": "false"
Expand Down
4 changes: 2 additions & 2 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ interface ICommandNameWithoutArgumentTypeMapping {
['workbench.action.showCommands']: [];
['workbench.action.debug.continue']: [];
['workbench.action.debug.stepOver']: [];
['workbench.action.debug.restart']: [];
['workbench.action.debug.stop']: [];
['workbench.action.reloadWindow']: [];
['workbench.action.closeActiveEditor']: [];
Expand Down Expand Up @@ -193,11 +194,10 @@ export interface ICommandNameArgumentTypeMapping extends ICommandNameWithoutArgu
NotebookDocument | undefined
];
[DSCommands.SelectNativeJupyterUriFromToolBar]: [];
[DSCommands.DebugNotebook]: [];
[DSCommands.RunByLine]: [NotebookCell];
[DSCommands.RunAndDebugCell]: [NotebookCell];
[DSCommands.RunByLineNext]: [NotebookCell];
[DSCommands.RunByLineStop]: [];
[DSCommands.RunByLineStop]: [NotebookCell];
[DSCommands.ReplayPylanceLog]: [Uri];
[DSCommands.ReplayPylanceLogStep]: [];
[DSCommands.InstallPythonExtensionViaKernelPicker]: [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { NotebookCell } from 'vscode';
import { DebugProtocol } from 'vscode-debugprotocol';
import { IKernel } from '../../../kernels/types';
import { DebuggingTelemetry } from '../../../notebooks/debugger/constants';
import { isJustMyCodeNotification } from '../../../notebooks/debugger/debugCellControllers';
import { isJustMyCodeNotification } from '../../../notebooks/debugger/controllers/debugCellController';
import { IDebuggingDelegate, IKernelDebugAdapter } from '../../../notebooks/debugger/debuggingTypes';
import { cellDebugSetup } from '../../../notebooks/debugger/helper';
import { createDeferred } from '../../../platform/common/utils/async';
Expand All @@ -18,8 +18,9 @@ import { getInteractiveCellMetadata } from '../../helpers';
* Dumping a cell is how the IPython kernel determines the file path of a cell
*/
export class DebugCellController implements IDebuggingDelegate {
private readonly _ready = createDeferred<void>();
private _ready = createDeferred();
public readonly ready = this._ready.promise;

private cellDumpInvoked?: boolean;
constructor(
private readonly debugAdapter: IKernelDebugAdapter,
Expand All @@ -45,7 +46,7 @@ export class DebugCellController implements IDebuggingDelegate {
}

private debugCellDumped?: Promise<void>;
public async willSendRequest(request: DebugProtocol.Request): Promise<void> {
public async willSendRequest(request: DebugProtocol.Request): Promise<undefined | DebugProtocol.Response> {
const metadata = getInteractiveCellMetadata(this.debugCell);
if (request.command === 'setBreakpoints' && metadata && metadata.generatedCode && !this.cellDumpInvoked) {
if (!this.debugCellDumped) {
Expand All @@ -60,5 +61,7 @@ export class DebugCellController implements IDebuggingDelegate {
await this.debugCellDumped;
this._ready.resolve();
}

return undefined;
}
}
15 changes: 15 additions & 0 deletions src/interactive-window/debugger/jupyter/debugger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

'use strict';
import { Debugger } from '../../../notebooks/debugger/debugger';
import { createDeferred } from '../../../platform/common/utils/async';

export class IWDebugger extends Debugger {
private readonly _ready = createDeferred<void>();
public readonly ready = this._ready.promise;

public resolve() {
this._ready.resolve();
}
}
167 changes: 75 additions & 92 deletions src/interactive-window/debugger/jupyter/debuggingManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,53 +3,53 @@

import { inject, injectable } from 'inversify';
import {
NotebookDocument,
debug,
DebugAdapterDescriptor,
DebugAdapterInlineImplementation,
DebugSession,
NotebookCell,
DebugSessionOptions,
DebugAdapterDescriptor,
NotebookEditor,
debug
NotebookCell,
NotebookDocument,
NotebookEditor
} from 'vscode';
import { IKernelProvider } from '../../../kernels/types';
import { IControllerLoader, IControllerSelection } from '../../../notebooks/controllers/types';
import { pythonIWKernelDebugAdapter } from '../../../notebooks/debugger/constants';
import { DebuggingManagerBase } from '../../../notebooks/debugger/debuggingManagerBase';
import {
IDebuggingManager,
KernelDebugMode,
IKernelDebugAdapterConfig,
IDebugLocationTrackerFactory
IDebugLocationTrackerFactory,
IInteractiveWindowDebugConfig,
KernelDebugMode
} from '../../../notebooks/debugger/debuggingTypes';
import { IKernelProvider } from '../../../kernels/types';
import { IpykernelCheckResult, assertIsDebugConfig } from '../../../notebooks/debugger/helper';
import { KernelDebugAdapter } from './kernelDebugAdapter';
import { assertIsInteractiveWindowDebugConfig, IpykernelCheckResult } from '../../../notebooks/debugger/helper';
import { IExtensionSingleActivationService } from '../../../platform/activation/types';
import {
ICommandManager,
IApplicationShell,
IVSCodeNotebook,
IDebugService
ICommandManager,
IDebugService,
IVSCodeNotebook
} from '../../../platform/common/application/types';
import { IPlatformService } from '../../../platform/common/platform/types';
import { IConfigurationService } from '../../../platform/common/types';
import { DataScience } from '../../../platform/common/utils/localize';
import { traceInfoIfCI, traceInfo, traceError } from '../../../platform/logging';
import { noop } from '../../../platform/common/utils/misc';
import { IServiceContainer } from '../../../platform/ioc/types';
import { traceError, traceInfo, traceInfoIfCI } from '../../../platform/logging';
import * as path from '../../../platform/vscode-path/path';
import { DebugCellController } from './debugCellControllers';
import { DebuggingManagerBase } from '../../../notebooks/debugger/debuggingManagerBase';
import { IConfigurationService } from '../../../platform/common/types';
import { IFileGeneratedCodes } from '../../editor-integration/types';
import { buildSourceMap } from '../helper';
import { noop } from '../../../platform/common/utils/misc';
import { IInteractiveWindowDebuggingManager } from '../../types';
import { IControllerLoader, IControllerSelection } from '../../../notebooks/controllers/types';
import { IServiceContainer } from '../../../platform/ioc/types';
import { buildSourceMap } from '../helper';
import { DebugCellController } from './debugCellController';
import { IWDebugger } from './debugger';
import { KernelDebugAdapter } from './kernelDebugAdapter';

/**
* The DebuggingManager maintains the mapping between notebook documents and debug sessions.
*/
@injectable()
export class InteractiveWindowDebuggingManager
extends DebuggingManagerBase
implements IExtensionSingleActivationService, IDebuggingManager, IInteractiveWindowDebuggingManager
implements IExtensionSingleActivationService, IInteractiveWindowDebuggingManager
{
public constructor(
@inject(IKernelProvider) kernelProvider: IKernelProvider,
Expand Down Expand Up @@ -85,102 +85,86 @@ export class InteractiveWindowDebuggingManager
})
);
}

public getDebugMode(_notebook: NotebookDocument): KernelDebugMode | undefined {
return KernelDebugMode.InteractiveWindow;
}

public async start(editor: NotebookEditor, cell: NotebookCell) {
traceInfoIfCI(`Starting debugging IW`);

if (this.notebookInProgress.has(editor.notebook)) {
traceInfo(`Cannot start debugging. Already debugging this notebook`);
return;
}

if (this.isDebugging(editor.notebook)) {
traceInfo(`Cannot start debugging. Already debugging this notebook document. Toolbar should update`);
return;
}

const checkIpykernelAndStart = async (allowSelectKernel = true): Promise<void> => {
const ipykernelResult = await this.checkForIpykernel6(editor.notebook);
switch (ipykernelResult) {
case IpykernelCheckResult.NotInstalled:
// User would have been notified about this, nothing more to do.
return;
case IpykernelCheckResult.Outdated:
case IpykernelCheckResult.Unknown: {
this.promptInstallIpykernel6().then(noop, noop);
return;
}
case IpykernelCheckResult.Ok: {
await this.startDebuggingCell(editor.notebook, cell);
return;
}
case IpykernelCheckResult.ControllerNotSelected: {
if (allowSelectKernel) {
await this.commandManager.executeCommand('notebook.selectKernel', { notebookEditor: editor });
await checkIpykernelAndStart(false);
}
}
}
};

try {
this.notebookInProgress.add(editor.notebook);
await checkIpykernelAndStart();
} catch (e) {
traceInfo(`Error starting debugging: ${e}`);
} finally {
this.notebookInProgress.delete(editor.notebook);
const ipykernelResult = await this.checkIpykernelAndPrompt(cell);
if (ipykernelResult === IpykernelCheckResult.Ok) {
await this.startDebuggingCell(editor.notebook, cell);
}
}

private async startDebuggingCell(doc: NotebookDocument, cell: NotebookCell) {
const settings = this.configService.getSettings(doc.uri);
const config: IKernelDebugAdapterConfig = {
const config: IInteractiveWindowDebugConfig = {
type: pythonIWKernelDebugAdapter,
name: path.basename(doc.uri.toString()),
request: 'attach',
justMyCode: settings.debugJustMyCode,
__interactiveWindowNotebookUri: doc.uri.toString(),
__notebookUri: doc.uri.toString(),
// add a property to the config to know if the session is runByLine
__mode: KernelDebugMode.InteractiveWindow,
__cellIndex: cell.index
};
const opts: DebugSessionOptions = { suppressSaveBeforeStart: true };
return this.startDebuggingConfig(doc, config, opts);
await this.startDebuggingConfig(config, opts);
const dbgr = this.notebookToDebugger.get(doc);
if (!dbgr) {
traceError('Debugger not found, could not start debugging.');
return;
}
await (dbgr as IWDebugger).ready;
}

protected override async createDebugAdapterDescriptor(
session: DebugSession
): Promise<DebugAdapterDescriptor | undefined> {
const config = session.configuration;
assertIsDebugConfig(config);

const activeDoc = config.__interactiveWindowNotebookUri
? this.vscNotebook.notebookDocuments.find(
(doc) => doc.uri.toString() === config.__interactiveWindowNotebookUri
)
: this.vscNotebook.activeNotebookEditor?.notebook;
if (!activeDoc || typeof config.__cellIndex !== 'number') {
// This cannot happen.
protected async createDebugAdapterDescriptor(session: DebugSession): Promise<DebugAdapterDescriptor | undefined> {
const config = session.configuration as IInteractiveWindowDebugConfig;
assertIsInteractiveWindowDebugConfig(config);

const notebook = this.vscNotebook.notebookDocuments.find((doc) => doc.uri.toString() === config.__notebookUri);
if (!notebook || typeof config.__cellIndex !== 'number') {
traceError('Invalid debug session for debugging of IW using Jupyter Protocol');
return;
}

// TODO we apparently always have a kernel here, clean up typings
const kernel = await this.ensureKernelIsRunning(activeDoc);
const debug = this.getDebuggerByUri(activeDoc);
if (!debug) {
if (this.notebookInProgress.has(notebook)) {
traceInfo(`Cannot start debugging. Already debugging this notebook`);
return;
}

if (this.isDebugging(notebook)) {
traceInfo(`Cannot start debugging. Already debugging this notebook document. Toolbar should update`);
return;
}

const dbgr = new IWDebugger(notebook, config, session);
this.notebookToDebugger.set(notebook, dbgr);
try {
this.notebookInProgress.add(notebook);
return await this.doCreateDebugAdapterDescriptor(config, session, notebook, dbgr);
} finally {
this.notebookInProgress.delete(notebook);
}
}

private async doCreateDebugAdapterDescriptor(
config: IInteractiveWindowDebugConfig,
session: DebugSession,
notebook: NotebookDocument,
dbgr: IWDebugger
): Promise<DebugAdapterDescriptor | undefined> {
const kernel = await this.ensureKernelIsRunning(notebook);
if (!kernel?.session) {
this.appShell.showInformationMessage(DataScience.kernelWasNotStarted()).then(noop, noop);
return;
}
const adapter = new KernelDebugAdapter(
session,
debug.document,
notebook,
kernel.session,
kernel,
this.platform,
Expand All @@ -190,22 +174,21 @@ export class InteractiveWindowDebuggingManager

this.disposables.push(adapter.onDidEndSession(this.endSession.bind(this)));

// Wait till we're attached before resolving the session
const cell = activeDoc.cellAt(config.__cellIndex);
const cell = notebook.cellAt(config.__cellIndex);
const controller = new DebugCellController(adapter, cell, kernel!);
adapter.setDebuggingDelegate(controller);
adapter.setDebuggingDelegates([controller]);
controller.ready
.then(() => debug.resolve(session))
.then(() => dbgr.resolve())
.catch((ex) => console.error('Failed waiting for controller to be ready', ex));

this.trackDebugAdapter(activeDoc, adapter);
this.trackDebugAdapter(notebook, adapter);
return new DebugAdapterInlineImplementation(adapter);
}

// TODO: This will likely be needed for mapping breakpoints and such
public async updateSourceMaps(notebookEditor: NotebookEditor, hashes: IFileGeneratedCodes[]): Promise<void> {
// Make sure that we have an active debugging session at this point
let debugSession = await this.getDebugSession(notebookEditor.notebook);
let debugSession = this.getDebugSession(notebookEditor.notebook);
if (debugSession) {
traceInfoIfCI(`Sending debug request for source map`);
await Promise.all(
Expand Down
Loading

0 comments on commit 251bcc7

Please sign in to comment.