Add Watch Pane support for the notebook debugger#1282
Conversation
ba17a11 to
87ea6f3
Compare
| T: Send + 'static, | ||
| { | ||
| let result: SharedOption<harp::Result<T>> = Arc::new(Mutex::new(None)); | ||
| let (done_tx, done_rx) = bounded::<()>(1); |
There was a problem hiding this comment.
oh nevermind that's the rendezvous channel for the task, it's not the try-idle channel which is indeed 0.
It should be 1 here because we might not be able to take the notification right away, since we might be in the middle of theTRY_IDLE_TIMEOUT timeout-send below. The task sending the notification would needlessly block until we are able to take it.
thomasp85
left a comment
There was a problem hiding this comment.
This generally LG - the one comment I have is about the footgun a user could trigger by having an expression in the watchpane that either resulted in an infinite loop or very slow evaluation. The experience in that situation is not optimal as there is no graceful recovery.
|
@thomasp85 Good point, extracted in posit-dev/positron#14481 |
Addresses posit-dev/positron#13323
The debugger support for notebooks and other Jupyter apps implemented in #1170 and #1171 was quite comprehensive except for one handler:
evaluate, which powers the Watch Pane.The reason it was delayed is that this handler was written for the Console path with an asynchronous approach. The handler needs to evaluate R code in the console and, by the time we get the Evaluate DAP request when the frontend refreshes the Wath Pane contents after R stops at a breakpoint (or
browser()call), R might have resumed evaluation already. That could be either because the user typednvery fast or because they evaluated multiple top-level expresssions in one go and R stops at each of them. To support this case gracefully, the Evaluate handler is executed via an Idle task that only fires when R is no longer busy. Once it completes, a response is sent to the frontend with the result. This happens asynchronously: other DAP requests might come in and be handled while the Evaluate idle task is in flight.This approach did not work out for the Jupyter layer because the handling happens on the Control socket (used for interrupts, shutdowns, and DAP messaging) and expects a quick response right away. While the protocol is silent on whether responses can be asynchronous, reference implementations assume serial handling, both on the frontend side (e.g. VS Code) and the backend side (ipykernel).
This PR solves this by adding a new "try-idle" R task that fires under 200ms if R becomes idle in that time frame, and use that in the Jupyter handler for Evaluate. In my initial implementation there was no timeout and the task would only succeed if the Console was parked in its event loop right at that moment. However that approach had two races that the timeout-waiting fixes:
I picked 200ms arbitrarily, it felt like the maximum amount of time that is reasonable to defer other important Control messages like interrupts or shut downs when R is truly busy and can't service our request.