When does RStudio's Stop button fail to interrupt a running computation?
0 reputation · 09 Sept 2022, 11:34 UTC
0 reputation · 09 Sept 2022, 11:34 UTC
I run long computations in the RStudio console and rely on the Stop button (or Esc) to cancel them. Sometimes the interrupt works immediately; other times the session appears unresponsive and my only apparent recourse is Terminate R, which discards the workspace.
My understanding is that Stop sends an interrupt that R only honors when the interpreter checks for it, so code executing inside compiled C/C++/Fortran routines that never call R_CheckUserInterrupt() will not stop until control returns to R. I also believe operations blocked in certain system calls may ignore the request entirely, and that behavior differs between Windows and Unix-like systems.
Since RStudio has no built-in per-command timeout, I am weighing setTimeLimit() against package-level timeout wrappers, but I am unsure how reliably either fires while native code is running.
Which categories of long-running operations are known to ignore the Stop interrupt? Does setTimeLimit(elapsed = ...) fire reliably when execution is inside compiled code? Is there any way to predict, before launching a computation, whether cancellation will be graceful?
29775 reputation · 09 Sept 2022, 16:56 UTC
RStudio's Stop button (and the Esc key) fails whenever the R interpreter is blocked and cannot check its internal interrupt flag. This occurs primarily when execution is inside compiled native code (C, C++, or Fortran) that does not explicitly call R_CheckUserInterrupt(), or when the process is blocked by a synchronous system call (such as certain I/O or network operations) that prevents the R event loop from cycling.
.Call() or .External(), control leaves the R interpreter. If that native function runs a long loop without manually checking the interrupt status, R cannot see the "Stop" request until the native function returns.setTimeLimit()It is a common assumption that setTimeLimit(elapsed = ...) provides a hard timeout. However, setTimeLimit relies on the same mechanism as the Stop button: it triggers an error when R checks the time. If the execution is trapped in a native C++ loop that doesn't check for interrupts, setTimeLimit will not fire until the native code returns control to R. It is not a preemptive "kill" signal.
To verify if a specific package or function is "interrupt-safe," you can test it with a known non-interruptible pattern. If you use Rcpp, a loop like this will ignore the Stop button:
// Non-interruptible C++ loop
for(int i = 0; i < 1000000000; ++i) {
// Do heavy work without R_CheckUserInterrupt()
}
Conversely, adding R_CheckUserInterrupt() inside the loop allows the Stop button to function.
Before launching a computation, you can gauge the risk based on these categories:
| Operation Type | Interrupt Reliability | Risk |
|---|---|---|
| Pure R loops/apply | High | Low |
Standard R packages (e.g., dplyr) |
Medium-High | Low |
| Heavy Linear Algebra (BLAS/LAPACK) | Low | Medium |
| Custom C++/Fortran extensions | Variable | High (if not explicitly coded) |
Missing Diagnostic: Are you running these computations on a local machine or via RStudio Server/Workbench? Network latency in remote sessions can sometimes mimic a frozen process even when the R kernel is technically responsive.
Use comments to ask for clarification. Post a solution as an answer.
3,110 reputation · 09 Sept 2022, 15:56 UTC
To build on the discussion of compiled code, it is important to distinguish between the foreground console and background sessions. If you are using RStudio Jobs or packages like future or callr to run computations in a separate process, the primary Stop button in the console will not affect those workers.
In these scenarios, the interrupt signal is sent to the main R session, but the worker process remains isolated. To cancel these, you must use the specific "Stop" or "Terminate" buttons within the Jobs pane or the specific handle provided by the package. This often leads to the perception that the Stop button has "failed," when in reality, the signal was delivered to the wrong process.
You can verify this by running a long Sys.sleep() via future::plan(multisession). You will notice the console returns to the prompt immediately upon clicking Stop, but the background worker continues to consume system resources until it finishes or is manually killed via the OS task manager.