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
Merged

prevent debugpy from getting stuck #7612

merged 16 commits into from
Sep 24, 2021

Conversation

DavidKutu
Copy link

@DavidKutu DavidKutu commented Sep 21, 2021

-prevent debugpy from getting stuck by running code
-if we can't continue in RBL, disconnect

For #7586, #7584 and parts 1 and 7 of #7218

  • Pull request represents a single change (i.e. not fixing disparate/unrelated things in a single PR).
  • Title summarizes what is changing.
  • Has a news entry file (remember to thank yourself!).
  • Appropriate comments and documentation strings in the code.
  • Has sufficient logging.
  • Has telemetry for enhancements.
  • Unit tests & system/integration tests are added/updated.
  • Test plan is updated as appropriate.
  • package-lock.json has been regenerated by running npm install (if dependencies have changed).

by running code
-throttle all debugging functions
-if we can't continue in RBL, disconnect
-have run and debug button respect
'notebook.consolidatedRunButton' setting
@DavidKutu DavidKutu requested a review from a team as a code owner September 21, 2021 23:38
package.json Outdated
}
],
"notebook/cell/execute": [
{
"command": "jupyter.runAndDebugCell",
"when": "notebookType == jupyter-notebook && jupyter.ispythonnotebook && notebookCellType == code && isWorkspaceTrusted && !jupyter.notebookeditor.runByLineInProgress"
"when": "notebookType == jupyter-notebook && jupyter.ispythonnotebook && notebookCellType == code && isWorkspaceTrusted && config.notebook.consolidatedRunButton"
Copy link
Member

Choose a reason for hiding this comment

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

Why this change? It always goes in that menu regardless of the setting by design. Probably will rename the setting or rethink this but I don't want it on the cell toolbar.

Copy link
Author

Choose a reason for hiding this comment

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

why not?

I moved it because some people will probably want the setting to get rid of the drop down on the run button

Copy link
Member

Choose a reason for hiding this comment

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

I'm not going to add another button to every cell toolbar unless masses of angry users kick down my door


this.commandManager.registerCommand(
DSCommands.RunByLineNext,
throttle((cell: NotebookCell | undefined) => {
Copy link
Member

Choose a reason for hiding this comment

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

Why throttle this? When I am doing normal javascript debugging, I can step as fast as I want. I can even hold down F10 to step really fast. The same should work here.

Copy link
Author

Choose a reason for hiding this comment

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

Because that seems to be the issue, clicking fast enough causes debugpy to freeze.

The changes to the controller are what gets it out of the situation (finishes that frozen session and un freezes debugpy).
The throttling is meant to prevent the situation.

Its removable, but I'd rather have at least some.

Copy link
Member

Choose a reason for hiding this comment

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

It's hard to believe that debugpy just can't step fast. I can debug a normal python script and step over 50 lines in a couple seconds

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree, I think we need to identify the root cause, this feels like a bandaid

Copy link
Contributor

Choose a reason for hiding this comment

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

I think they problem is not because it happens fast, it's because we start multiple debug sessions instead of checkingbif we have already started one

cell = editor?.document.cellAt(range.start);
this.commandManager.registerCommand(
DSCommands.RunAndDebugCell,
throttle(async (cell: NotebookCell | undefined) => {
Copy link
Member

Choose a reason for hiding this comment

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

I worry it will feel broken if this command sometimes doesn't work if stuff happens too quickly

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah throttle of 1 second seems like way too much. Maybe like 100ms

@codecov-commenter
Copy link

codecov-commenter commented Sep 21, 2021

Codecov Report

Merging #7612 (58237c8) into main (6fb16b4) will increase coverage by 2%.
The diff coverage is 37%.

❗ Current head 58237c8 differs from pull request most recent head 8c53b34. Consider uploading reports for the commit 8c53b34 to get more accurate results

@@          Coverage Diff           @@
##            main   #7612    +/-   ##
======================================
+ Coverage     66%     68%    +2%     
======================================
  Files        365     363     -2     
  Lines      22660   22550   -110     
  Branches    3445    3429    -16     
======================================
+ Hits       15111   15516   +405     
+ Misses      6263    5704   -559     
- Partials    1286    1330    +44     
Impacted Files Coverage Δ
src/client/debugger/jupyter/debuggingManager.ts 61% <35%> (+35%) ⬆️
src/client/debugger/jupyter/debugControllers.ts 71% <50%> (+61%) ⬆️
...ient/datascience/jupyter/kernels/kernelSelector.ts 57% <0%> (-24%) ⬇️
...lient/datascience/notebookAndInteractiveTracker.ts 76% <0%> (-8%) ⬇️
...ent/datascience/jupyter/jupyterNotebookProvider.ts 70% <0%> (-5%) ⬇️
...rc/client/datascience/notebookStorage/baseModel.ts 70% <0%> (-4%) ⬇️
...ient/datascience/raw-kernel/rawNotebookProvider.ts 66% <0%> (-4%) ⬇️
src/client/datascience/telemetry/telemetry.ts 72% <0%> (-3%) ⬇️
...datascience/interactive-common/notebookProvider.ts 79% <0%> (-3%) ⬇️
src/client/datascience/webviews/webviewHost.ts 77% <0%> (-2%) ⬇️
... and 55 more

return;
}

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

public stop(): void {
void executeDebugPyFix(this.kernel);
Copy link
Contributor

@DonJayamanne DonJayamanne Sep 22, 2021

Choose a reason for hiding this comment

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

Some comments would be good here, didn't make sense why this is required
Why do we need to setup the debugger again when it's already running

Copy link
Member

Choose a reason for hiding this comment

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

I'm also curious about this, we execute this code when we start, why do we have to execute it when we stop?

await debugAdapter.dumpCell(debugCell.index);
}

async function executeDebugPyFix(kernel: IKernel) {
// 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) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This is always not undefined

this.commandManager.registerCommand(
DSCommands.DebugNotebook,
throttle(async () => {
const editor = this.vscNotebook.activeNotebookEditor;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the solution is, if we're already debugging or have started then return from here.
I'd if in progress then get out, mashed three throttle unnecessary.

this.updateToolbar(true);
this.updateCellToolbar(true);
await this.startDebuggingCell(editor.document, KernelDebugMode.RunByLine, cell);
if (editor) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment as earlier

Copy link
Contributor

@DonJayamanne DonJayamanne left a comment

Choose a reason for hiding this comment

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

We don't need the throttle

David Kutugata added 2 commits September 22, 2021 13:50
-check if we're already debugging
-change code that we execute to get unfrozen
@@ -95,6 +95,9 @@ export class DebuggingManager implements IExtensionSingleActivationService, IDeb
this.commandManager.registerCommand(DSCommands.DebugNotebook, async () => {
const editor = this.vscNotebook.activeNotebookEditor;
if (editor) {
if (this.notebookToDebugger.has(editor.document)) {
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 this is going to be enough.
If user clicks this button while checkforIpykernel6 is still running, then yo'ull end up with duplicate sessions.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think you can move this entire if condition from line 98 to 109 into a seprate fucntion, this has been repeated in 3 places, and then you can easily check if that method has already been called once before if yes, then return.

@@ -232,6 +212,50 @@ export class DebuggingManager implements IExtensionSingleActivationService, IDeb
this.runByLineInProgress.set(runningByLine).ignoreErrors();
}

private async tryToStartDebugging(mode: KernelDebugMode, editor?: NotebookEditor, cell?: NotebookCell) {
if (editor) {
if (this.notebookInProgress.has(editor.document)) {
Copy link
Member

Choose a reason for hiding this comment

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

This is checking the case where debugging is starting, but doesn't it still need to check this.notebookToDebugger.has?

if (cell) {
this.updateToolbar(true);
this.updateCellToolbar(true);
await this.startDebuggingCell(editor.document, KernelDebugMode.RunByLine, cell);
Copy link
Contributor

Choose a reason for hiding this comment

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

in line 233 we're not awaiting, but here we're awaiting.
Confused why that's the case, comments would help.

Copy link
Author

Choose a reason for hiding this comment

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

It'll be good to await them all

case KernelDebugMode.Cell:
if (cell) {
this.updateToolbar(true);
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.

@roblourens
Copy link
Member

It took longer (this is after many attempts) but I can still break it

Sep-22-2021 18-37-12

and context keys shows its not
@DavidKutu
Copy link
Author

@roblourens, the latest commit should catch your scenario

}

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.

Copy link
Contributor

@DonJayamanne DonJayamanne left a comment

Choose a reason for hiding this comment

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

.

Copy link
Contributor

@DonJayamanne DonJayamanne left a comment

Choose a reason for hiding this comment

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

Removing the unblocking request.

Copy link
Contributor

@DonJayamanne DonJayamanne left a comment

Choose a reason for hiding this comment

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

.

break;
case KernelDebugMode.Cell:
if (cell) {
await this.startDebuggingCell(editor.document, KernelDebugMode.Cell, cell);
this.updateToolbar(true);
if (this.isDebugging(editor.document)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Under what circustances will this if condition be false, i don't think this can ever be false, its aleways true, it should be true, else the previous await should throw an error.
Having this if makes it look as though the previous code can run to completion without errors & not start the debugger (which i think is impossible)

Copy link
Author

Choose a reason for hiding this comment

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

I guess that's true, it just feels safer.
I'll remove them

}
break;
case KernelDebugMode.RunByLine:
if (cell) {
await this.startDebuggingCell(editor.document, KernelDebugMode.RunByLine, cell);
this.updateToolbar(true);
this.updateCellToolbar(true);
if (this.isDebugging(editor.document)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

As earlier, i don't think this if condition is required.

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

@DavidKutu DavidKutu merged commit c6d0357 into main Sep 24, 2021
@DavidKutu DavidKutu deleted the david/RBLDoesntStart branch September 24, 2021 00:18
@@ -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

@roblourens
Copy link
Member

Played with it for awhile and was not able to totally break it, nice!

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.

5 participants