|
10 | 10 | // The frontend methods called by R are forwarded to the corresponding |
11 | 11 | // `RMain` methods via `R_MAIN`. |
12 | 12 |
|
| 13 | +use std::cell::RefCell; |
| 14 | +use std::cell::UnsafeCell; |
13 | 15 | use std::collections::HashMap; |
14 | 16 | use std::ffi::*; |
15 | 17 | use std::os::raw::c_uchar; |
@@ -142,20 +144,23 @@ pub enum SessionMode { |
142 | 144 | // These values must be global in order for them to be accessible from R |
143 | 145 | // callbacks, which do not have a facility for passing or returning context. |
144 | 146 |
|
145 | | -// We use the `once_cell` crate for init synchronisation because the stdlib |
146 | | -// equivalent `std::sync::Once` does not have a `wait()` method. |
147 | | - |
148 | 147 | /// Used to wait for complete R startup in `RMain::wait_initialized()` or |
149 | 148 | /// check for it in `RMain::is_initialized()`. |
| 149 | +/// |
| 150 | +/// We use the `once_cell` crate for init synchronisation because the stdlib |
| 151 | +/// equivalent `std::sync::Once` does not have a `wait()` method. |
150 | 152 | static R_INIT: once_cell::sync::OnceCell<()> = once_cell::sync::OnceCell::new(); |
151 | 153 |
|
152 | | -// The global state used by R callbacks. |
153 | | -// |
154 | | -// Doesn't need a mutex because it's only accessed by the R thread. Should |
155 | | -// not be used elsewhere than from an R frontend callback or an R function |
156 | | -// invoked by the REPL (this is enforced by `RMain::get()` and |
157 | | -// `RMain::get_mut()`). |
158 | | -static mut R_MAIN: Option<RMain> = None; |
| 154 | +thread_local! { |
| 155 | + /// The `RMain` singleton. |
| 156 | + /// |
| 157 | + /// It is wrapped in an `UnsafeCell` because we currently need to bypass the |
| 158 | + /// borrow checker rules (see https://github.com/posit-dev/ark/issues/663). |
| 159 | + /// The `UnsafeCell` itself is wrapped in a `RefCell` because that's the |
| 160 | + /// only way to get a `set()` method on the thread-local storage key and |
| 161 | + /// bypass the lazy initializer (which panics for other threads). |
| 162 | + pub static R_MAIN: RefCell<UnsafeCell<RMain>> = panic!("Must access `R_MAIN` from the R thread"); |
| 163 | +} |
159 | 164 |
|
160 | 165 | /// Banner output accumulated during startup |
161 | 166 | static mut R_BANNER: String = String::new(); |
@@ -319,22 +324,21 @@ impl RMain { |
319 | 324 | let (tasks_interrupt_tx, tasks_interrupt_rx) = unbounded::<RTask>(); |
320 | 325 | let (tasks_idle_tx, tasks_idle_rx) = unbounded::<RTask>(); |
321 | 326 |
|
322 | | - unsafe { |
323 | | - R_MAIN = Some(RMain::new( |
324 | | - tasks_interrupt_rx, |
325 | | - tasks_idle_rx, |
326 | | - comm_manager_tx, |
327 | | - r_request_rx, |
328 | | - stdin_request_tx, |
329 | | - stdin_reply_rx, |
330 | | - iopub_tx, |
331 | | - kernel_init_tx, |
332 | | - kernel_request_rx, |
333 | | - dap, |
334 | | - session_mode, |
335 | | - )); |
336 | | - }; |
337 | | - let r_main = unsafe { R_MAIN.as_mut().unwrap() }; |
| 327 | + R_MAIN.set(UnsafeCell::new(RMain::new( |
| 328 | + tasks_interrupt_rx, |
| 329 | + tasks_idle_rx, |
| 330 | + comm_manager_tx, |
| 331 | + r_request_rx, |
| 332 | + stdin_request_tx, |
| 333 | + stdin_reply_rx, |
| 334 | + iopub_tx, |
| 335 | + kernel_init_tx, |
| 336 | + kernel_request_rx, |
| 337 | + dap, |
| 338 | + session_mode, |
| 339 | + ))); |
| 340 | + |
| 341 | + let main = RMain::get_mut(); |
338 | 342 |
|
339 | 343 | let mut r_args = r_args.clone(); |
340 | 344 |
|
@@ -444,7 +448,7 @@ impl RMain { |
444 | 448 | log::error!("Can't load R modules: {err:?}"); |
445 | 449 | }, |
446 | 450 | Ok(namespace) => { |
447 | | - r_main.positron_ns = Some(namespace); |
| 451 | + main.positron_ns = Some(namespace); |
448 | 452 | }, |
449 | 453 | } |
450 | 454 |
|
@@ -477,7 +481,7 @@ impl RMain { |
477 | 481 | log::info!( |
478 | 482 | "R has started and ark handlers have been registered, completing initialization." |
479 | 483 | ); |
480 | | - r_main.complete_initialization(); |
| 484 | + main.complete_initialization(); |
481 | 485 | } |
482 | 486 |
|
483 | 487 | // Now that R has started and libr and ark have fully initialized, run site and user |
@@ -585,33 +589,26 @@ impl RMain { |
585 | 589 |
|
586 | 590 | /// Access a reference to the singleton instance of this struct |
587 | 591 | /// |
588 | | - /// SAFETY: Accesses must occur after `RMain::start()` initializes it, and must |
589 | | - /// occur on the main R thread. |
| 592 | + /// SAFETY: Accesses must occur after `RMain::start()` initializes it. |
590 | 593 | pub fn get() -> &'static Self { |
591 | 594 | RMain::get_mut() |
592 | 595 | } |
593 | 596 |
|
594 | 597 | /// Access a mutable reference to the singleton instance of this struct |
595 | 598 | /// |
596 | | - /// SAFETY: Accesses must occur after `RMain::start()` initializes it, and must |
597 | | - /// occur on the main R thread. |
| 599 | + /// SAFETY: Accesses must occur after `RMain::start()` initializes it. |
| 600 | + /// Be aware that we're bypassing the borrow checker. The only guarantee we |
| 601 | + /// have is that `R_MAIN` is only accessed from the R thread. If you're |
| 602 | + /// inspecting mutable state, or mutating state, you must reason the |
| 603 | + /// soundness by yourself. |
598 | 604 | pub fn get_mut() -> &'static mut Self { |
599 | | - if !RMain::on_main_thread() { |
600 | | - let thread = std::thread::current(); |
601 | | - let name = thread.name().unwrap_or("<unnamed>"); |
602 | | - let message = |
603 | | - format!("Must access `R_MAIN` on the main R thread, not thread '{name}'."); |
604 | | - #[cfg(debug_assertions)] |
605 | | - panic!("{message}"); |
606 | | - #[cfg(not(debug_assertions))] |
607 | | - log::error!("{message}"); |
608 | | - } |
| 605 | + R_MAIN.with_borrow_mut(|cell| { |
| 606 | + let main_ref = cell.get_mut(); |
609 | 607 |
|
610 | | - unsafe { |
611 | | - R_MAIN |
612 | | - .as_mut() |
613 | | - .expect("`R_MAIN` can't be used before it is initialized!") |
614 | | - } |
| 608 | + // We extend the lifetime to `'static` as `R_MAIN` is effectively static once initialized. |
| 609 | + // This allows us to return a `&mut` from the unsafe cell to the caller. |
| 610 | + unsafe { std::mem::transmute::<&mut RMain, &'static mut RMain>(main_ref) } |
| 611 | + }) |
615 | 612 | } |
616 | 613 |
|
617 | 614 | pub fn with<F, T>(f: F) -> T |
|
0 commit comments