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

prevent debugpy from getting stuck #7612

Merged
merged 16 commits into from
Sep 24, 2021
9 changes: 5 additions & 4 deletions src/client/debugger/jupyter/debugControllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,16 @@ export class RunByLineController implements IDebuggingDelegate {
public continue(): void {
if (typeof this.lastPausedThreadId !== 'number') {
traceVerbose(`No paused thread, can't do RBL`);
this.stop();
Copy link
Member

Choose a reason for hiding this comment

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

With this, if I click the RBL button and then click it again before the first pause, it disconnects. That's not that bad of a problem but could be annoying, especially since it will then run the whole cell, even if I didn't want/expect it to.

I guess this is working around a case where we attach to debugpy and it doesn't respond, but if we have fixed the issue on our end of trying to attach twice at a time, then I don't understand how else we get into that case

return;
}

void this.debugAdapter.stepIn(this.lastPausedThreadId);
}

public stop(): void {
// When debugpy gets stuck, running a cell fixes it and allows us to start another debugging session
void this.kernel.executeHidden('pass');
Copy link
Author

Choose a reason for hiding this comment

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

I'll file a bug in ipykernel, and mention it here, but I don't see what else we can do

Copy link
Member

Choose a reason for hiding this comment

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

When debugpy gets stuck

It would be good to explain how that happens

this.debugAdapter.disconnect();
}

Expand Down Expand Up @@ -173,10 +176,8 @@ async function cellDebugSetup(
): Promise<void> {
// remove this if when https://github.com/microsoft/debugpy/issues/706 is fixed and ipykernel ships it
// executing this code restarts debugpy and fixes https://github.com/microsoft/vscode-jupyter/issues/7251
if (kernel) {
const code = 'import debugpy\ndebugpy.debug_this_thread()';
await kernel.executeHidden(code);
}
const code = 'import debugpy\ndebugpy.debug_this_thread()';
await kernel.executeHidden(code);

await debugAdapter.dumpCell(debugCell.index);
}
105 changes: 69 additions & 36 deletions src/client/debugger/jupyter/debuggingManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import {
ProgressLocation,
DebugAdapterDescriptor,
Event,
EventEmitter
EventEmitter,
NotebookEditor
} from 'vscode';
import * as path from 'path';
import { IKernel, IKernelProvider } from '../../datascience/jupyter/kernels/types';
Expand Down Expand Up @@ -51,6 +52,7 @@ export class DebuggingManager implements IExtensionSingleActivationService, IDeb
private notebookToDebugger = new Map<NotebookDocument, Debugger>();
private notebookToDebugAdapter = new Map<NotebookDocument, KernelDebugAdapter>();
private notebookToRunByLineController = new Map<NotebookDocument, RunByLineController>();
private notebookInProgress = new Set<NotebookDocument>();
private cache = new Map<PythonEnvironment, boolean>();
private readonly disposables: IDisposable[] = [];
private _doneDebugging = new EventEmitter<void>();
Expand Down Expand Up @@ -81,9 +83,9 @@ export class DebuggingManager implements IExtensionSingleActivationService, IDeb
workspace.onDidCloseNotebookDocument(async (document) => {
const dbg = this.notebookToDebugger.get(document);
if (dbg) {
await dbg.stop();
this.updateToolbar(false);
this.updateCellToolbar(false);
await dbg.stop();
}
}),

Expand All @@ -94,16 +96,7 @@ export class DebuggingManager implements IExtensionSingleActivationService, IDeb

this.commandManager.registerCommand(DSCommands.DebugNotebook, async () => {
const editor = this.vscNotebook.activeNotebookEditor;
if (editor) {
if (await this.checkForIpykernel6(editor.document)) {
this.updateToolbar(true);
void this.startDebugging(editor.document);
} else {
void this.installIpykernel6();
}
} else {
void this.appShell.showErrorMessage(DataScience.noNotebookToDebug());
}
await this.tryToStartDebugging(KernelDebugMode.Everything, editor);
}),

this.commandManager.registerCommand(DSCommands.RunByLine, async (cell: NotebookCell | undefined) => {
Expand All @@ -120,17 +113,7 @@ export class DebuggingManager implements IExtensionSingleActivationService, IDeb
return;
}

if (editor) {
if (await this.checkForIpykernel6(editor.document, DataScience.startingRunByLine())) {
this.updateToolbar(true);
this.updateCellToolbar(true);
await this.startDebuggingCell(editor.document, KernelDebugMode.RunByLine, cell);
} else {
void this.installIpykernel6();
}
} else {
void this.appShell.showErrorMessage(DataScience.noNotebookToDebug());
}
await this.tryToStartDebugging(KernelDebugMode.RunByLine, editor, cell);
}),

this.commandManager.registerCommand(DSCommands.RunByLineNext, (cell: NotebookCell | undefined) => {
Expand All @@ -146,6 +129,10 @@ export class DebuggingManager implements IExtensionSingleActivationService, IDeb
return;
}

if (this.notebookInProgress.has(cell.notebook)) {
return;
}

const controller = this.notebookToRunByLineController.get(cell.notebook);
if (controller && controller.debugCell.document.uri.toString() === cell.document.uri.toString()) {
controller.continue();
Expand All @@ -157,7 +144,9 @@ export class DebuggingManager implements IExtensionSingleActivationService, IDeb
if (editor) {
const controller = this.notebookToRunByLineController.get(editor.document);
if (controller) {
sendTelemetryEvent(DebuggingTelemetry.endedSession, undefined, { reason: 'withKeybinding' });
sendTelemetryEvent(DebuggingTelemetry.endedSession, undefined, {
reason: 'withKeybinding'
});
controller.stop();
}
}
Expand All @@ -177,16 +166,7 @@ export class DebuggingManager implements IExtensionSingleActivationService, IDeb
return;
}

if (editor) {
if (await this.checkForIpykernel6(editor.document)) {
this.updateToolbar(true);
void this.startDebuggingCell(editor.document, KernelDebugMode.Cell, cell);
} else {
void this.installIpykernel6();
}
} else {
void this.appShell.showErrorMessage(DataScience.noNotebookToDebug());
}
await this.tryToStartDebugging(KernelDebugMode.Cell, editor, cell);
})
);
}
Expand Down Expand Up @@ -232,6 +212,59 @@ export class DebuggingManager implements IExtensionSingleActivationService, IDeb
this.runByLineInProgress.set(runningByLine).ignoreErrors();
}

private async tryToStartDebugging(mode: KernelDebugMode, editor?: NotebookEditor, cell?: NotebookCell) {
if (!editor) {
void this.appShell.showErrorMessage(DataScience.noNotebookToDebug());
return;
}

if (this.notebookInProgress.has(editor.document)) {
return;
}

if (this.isDebugging(editor.document)) {
this.updateToolbar(true);
Copy link
Member

Choose a reason for hiding this comment

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

Why do you have to do this here? If you are already debugging, shouldn't it already be in this state?

Copy link
Author

Choose a reason for hiding this comment

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

it should, but that's the buggy state you get trapped in.

this would correct the context keys

Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure i like this, feels like we're re-executing some code & that seems to fix a problem (but we don't know why).
I would like us to fix the root cause, why is this required in the first place

Copy link
Author

Choose a reason for hiding this comment

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

The root cause is out of our hands, its debugpy freezing inside ipykernel.

And I disagree, we should be trying to fix root causes, but since the root cause is not on our code, we should also try to fix it if it happens.

Copy link
Contributor

@DonJayamanne DonJayamanne Sep 23, 2021

Choose a reason for hiding this comment

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

its debugpy freezing inside ipykernel.

If that's the cause, how can updating the toolbar fix anything in the kernel (i.e. updating a UI will not address anything in ipykernel freezing).
I think Rob and I would like to know why we need lines 225-230 when we're already debugging,
I dont' think this has to do with ipykernel state.
Unless I'm missing something here.

Copy link
Member

Choose a reason for hiding this comment

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

It seems like the root cause is us having the context keys out of sync with the RBL lifecycle. Your latest commit is a good one towards fixing that, I think previously we reset the context keys too early, before debugging actually stopped.

Fundamentally, at this point in the code, two things are out of sync. It's hard to point at one and say this one is always right. It should be possible to write the code so that they are always in sync.

if (mode === KernelDebugMode.RunByLine) {
this.updateCellToolbar(true);
}
return;
}

try {
this.notebookInProgress.add(editor.document);
if (
await this.checkForIpykernel6(
editor.document,
mode === KernelDebugMode.RunByLine ? DataScience.startingRunByLine() : undefined
)
) {
switch (mode) {
case KernelDebugMode.Everything:
await this.startDebugging(editor.document);
this.updateToolbar(true);
break;
case KernelDebugMode.Cell:
if (cell) {
await this.startDebuggingCell(editor.document, KernelDebugMode.Cell, cell);
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm sorry, but I'm not convinced we need awaits here
The old code didn't have awaits, feels like there's something we're not understanding here
I.e. why do we need an await or what happens if there is no await?

Would like to have some comments why we must await, e.g. tomorrow what would stop working if we removed it.

Copy link
Author

Choose a reason for hiding this comment

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

it helps with keeping the context keys aligned with the status of the debugger. My most recent commit puts them after the awaits.

this.updateToolbar(true);
}
break;
case KernelDebugMode.RunByLine:
if (cell) {
await this.startDebuggingCell(editor.document, KernelDebugMode.RunByLine, cell);
this.updateToolbar(true);
this.updateCellToolbar(true);
}
break;
}
} else {
void this.installIpykernel6();
}
} finally {
this.notebookInProgress.delete(editor.document);
}
}

private async startDebuggingCell(
doc: NotebookDocument,
mode: KernelDebugMode.Cell | KernelDebugMode.RunByLine,
Expand Down Expand Up @@ -286,8 +319,6 @@ export class DebuggingManager implements IExtensionSingleActivationService, IDeb
}

private async endSession(session: DebugSession) {
void this.updateToolbar(false);
void this.updateCellToolbar(false);
this._doneDebugging.fire();
for (const [doc, dbg] of this.notebookToDebugger.entries()) {
if (dbg && session.id === (await dbg.session).id) {
Expand All @@ -296,6 +327,8 @@ export class DebuggingManager implements IExtensionSingleActivationService, IDeb
break;
}
}
this.updateToolbar(false);
this.updateCellToolbar(false);
}

private async createDebugAdapterDescriptor(session: DebugSession): Promise<DebugAdapterDescriptor | undefined> {
Expand Down