-
Notifications
You must be signed in to change notification settings - Fork 298
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
Conversation
by running code -throttle all debugging functions -if we can't continue in RBL, disconnect -have run and debug button respect 'notebook.consolidatedRunButton' setting
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" |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) => { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) => { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 Report
@@ 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
|
return; | ||
} | ||
|
||
void this.debugAdapter.stepIn(this.lastPausedThreadId); | ||
} | ||
|
||
public stop(): void { | ||
void executeDebugPyFix(this.kernel); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment as earlier
There was a problem hiding this 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
-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)) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
a debugging session
@@ -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)) { |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
and context keys shows its not
@roblourens, the latest commit should catch your scenario |
} | ||
|
||
if (this.isDebugging(editor.document)) { | ||
this.updateToolbar(true); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.
There was a problem hiding this 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.
There was a problem hiding this 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)) { |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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)) { |
There was a problem hiding this comment.
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'); |
There was a problem hiding this comment.
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
@@ -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(); |
There was a problem hiding this comment.
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
Played with it for awhile and was not able to totally break it, nice! |
-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
package-lock.json
has been regenerated by runningnpm install
(if dependencies have changed).