Environment
languageserver: 0.3.18
callr: 3.8.0
- R: 4.6.1 (macOS arm64)
- vscode-R extension: 2.8.8
- Quarto VS Code extension: 1.135.0
- Quarto CLI: 1.10.18
- VS Code: 1.133.0
- OS: macOS 26.6.2 (Apple Silicon)
Summary
Editing a large .qmd file (prose-heavy, ~20KB, with a handful of embedded R chunks) causes sustained high CPU / fan noise while typing, even when typing in plain prose sections far from any R chunk. Process monitoring shows the language server spawning a brand-new R interpreter subprocess (via callr) on nearly every edit event, each spiking 40-90%+ CPU for roughly a second — dozens of these in a 15-second span of continuous typing.
Root cause (traced locally)
Tracing through the installed languageserver namespace:
textDocument/didChange calls self$text_sync(..., parse = TRUE, delay = 0.5) unconditionally (this is not gated by diagnostics/r.lsp.diagnostics — only the separate lintr pass is).
- This schedules a
parse_task, whose callback parse_callback compares parse_data$packages against the previous parse's package list with !identical(old_parse_data$packages, parse_data$packages).
- Whenever that comparison reads as "changed," it calls
resolve_task, which calls create_task(target = resolve_attached_packages, ...). This spawns a new R subprocess via callr just to run:
function (pkgs = NULL) {
for (pkg in pkgs) {
tryCatch(library(pkg, character.only = TRUE), error = function(e) NULL)
}
rev(.packages())
}
In this document, the packages field appears to be recomputed as "changed" on nearly every edit — including edits in prose text nowhere near an R chunk — which suggests the derived package list is not stable/order-independent across reparses of the whole compound document (e.g. identical() on a vector recomputed via .packages()/set-like logic is order-sensitive; re-scanning the whole document on every change may not guarantee stable ordering even when the actual set of used packages hasn't changed).
Confirmed via direct captures on this machine: dozens of distinct R --slave --no-save --no-restore -f .../callr-scr-* child processes spawned within a 12-15 second typing burst, each the child of the running languageServer.R process. Deserializing the corresponding callr-fun-*.rds payloads consistently showed the target function to be resolve_attached_packages.
Impact
- A brand-new R process incurs real interpreter-startup cost (loading base packages, etc.), so this isn't a cheap no-op — it's a genuine, repeated, avoidable cost.
- For large documents (common with Quarto books/long-form
.qmd reports), this makes editing prose sections generate continuous CPU/thermal load with no R code involved.
- There's currently no setting to disable just this behavior —
r.lsp.diagnostics only gates the lintr pass, not the parse/package-resolution path. The only way to stop it is r.lsp.enabled = false, which also removes completion/hover/diagnostics/go-to-definition entirely.
Happy to provide the full process-monitoring logs / captured callr-fun payloads if useful.
Environment
languageserver: 0.3.18callr: 3.8.0Summary
Editing a large
.qmdfile (prose-heavy, ~20KB, with a handful of embedded R chunks) causes sustained high CPU / fan noise while typing, even when typing in plain prose sections far from any R chunk. Process monitoring shows the language server spawning a brand-new R interpreter subprocess (viacallr) on nearly every edit event, each spiking 40-90%+ CPU for roughly a second — dozens of these in a 15-second span of continuous typing.Root cause (traced locally)
Tracing through the installed
languageservernamespace:textDocument/didChangecallsself$text_sync(..., parse = TRUE, delay = 0.5)unconditionally (this is not gated bydiagnostics/r.lsp.diagnostics— only the separate lintr pass is).parse_task, whose callbackparse_callbackcomparesparse_data$packagesagainst the previous parse's package list with!identical(old_parse_data$packages, parse_data$packages).resolve_task, which callscreate_task(target = resolve_attached_packages, ...). This spawns a new R subprocess viacallrjust to run:In this document, the
packagesfield appears to be recomputed as "changed" on nearly every edit — including edits in prose text nowhere near an R chunk — which suggests the derived package list is not stable/order-independent across reparses of the whole compound document (e.g.identical()on a vector recomputed via.packages()/set-like logic is order-sensitive; re-scanning the whole document on every change may not guarantee stable ordering even when the actual set of used packages hasn't changed).Confirmed via direct captures on this machine: dozens of distinct
R --slave --no-save --no-restore -f .../callr-scr-*child processes spawned within a 12-15 second typing burst, each the child of the runninglanguageServer.Rprocess. Deserializing the correspondingcallr-fun-*.rdspayloads consistently showed the target function to beresolve_attached_packages.Impact
.qmdreports), this makes editing prose sections generate continuous CPU/thermal load with no R code involved.r.lsp.diagnosticsonly gates the lintr pass, not the parse/package-resolution path. The only way to stop it isr.lsp.enabled = false, which also removes completion/hover/diagnostics/go-to-definition entirely.Happy to provide the full process-monitoring logs / captured
callr-funpayloads if useful.