Skip to content

Remove interrupt tasks entirely#1222

Open
DavisVaughan wants to merge 13 commits into
mainfrom
feature/task-timing
Open

Remove interrupt tasks entirely#1222
DavisVaughan wants to merge 13 commits into
mainfrom
feature/task-timing

Conversation

@DavisVaughan
Copy link
Copy Markdown
Contributor

@DavisVaughan DavisVaughan commented May 19, 2026

  • Wait for the next Positron release, merge 1 day after that so we have time to test

Closes #1187

In #1187 we realized that our "interrupt time" tasks have never been running on Windows, and...no one has complained.

Practically, this means that LSP features like completions or hover have not been running on Windows when R is busy, i.e. when R is running some long running computation, even if that code is checking for interrupts periodically via R_CheckUserInterrupt(). Unix machines, on the other hand, do return completions or hover results at interrupt time, but it is fairly unreliable and highly subject to the R code being run, i.e. this video shows how when you run a long running lm(), you can basically hang the LSP on a Mac because it almost never calls R_CheckUserInterrupt():

Screen.Recording.2026-05-15.at.12.21.07.PM.mov

We've long through that doing extensive work at R interrupt time is rather dangerous (you could accidentally load a namespace mid-R execution, which would be wild), so we've wanted to remove these. And with oak, we are going to rely on interrupt time less and less anyways, because more things can be done statically without talking to the main R session.

So this PR removes interrupt tasks entirely. I'll leave more notes inline.

We no longer need this. The potential issue was that we'd call some R code while on the LSP thread, and behind our back while on that LSP thread R would run finalizers that were only meant to be run on the main R thread. This can no longer happen because `r_task()`s are always run on the main R thread. There is no longer any R code that runs on the LSP thread.

We could run `R_RunPendingFinalizers()` ourselves more aggressively after each top level command finishes, but RStudio does not do this, so I don't currently see a need for us to either.
We now only run interrupt tasks at idle time, so in a follow up commit we can remove interrupt tasks as a concept.

We still run activity handlers regularly, particularly important for the later R package

We still call `R_ProcessEvents()` somewhat regularly while idling, mostly out of good faith to whatever R's default methods do, but this is also where our `self.debug_filter` draining occurs now - and this is newly now also being called on Windows, so that's good. I think if we do use `R_ProcessEvents()` for more going forward, it should only be for "non critical" things that can happen at any arbitrary time.
So LSP didChange notifications can invalidate breakpoints while paused in the debugger
We have some indirect tests that get at this too, but a direct test feels quite useful and explanatory
Comment on lines -509 to 514
r_task::spawn(RTask::interrupt({
//
// Use `idle_any_prompt` so DAP breakpoint invalidation still fires when
// the LSP signals a document change while R is paused at `browser()`.
r_task::spawn(RTask::idle_any_prompt({
let dap_clone = console.debug_dap.clone();
async move || {
async move |_| {
Console::process_console_notifications(console_notification_rx, dap_clone).await
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added a test for this as well in dap_breakpoints.rs

Comment on lines +1960 to +1962
// For Sync tasks (i.e. only `r_task()`s), we want to log excessive waiting,
// because we are blocking the calling thread
if matches!(task, QueuedRTask::Sync(_)) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We used to log "excessive waiting" for interrupt tasks

We don't have interrupt tasks anymore, but we do have Sync tasks, which are only used by r_task().

I thought it was appropriate to change this to log excessive waiting for r_task()s, since those are blocking, and since they previously were interrupt tasks. So it felt like the spirit was the same.

Comment on lines 2496 to 2505
@@ -2475,33 +2503,6 @@ impl Console {
if let Some(text) = self.debug_filter.check_timeout() {
self.emit_stdout(text);
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I have kept the debug_filter check as part of an R_ProcessEvents() hook, which is now also being run on Windows.

This is now the ONLY THING special that we do at interrupt time. And it doesn't talk to the R API in any way, so should be very safe.

It's important we run this regularly-ish because the whole point is to not hold onto user output that might (for whatever reason) look like debug: output too long during long running computation

We talked about moving this to another thread, which would let us remove interrupt time hooks altogether. I still think that idea is kind of nice in theory, but I wasn't quite sure how that thread would work.

  • Does it just busy loop with a 25ms sleep between checks?
  • Also, it would force DebugFilterState to be wrapped in a Mutex so it could be shared between Console and this extra thread, and that felt gross

I think we should keep this as is for this PR and if we still feel like we want to move it to a separate thread, we should do a follow up PR and aim for the simplest strategy possible (or, again, explore removing this check entirely)

Comment on lines -2501 to -2504
// Run pending finalizers. We need to do this eagerly as otherwise finalizers
// might end up being executed on the LSP thread.
// https://github.com/rstudio/positron/issues/431
unsafe { R_RunPendingFinalizers() };
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No longer needed since we don't run R code off the main R thread

Comment on lines -110 to +131
// NOTE: This is a legit use of interrupt-time task. No R access here, and
// we need to go through Console since it owns the UI comm.
r_task(|| {
if let Some(ui) = Console::get().ui_comm() {
ui.show_message(String::from(user_message));
}
let request = client.send_request::<request::ShowMessageRequest>(ShowMessageRequestParams {
typ: MessageType::ERROR,
message: String::from(user_message),
actions: None,
});
match tokio::time::timeout(Duration::from_secs(5), request).await {
Ok(result) => {
result.log_err();
},
Err(_) => {
log::warn!("Timed out waiting for frontend to acknowledge LSP crash notification");
},
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

report_crash() moves off needing to use the ui comm!

Comment thread crates/ark/src/r_task.rs
Comment on lines +331 to +334
/// For rare performance sensitive cases where you'd like to avoid the cost of
/// poking R options via [Self::pin_with_capture()] and you know you don't need
/// the safety of capturing output because you aren't running R code
pub(crate) fn send_idle_without_capture<F, Fut>(fun: F) -> Self
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I did end up wanting this for Drop in RThreadSafe

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No longer relevant since R_PolledEvents is a no-op if no one sets it, and we don't set this anymore

Comment thread crates/harp/src/exec.rs
Comment on lines +512 to 514
// TODO: These days this just means `RLocalInterruptsSuspended`.
// Should we simplify the name from `r_sandbox()` to `r_interrupts_suspended()`?
let _scope = crate::raii::RLocalSandbox::new();
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I was curious about your thoughts on this, since r_sandbox() does less now

@DavisVaughan DavisVaughan requested a review from lionel- May 19, 2026 16:05
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.

On Windows, call some equivalent to R_PolledEvents

1 participant