Skip to content
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

Implemented Restart and debug #10462

Merged
merged 8 commits into from Aug 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/debugger-extension/schema/main.json
Expand Up @@ -5,6 +5,13 @@
"jupyter.lab.setting-icon-label": "Debugger",
"jupyter.lab.menus": {
fcollonval marked this conversation as resolved.
Show resolved Hide resolved
"main": [
{
"id": "jp-mainmenu-kernel",
"items": [
{ "type": "separator", "rank": 1.2 },
{ "command": "debugger:restart-debug", "rank": 1.2 }
]
},
{
"id": "jp-mainmenu-view",
"items": [
Expand Down
50 changes: 46 additions & 4 deletions packages/debugger-extension/src/index.ts
Expand Up @@ -15,6 +15,7 @@ import {
ICommandPalette,
IThemeManager,
MainAreaWidget,
sessionContextDialogs,
WidgetTracker
} from '@jupyterlab/apputils';
import { IEditorServices } from '@jupyterlab/codeeditor';
Expand All @@ -31,7 +32,11 @@ import {
import { DocumentWidget } from '@jupyterlab/docregistry';
import { FileEditor, IEditorTracker } from '@jupyterlab/fileeditor';
import { ILoggerRegistry } from '@jupyterlab/logconsole';
import { INotebookTracker, NotebookPanel } from '@jupyterlab/notebook';
import {
INotebookTracker,
NotebookActions,
NotebookPanel
} from '@jupyterlab/notebook';
import {
standardRendererFactories as initialFactories,
RenderMimeRegistry
Expand Down Expand Up @@ -166,19 +171,49 @@ const files: JupyterFrontEndPlugin<void> = {
const notebooks: JupyterFrontEndPlugin<void> = {
id: '@jupyterlab/debugger-extension:notebooks',
autoStart: true,
requires: [IDebugger, INotebookTracker],
optional: [ILabShell],
requires: [IDebugger, INotebookTracker, ITranslator],
optional: [ILabShell, ICommandPalette],
activate: (
app: JupyterFrontEnd,
service: IDebugger,
notebookTracker: INotebookTracker,
labShell: ILabShell | null
translator: ITranslator,
labShell: ILabShell | null,
palette: ICommandPalette | null
) => {
const handler = new Debugger.Handler({
type: 'notebook',
shell: app.shell,
service
});

const trans = translator.load('jupyterlab');
app.commands.addCommand(Debugger.CommandIDs.restartDebug, {
label: trans.__('Restart Kernel and Debug…'),
caption: trans.__('Restart Kernel and Debug…'),
isEnabled: () => {
return service.isStarted;
},
execute: async () => {
const state = service.getDebuggerState();
console.log(state.cells);
const { context, content } = notebookTracker.currentWidget!;

await service.stop();
const restarted = await sessionContextDialogs!.restart(
context.sessionContext
);
if (restarted) {
await service.restoreDebuggerState(state);
await handler.updateWidget(
notebookTracker.currentWidget!,
notebookTracker.currentWidget!.sessionContext.session
);
await NotebookActions.runAll(content, context.sessionContext);
}
}
});

const updateHandlerAndCommands = async (
widget: NotebookPanel
): Promise<void> => {
Expand All @@ -199,6 +234,13 @@ const notebooks: JupyterFrontEndPlugin<void> = {
return;
}

if (palette) {
palette.addItem({
category: 'Notebook Operations',
command: Debugger.CommandIDs.restartDebug
});
}

notebookTracker.currentChanged.connect(
async (_, notebookPanel: NotebookPanel) => {
await updateHandlerAndCommands(notebookPanel);
Expand Down
9 changes: 9 additions & 0 deletions packages/debugger/src/config.ts
Expand Up @@ -64,6 +64,15 @@ export class DebuggerConfig implements IDebugger.IConfig {
this._fileParams.set(kernel, { kernel, prefix, suffix });
}

/**
* Gets the parameters used for the temp files (e.e. cells) for a kernel.
*
* @param kernel - The kernel name from current session.
*/
getTmpFileParams(kernel: string): IDebugger.IConfig.FileParams {
return this._fileParams.get(kernel)!;
}

private _fileParams = new Map<string, IDebugger.IConfig.FileParams>();
private _hashMethods = new Map<string, (code: string) => string>();
}
2 changes: 2 additions & 0 deletions packages/debugger/src/debugger.ts
Expand Up @@ -107,6 +107,8 @@ export namespace Debugger {
export const inspectVariable = 'debugger:inspect-variable';

export const evaluate = 'debugger:evaluate';

export const restartDebug = 'debugger:restart-debug';
}

/**
Expand Down
10 changes: 5 additions & 5 deletions packages/debugger/src/handler.ts
Expand Up @@ -105,11 +105,11 @@ export class DebuggerHandler {
delete this._kernelChangedHandlers[widget.id];
delete this._statusChangedHandlers[widget.id];
delete this._iopubMessageHandlers[widget.id];
return this._update(widget, connection);
return this.updateWidget(widget, connection);
}

const kernelChanged = (): void => {
void this._update(widget, connection);
void this.updateWidget(widget, connection);
};
const kernelChangedHandler = this._kernelChangedHandlers[widget.id];

Expand All @@ -125,7 +125,7 @@ export class DebuggerHandler {
): void => {
// FIXME-TRANS: Localizable?
if (status.endsWith('restarting')) {
void this._update(widget, connection);
void this.updateWidget(widget, connection);
}
};
const statusChangedHandler = this._statusChangedHandlers[widget.id];
Expand Down Expand Up @@ -156,7 +156,7 @@ export class DebuggerHandler {
connection.iopubMessage.connect(iopubMessage);
this._iopubMessageHandlers[widget.id] = iopubMessage;

return this._update(widget, connection);
return this.updateWidget(widget, connection);
}

/**
Expand Down Expand Up @@ -194,7 +194,7 @@ export class DebuggerHandler {
* @param widget The widget to update.
* @param connection The session connection.
*/
private async _update(
async updateWidget(
widget: DebuggerHandler.SessionWidget[DebuggerHandler.SessionType],
connection: Session.ISessionConnection | null
): Promise<void> {
Expand Down
83 changes: 72 additions & 11 deletions packages/debugger/src/service.ts
Expand Up @@ -303,17 +303,7 @@ export class DebuggerService implements IDebugger, IDisposable {
const { breakpoints } = this._model.breakpoints;
await this.stop();
await this.start();

// Re-send the breakpoints to the kernel and update the model.
for (const [source, points] of breakpoints) {
await this._setBreakpoints(
points
.filter(({ line }) => typeof line === 'number')
.map(({ line }) => ({ line: line! })),
source
);
}
this._model.breakpoints.restoreBreakpoints(breakpoints);
await this._restoreBreakpoints(breakpoints);
}

/**
Expand Down Expand Up @@ -471,6 +461,57 @@ export class DebuggerService implements IDebugger, IDisposable {
await this.session.sendRequest('configurationDone', {});
}

/**
* Get the debugger state
*
* @returns Debugger state
*/
getDebuggerState(): IDebugger.State {
const breakpoints = this._model.breakpoints.breakpoints;
let cells: string[] = [];
for (const id of breakpoints.keys()) {
const editorList = this._debuggerSources!.find({
Copy link
Member

@jtpio jtpio Aug 26, 2021

Choose a reason for hiding this comment

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

A follow-up for this could be to try to remove the non-null assertion, since IDebugger.ISources might not be available.

focus: false,
kernel: this.session?.connection?.kernel?.name ?? '',
path: this._session?.connection?.path ?? '',
source: id
});
const tmp_cells = editorList.map(e => e.model.value.text);
cells = cells.concat(tmp_cells);
}
return { cells, breakpoints };
}

/**
* Restore the debugger state
*
* @param state Debugger state
* @returns Whether the state has been restored successfully or not
*/
async restoreDebuggerState(state: IDebugger.State): Promise<boolean> {
await this.start();

for (const cell of state.cells) {
await this._dumpCell(cell);
}

const breakpoints = new Map<string, IDebugger.IBreakpoint[]>();
const kernel = this.session?.connection?.kernel?.name ?? '';
const { prefix, suffix } = this._config.getTmpFileParams(kernel);
for (const item of state.breakpoints) {
const [id, list] = item;
const unsuffixedId = id.substr(0, id.length - suffix.length);
const codeHash = unsuffixedId.substr(unsuffixedId.lastIndexOf('/') + 1);
const newId = prefix.concat(codeHash).concat(suffix);
breakpoints.set(newId, list);
}

await this._restoreBreakpoints(breakpoints);
const config = await this.session!.sendRequest('configurationDone', {});
await this.restoreState(false);
return config.success;
}

/**
* Clear the current model.
*/
Expand Down Expand Up @@ -750,6 +791,26 @@ export class DebuggerService implements IDebugger, IDisposable {
});
}

/**
* Re-send the breakpoints to the kernel and update the model.
*
* @param breakpoints The map of breakpoints to send
*/
private async _restoreBreakpoints(
breakpoints: Map<string, IDebugger.IBreakpoint[]>
): Promise<void> {
for (const [source, points] of breakpoints) {
console.log(source);
await this._setBreakpoints(
points
.filter(({ line }) => typeof line === 'number')
.map(({ line }) => ({ line: line! })),
source
);
}
this._model.breakpoints.restoreBreakpoints(breakpoints);
}

private _config: IDebugger.IConfig;
private _debuggerSources: IDebugger.ISources | null;
private _eventMessage = new Signal<IDebugger, IDebugger.ISession.Event>(this);
Expand Down
37 changes: 37 additions & 0 deletions packages/debugger/src/tokens.ts
Expand Up @@ -153,6 +153,21 @@ export interface IDebugger {
breakpoints: IDebugger.IBreakpoint[],
path?: string
): Promise<void>;

/**
* Get the debugger state
*
* @returns Debugger state
*/
getDebuggerState(): IDebugger.State;

/**
* Restore the debugger state
*
* @param state Debugger state
* @returns Whether the state has been restored successfully or not
*/
restoreDebuggerState(state: IDebugger.State): Promise<boolean>;
}

/**
Expand Down Expand Up @@ -184,6 +199,21 @@ export namespace IDebugger {
*/
export interface IBreakpoint extends DebugProtocol.Breakpoint {}

/*
* The state of the debugger, used for restoring a debugging session
* after restarting the kernel.
*/
export type State = {
/**
* List of cells to dump after the kernel has restarted
*/
cells: string[];
/**
* Map of breakpoints to send back to the kernel after it has restarted
*/
breakpoints: Map<string, IDebugger.IBreakpoint[]>;
};

/**
* Debugger file and hashing configuration.
*/
Expand All @@ -209,6 +239,13 @@ export namespace IDebugger {
* @param params - Temporary file prefix and suffix for a kernel.
*/
setTmpFileParams(params: IConfig.FileParams): void;

/**
* Gets the parameters used for the temp files (e.e. cells) for a kernel.
*
* @param kernel - The kernel name from current session.
*/
getTmpFileParams(kernel: string): IConfig.FileParams;
}

/**
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.