JuliaInterpreter.jl
JuliaInterpreter.jl copied to clipboard
Provide a pause token argument for the debug_command function
EDIT: See at the end of the thread, I would actually prefer a different design now than described up here.
In the VS Code extension, we have a situation that the UI can send a PAUSE request while the debugger is currently running code. It can also set a request to add another breakpoint while code is running in the interpreter.
I've looked through the upcoming Jupyter debug support, and we have the same situation there, i.e. we should ideally support a scenario where we can handle a few key requests while the debugger is actually running user code.
Maybe one way to handle that is provide the ability to provide a callback function to the debugger that will be run after every statement that the interpreter is running. And maybe we could say there are two things that can happen in such a callback function: it can change the breakpoints that are set, and if it returns some particular value, the debugger will pause debugging at whatever statement it is right now.
Obviously such a callback function would have to be super fast, because it would be called a lot.
The way I would implement it in the VS Code debugging code would probably something like this:
- First, check whether at least 0.5 seconds have elapsed since the last time it did any processing. If not, return immediately (so this would guarantee that it adds really minimal overhead in most cases).
- Check whether a PAUSE or breakpoint request was received from the client, and then handle things accordingly.
I could imagine that I might move the message handling into a background thread eventually (once we have a notion of background threads that run with a certain priority and are not potentially flooded with the PARTR task scheduling logic), but even then I think this kind of cooperative callback option would be the way to go: in that case it might just check some channel for the existence of a new message that triggers a PAUSE or breakpoint.
I'm not super sure about this design, so would be quite interested to hear other ideas how we could solve this as well! But it would be good if we could add something like that in some way.
I imagine that this would be infinitely more difficult if some of the compile ideas via Cassette were implemented... Or maybe not? @oxinabox just pinging you here to make sure this is on your radar.
I'd want to make it less overhead than checking the clock and any pending requests. But just a counter, and doing a more thorough check every 10^4 operations, would likely have very little overhead.
Can you clarify why this needs to be part of JuliaInterpretter at all?
Why not just another task running on a timer that disables / creates breakpoints ?
Yeah, I think that should at least be tried first. Of course, you are not going to know exactly when the task gets scheduled but I don't think it will be a problem in practice.
Given our performance problems, though, a way for GUIs to be able to interrupt without having to know where to set a breakpoint seems like a good thing? Or is there a good way to do that too?
A "break now!" button seems reasonable. Could be added as a flag check to shouldbreak. The question is how you activate it. By a task that sets the flag, or a callback you pass in, in the beginning?
A callback that's called every once in a while (i.e. every x instructions) seems like a good solution, imho.
I'd want to make it less overhead than checking the clock and any pending requests. But just a counter, and doing a more thorough check every 10^4 operations, would likely have very little overhead.
I could have that check in the callback, right? So say while we still don't have background threading in Julia, I could have a check in the callback that just counts up to 10^4 operations, and then checks for something more meaningful (like new messages or something like that). Once we have support for background threading, the callback could just check for some atomic flag. It seems generally more flexible if that check happens outside of JuliaInterpreter.
Why not just another task running on a timer that disables / creates breakpoints ?
What if I'm debugging some code that never yields? Then this other task will never run, but I'd still like to be able to add bps or pause such code. Of course JuliaInterpreter could periodically yield, but that seems higher overhead to me than calling a very small callback function.
From the VS Code extension point of view, a design that would work really well would be this: debug_command accepts another argument, namely a callback function. The callback function would accept no argument. If it returned true, JuliaInterpreter would pause execution, if it returned false it would just continue.
I would now actually prefer a different design than a callback :) I adjusted the title of the issue accordingly.
From the VS Code extensions point of view, ideally we could pass something akin to a "pause"-token to debug_command, where the token is either an Atomic{Bool} or an Event (the latter doesn't work on Julia 1.0, so that would be a problem).
JuliaInterpreter.jl would then just periodically somewhere in its loop check whether that pause token is true (or in signaled state in the case of Event), and if it is, pause execution and return as if there had been a breakpoint. It would be extra nice if the return value of debug_command allowed us to distinguish whether the interpreter paused because of this pause signal, or because it hit a "real" breakpoint.