---
title: "High-Performance Asynchronous Processing with mirai"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{High-Performance Asynchronous Processing with mirai}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
```
```{r package-status}
RDesk::rdesk_jobs_pending()
```
In a native desktop application, maintaining a highly responsive user interface is a primary design goal. If a user clicks a button to load a large dataset, compile a plot, or run a statistical model, and the user interface freezes while R computes, the user experience degrades.
To address this, RDesk implements a non-blocking asynchronous processing engine. Under the hood, RDesk utilizes the **mirai** package to offload intensive computations to persistent background worker processes, ensuring the frontend UI remains interactive at all times.
---
## The Asynchronous Architecture
RDesk operates as a parent process managing the main R6 event loop, while launching WebView2 as a native child window. To keep communication fast and secure, RDesk avoids loopback network stacks and HTTP overhead, relying instead on standard I/O pipes.
When a long-running task is submitted, RDesk offloads the work to **mirai background daemons**.
The diagrams below illustrate how RDesk, standard input/output pipes, and the `mirai` daemon pool interact to process background operations.
### 1. Architectural Component Map
This diagram illustrates the process and memory boundaries between the frontend user interface, the main R process event loop, and the pre-warmed background worker processes.
```mermaid
graph TD
subgraph Core [Main Application Event Flow]
Loop[R6 Event Loop] -->|1. Submit Job| Poller[Unresolved Job Poller]
Poller -->|2. Offload Task| Workers[mirai Daemon Pool]
Workers -->|3. Complete Job| Poller
Poller -->|4. Push Results| Loop
end
UI[Client UI
WebView2 Shell] <-->|stdin / stdout pipes| Loop
```
### 2. Message Lifecycle Sequence
This sequence diagram outlines the chronological flow of a message, from a user action in the UI, through the stdin pipe to R, submission to a mirai background daemon, non-blocking state polling, and the final return of results to the frontend.
```mermaid
sequenceDiagram
autonumber
participant UI as WebView2 UI (HTML/JS)
participant Main as Main R Process (Event Loop)
participant Workers as mirai Daemon Pool
UI->>Main: 1. Send action with payload
Note over Main: Receives message envelope
and schedules handler
Main->>Workers: 2. Offload computation
Note over Workers: Worker process executes task
in the background (non-blocking)
loop Event Loop
Main->>Main: 3. Non-blocking job polling
end
Workers-->>Main: 4. Complete task and return results
Main->>UI: 5. Push results back to UI
```
---
## Persistent Daemons vs On-Demand Processes
RDesk implements a **dual-backend** async mechanism that automatically selects the best available engine:
1. **mirai (Persistent Daemons - Default)**: Starts a pool of pre-warmed background workers at application launch using `mirai::daemons()`. When an asynchronous task is submitted, it is dispatched to an existing worker instantly.
2. **callr (On-Demand Processes - Fallback)**: Spins up a fresh R process for each task and terminates it upon completion. This is utilized in headless CI environments or systems where background daemons are unavailable.
### Why mirai is the preferred backend:
* **Zero Startup Latency**: Pre-warmed background workers start executing tasks in milliseconds, whereas spawning a fresh R process on demand via `callr` adds 1–2 seconds of process-creation overhead for each execution.
* **Resource Preservation**: A fixed pool of persistent workers ensures background memory usage remains flat and predictable, avoiding spikes from concurrent process launches.
* **Automatic Lifecycle Handling**: Daemons are automatically spun up on `App$run()` startup and cleanly terminated on application shutdown, preventing orphaned zombie processes.
---
## Under the Hood: The Developer Experience
For the application developer, RDesk abstracts this multi-process coordination into the standard `async()` wrapper.
```r
# In R/server.R
app$on_message("run_model", async(function(payload) {
# This code runs entirely in an isolated mirai worker.
# The UI remains 100% interactive and responsive.
Sys.sleep(3) # Simulate a heavy statistical modeling task
result <- kmeans(mtcars, centers = payload$centers)
list(centers = result$centers, size = result$size)
}, app = app, loading_message = "Running K-Means..."))
```
### Critical Rules for Async Workers:
Because the `async()` handler runs in an isolated `mirai` background process:
* **No `app$` Access**: Background workers cannot reach the main R process. Do not call `app$send()`, `app$toast()`, or `app$dialog_*` inside the worker closure. Return the final data as a list, and the wrapper handles routing.
* **Scoped Closures**: Ensure any package dependencies are explicitly imported (using `library()` inside the worker, or listed in the `DESCRIPTION` Imports) and variables are passed via the payload or explicitly bound in the local closure.