-
Notifications
You must be signed in to change notification settings - Fork 30k
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
Notebook debugging/"run by line" #126431
Comments
@roblourens @DavidKutu some comments:
BTW, my Nodebook sample shows how to implement notebook debugging functionality based on existing VS Code debugger extensions. It is simpler than the vscode-simple-jupyter-notebook because it does not rely on the DAP implementation of the xeus kernel and therefore has no DAP forwarding. |
The I don't think we plan to give cells a real ID, but I agree with your points about that problem. |
I don't think .NET & Julia support debugging in Jupyter today, hence if debugging were to be implemented, i don't think it would be implemented through the jupyter protocol, as both have their corresponding VS Code extensions that support notebooks and do not go through Jupyter (i.e Jupyter protocol isn't involved in execution today in .NET/Julia/R code execution). @davidanthoff /cc |
Yes, that is correct for Julia: there is no debugging support in the IJulia kernel that is used by JupyterLab. But we do have a DAP implementation in the Julia VS Code extension, so my hope would be that we can just use that for the Jupyter notebook scenario that uses the Julia extension to run code. I think my preference would be to try to get the full debugger working for Julia inside Jupyter notebooks, rather than spend time on a simplified "run by line" feature. There really wouldn't be any reason to use the latter if we have a full DAP/proper debugger experience implemented, right? |
Run by line was created first because of the feedback we got from data scientists. They didn't want a debugger, didn't see the reason for one and were not interested in using it. They just did printf debugging, why would they need something else? Run by line was our kind of debugger lite scenario so that these users would feel more comfortable with it. Right now we have about 40,000 people using it per month. I'm sure some of them would prefer full debugging, but we think run by line removes the worries about having to 'learn a debugger'. |
Maybe the question of UI and underlying implementation is distinct? I could imagine something where the button "run line by line" just starts the debugger? And maybe there is some UI for "next line" that is really hooked to "run next statement"? It just feels that one should easily be able to implement a line-by-line UI feature if there is an existing DAP implementation for a language, without adding another API/execution model or something like that. Also, just out of curiosity: this feature really would have to be "run statement by statement", right? Is there some API to detect the extend of the next statement? At least in Julia executing line-by-line doesn't make that much sense because there are many multi-line statements that would just be syntax errors if one tried to execute them line-by-line. We do have the feature to detect the next syntactically complete statement in our language server, but it is a custom protocol extension and we would need some more API to surface that to somewhere else, right? |
Yes that's right. That's what it is today.
No it isn't. Is built on top of the existing dap.
Yes technically, that's what how we implemented this for python in our webview based notebook. |
Run by line currently does a step into on every 'F10' by the user. It then checks the frame when the stop event comes in to see where the step into landed. If in the same cell, it stops the UI. If not in the same cell, it executes a step out command. This is repeated for every step by the user until it lands outside of the current cell (or meaning the frame that called the cell to begin with) |
@DonJayamanne Ah, cool! So essentially from our end, we "just" need to get the DAP story going with the notebooks :) I like that, because we of course want to do that in any case. @rchiodo Ah, so it actually will descend into a nested function calls step-by-step as long as those functions are all defined in one cell? What about things like higher order functions? Say you have this Julia code: function foo(i)
return i*2
end
x = map(foo, sourceArray) If you run a step-into command on the line with I think I'm still wondering, though, whether all of this just primarily introduces more choices, and more special cases, and whether one couldn't just make the normal debug experience "non scary"? But I think I should probably just try out the existing line-by-line to get a sense how that works :) |
For your map case (at least with the debugger for python code), it won't stop in map cause that's considered 'system' code. There's a 'just my code' feature of the DAP that prevents stepping through stuff that isn't your own code. But yes it doesn't work for cells that jump through intermediate functions that you defined that are not in the same cell. There's a lot of cases where Run by line doesn't really 'work'. We argued about a lot of them :) The behavior we have right now is where it ended up. There's also talk of run by line auto running to where the cursor is. Cause that's where the user is currently investigating. Why make them step all the way through the code above the cursor. |
@davidanthoff is Julia thinking about supporting debugging in JupyterLab? We thought if kernels supported debugging there, they'd want to use the same code in VS code to light up debugging. (meaning the kernel would expose the DAP instead of an extension) |
There are no concrete plans to add debugging to JupyterLab for Julia that I'm aware of. I think if someone wanted to do that, they would essentially reuse the DAP implementation that we have for the Julia VS Code extension and integrate that into IJulia.jl. The two peole in Julialand that are most familiar with this DAP stuff are @pfitzseb and myself, and at least I am focused on the Julia VS Code extension and getting the Jupyter story to work there, without a dependency on kernelspec installations :) My suspicion is that the same is true for @pfitzseb, given that he is also a core dev of the Julia VS Code extension. So I think for the Jupyter notebook story inside VS Code ideally we would shoot for a debugger implementation that really only needs VS Code, Julia extension and Jupyter extension and Julia itself installed, with no other setup required and no other config files like kernelspecs needed. |
@davidanthoff I think this is also possible if you were to give us an API that we could send messages to. We don't have to start a kernel (or need a kernelspec.json) in order to do 'jupyter' type stuff. I believe @claudiaregio has sent you a document describing how we want to enable all the stuff in the jupyter extension UI for all kernels (or notebook controllers as VS code calls them). To do so, we don't need a kernel on disk, we just need some object we can call requestExecute, or requestDebug on. This would mean you wouldn't have to duplicate the work we're doing to get debugging to work (it's not a lot but you can see some of it here: https://github.com/microsoft/vscode-jupyter/commits/david/debuging). Or the alternative is that all notebook controllers have to reimplement the work that David has done (with the benefit of not having to write a method called 'requestDebug') |
I still haven't decided on what the actual UX model for full debugging would look like. There are basically three options that have been proposed/discussed
My personal preference is 3 for being the easiest to use. 1 has also been popularly suggested, but I am not crazy about extra buttons/shortcuts, or about having to remember to do something different, when keyboard shortcuts for running cells are baked into a pro user's muscle memory. 2 might actually be a compromise, but I don't think anyone was excited about it. Then there is the question of UX for run-by-line. Currently this shows up in a button in the cell toolbar. We could:
|
@roblourens I like option 3 too and I assume that you would run with debugging enabled only if at least a single breakpoint exists in a notebook, right? Another thing to consider: running into an exception might result in a different (UX) experience in debug mode than in non-debug mode. |
In option 3, yes I would only run in debug mode when there is a breakpoint in the cell or document because there is a perf hit to running with the debugger enabled.
I forgot to call that out, thanks. Yes the "break on exception" behavior is weird for option 3 because we wouldn't really have a way to enable that when you don't have a breakpoint set...
That's also a good point. If I set a breakpoint in a .py file, and run a cell that calls into that file, I don't know how I will know to enable the debugger.
We just changed to showing it all the time (we removed the folding icon margin to do that)
It's a good shortcut, I'd be worried about stealing it from the debug viewlet though. Then I can't start a debug session normally when a notebook is open. https://github.com/microsoft/vscode-docs/blob/vnext/release-notes/images/1_59/run-by-line.gif |
For 3, I think we have to keep in mind that even if debugging Python doesn't incur a perf hit (which based on my limited understanding, it does), other languages we want to support may very well have this problem (update: missed @pfitzseb comment that Julia does incur a perf hit for sure). That makes this option as a generalized model not so great. Second, like @weinand and @pfitzseb, I'm also wondering about confusion as to what to expect if there are breakpoints elsewhere in the notebook. #%%
def foo(a):
🔴 print(a*2)
#%%
a = 10
foo(a) A debugger savvy user would likely expect if you debug the 2nd cell, that the breakpoint would hit when you step over the call to Finally, if we were to keep the gutter open for breakpoints all the time, I'm 100% sure that we will have people accidentally setting them and not having any clue as to what they just did and not being happy about it. Depending on the behavior after that, even more confusion could occur. Therefore, while I like the idea, I don't really see how we can pull it off in a way that won't be problematic for many. So while option 2 doesn't feel like nirvana it does offer a way to deal with the above disadvantages in a cleaner way.
|
Yes, there is a perf hit for Python It would make sense to say that we run in debug mode when any cell in the notebook has a breakpoint, that's also an option.
I haven't heard of this being a problem in editors in general. If we want to want to only show breakpoints in debug mode, then 2 is probably the only option that can do that. |
I like option 3, but there's a lot of scenarios that people have brought up that doesn't make it ideal. But I'd like to put it in as an option, with a setting people that don't care about performance can use it all the time. As to when does option 3 activate, there's no right answer here. Its either compromise the full debugger or take the perf hit. I say we have start the debug session when we start the kernel, and we hit all breakpoints, even in other files. But we do it with a setting that is off by default, and if its off, we rely on option 2. |
I think I'm also not too keen on 3. The performance issue @pfitzseb mentioned is the big one for Julia, but I also think there are some UI challenges that would be hard to overcome or make that not an ideal experience:
I initially thought 1 might be nice, but I think in the end I agree that the worry of too many buttons, too many permutations of different run modes with debug mode etc. also doesn't make that a very attractive option. So, here is a vote for option 2 :) There is another reason that I think makes 2 a good option: in the Julia extension we also have as another major way to run code the Julia REPL. It is exposed as a terminal in VS Code, and users can run code there either by just typing it in, or sending code from a text editor to the REPL . At the moment we expose debugging in the REPL via macros: if you prefix your code with either |
Sorry I'm changing my vote. Thinking about the user scenario (not really worrying about impl details), debugging a cell is just like debugging a python file (or a c# project, or a exe). I would expect to hit a new button. Which I believe is option 1. So there is a single command - 'Debug Cell' (like we have for the interactive window) that just starts the debugger. Option 3 sounds cool, but is confusing from a user point of view. When am I debugging? When am I not? Option 2 is weird because it introduces some sort of mode that nothing else has. Essentially it feels like we're exposing an implementation detail (that we have to attach to a kernel). Option 1 sounds the most like other scenarios. I'm changing my vote to # 1. |
Not sure how popular debugging has become in the Jupyter Lab world, but because it is a large ecosystem, there are now potentially a lot of notebook users (even those who are fully comfortable with debugging) who will look for and feel at home with a separate mode. We should at least keep that in mind. Oh, was gonna mention too that I'll bet that Juptyer Lab went through many of the very same conversations as they were defining what to do for debugging (maybe there's another public discussion about it?) Overall, I think the main decision so far is focusing around at least not having debugging on all the time without the user having any obvious control. |
My bet is the opposite. The implementation details crept into the UI. They knew they had to attach all the time. The easiest way to handle that is just have a button that essentially attaches. |
Oh, and to @roblourens questioning about users accidentally hitting the gutter and setting breakpoints in regular editors. First, I really don't know that this is a problem worth worrying about. It's just a guess on my part. :) But the reason I'm guessing this is because notebooks are much more "clickable" than regular editors. There's widgets all over the place. In addition, the click area to make a cell editable doesn't really correspond with anything in a regular editor since you're always in edit mode. Finally, the area tends to be much smaller in general, so there's just simply more chance that this could occur. |
Is toggling the same as attach? While it may be possible for Python, it isn't necessarily true for other runtimes/languages. Some kernels will likely require starting them under a debug mode. So having a debug button that's readily available on a cell may not really be viable for all languages. I.e. it would be weird, if not a non-starter, if after clicking it, the user is prompted with "Oh, you have to restart your kernel to debug this cell". It's also weird if it gets grayed out or disappears. A global switch for this type of kernel speaks to the likely need to be starting multiple sessions while debugging your notebooks. I.e. I don't necessarily want to always have to remember to run the first cell under the debugger in case I want to debug a cell later. I suppose the same could be said for run-by-line though... It's just that run-by-line doesn't carry the same weight or expectations that debugging does. |
So you're suggesting that it's possible that option 2 will cause the entire kernel to restart? Instead of having the kernel support attach? I don't think that case is necessary/likely. And optimizing for that UI flow is a bad choice. We should just say all kernels require attach support. The option 2 button is already implemented that way today.
|
My two cents: introducing new buttons and keyboard shortcuts for debugging is not popular but that's not what we can avoid. When there are breakpoints in the cell editor, we still need a way to just run the cell without entering debugging mode (hitting breakpoints), either through a separate button, or a new keyboard shortcut. Thus I vote for option 1. Either option 2 and option 3 will degrade the experience of this scenario. My personal preference is even beyond this: |
At least for Julia the attach option is not a problem, i.e. there wouldn't be a need to restart the kernel to start debugging. Could option 1 and 2 both be implemented? They don't necessarily strike me as mutually exclusive. There could be a button "debug this cell" that attaches the debugger and runs that cell and then automatically detaches the debugger from the kernel, and there could also be an option to attach the debugger for longer to a kernel, i.e. option 2. I kind of think that option 1 is great for the simplest scenario where I want to debug one cell, but what about "run all cells above", "run all cells below", "run cell and insert a new cell below", "run cell and don't insert a new cell below" etc. Do they all get a debug version as well? Obviously they can't really, but I can easily see a scenario where I would want to use those with debugging, and then option 2 starts to look nice. In the Julia extension we have this view of all the running kernels: We could pretty easily add a button at the location where the red arrow is that attaches a debugger to that given kernel. I think that in combination with say a "Debug this cell" button might be a pretty nice UI. You have an easy option right in your face, and a slightly more advanced version also available. |
One more random observation: I think what really tripped me with the "attach debugger to this notebook" examples that I've seen previously is that they used an icon in a place where normally the "run this file" button appears, and the icon also looked a bit like "run something". I think the problem there is if the command to attach the debugger to the notebook looks or feels similar to something that "runs" something, because it of course doesn't. So maybe just making sure the icon looks like "attach" and not like "run", and is not in the same location where we normally find "run" buttons would help. |
This seems reasonable, provided we have consciously made that decision and statement. |
The way we are doing this right now, we are implementing everything in the vscode-jupyter extension, contributing the UI from the extension, and are only concerned about the python kernels that this extension returns. I'm not really keen on making vscode opinionated about how notebook debugging works right now. Maybe later we could save extensions some effort by helping them hook into a common UI, when we have a more clear vision. I think the clear path forward for now is just implementing option 1 as an experiment to see how it feels. I thi.nk that option 3 is not going to happen based on the discussion, although I just talked about it with @DonJayamanne last night and it sounds like he might implement it that way for his node notebook, which is great, more experimentation and more data. Maybe to start out I would only have one new "Debug Cell" button which is equivalent to the normal "Run cell". It can go under the run button chevron all the time, and the setting that exists for that can just control whether the "Run above/below" buttons go in there too. And I'll try to come up with a keyboard shortcut for it. |
That sounds great. I think I'll go ahead and try to implement the "attach debugger to kernel" with the UI I outlined above for the Julia controller, and also do the "Debug cell" thing at the same time. One question for @roblourens: I assume you will add the "Debug Cell" button as part of the Jupyter extension? Would that button than somehow not be visible if a user selects the Julia notebook controller from our extension? And then we could also provide our own "Debug Cell" button? Or would we somehow "share" one "Debug Cell" button? |
At least for now, yes, the jupyter extension would contribute the button and it would only appear for kernels that we contribute and have decided we can debug. In the future if we wanted to standardize that UI, maybe we would make something shared that your extensions or others could hook into. |
Calling this resolved, and any other work will be done via bugs/feature requests. |
The current plan is to support both real debugging and "run by line" in Jupyter. "Run by line" is a simple debug mode that only allows stepping through the code of a single cell. It will be backed by a real debug session but with some debug UI in VS Code disabled.
Basic debugging questions/tasks
languages
property on the debugger contribution), which enables this action.debugAdapter
property which tells us the ID of a debug adapter that can debug the active kernel. If it's undefined, debugging is not enabled.[ ] Variables pane vs debug variables view[ ] The debug console vs interactive pane[ ] Figure out how debugging across different kernel types would work[ ] Should we put cells in readonly mode when paused?Run by line tasks
vscode.debug.startDebugging
DebugSessionOptions
. I think the first is preferable, but will there be debug UI shown before we get the capabilities?https://excalidraw.com/#json=5721711802580992,GeAhnXz10-VKb53A9XmgNA
cc @isidorn @weinand
The text was updated successfully, but these errors were encountered: