Skip to content

Commit 0f5bc87

Browse files
authored
Wrap R_MAIN in an UnsafeCell (#664)
Progress towards #661 Workaround for #663
1 parent 1366044 commit 0f5bc87

File tree

1 file changed

+44
-47
lines changed

1 file changed

+44
-47
lines changed

crates/ark/src/interface.rs

Lines changed: 44 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
// The frontend methods called by R are forwarded to the corresponding
1111
// `RMain` methods via `R_MAIN`.
1212

13+
use std::cell::RefCell;
14+
use std::cell::UnsafeCell;
1315
use std::collections::HashMap;
1416
use std::ffi::*;
1517
use std::os::raw::c_uchar;
@@ -142,20 +144,23 @@ pub enum SessionMode {
142144
// These values must be global in order for them to be accessible from R
143145
// callbacks, which do not have a facility for passing or returning context.
144146

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-
148147
/// Used to wait for complete R startup in `RMain::wait_initialized()` or
149148
/// 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.
150152
static R_INIT: once_cell::sync::OnceCell<()> = once_cell::sync::OnceCell::new();
151153

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+
}
159164

160165
/// Banner output accumulated during startup
161166
static mut R_BANNER: String = String::new();
@@ -319,22 +324,21 @@ impl RMain {
319324
let (tasks_interrupt_tx, tasks_interrupt_rx) = unbounded::<RTask>();
320325
let (tasks_idle_tx, tasks_idle_rx) = unbounded::<RTask>();
321326

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();
338342

339343
let mut r_args = r_args.clone();
340344

@@ -444,7 +448,7 @@ impl RMain {
444448
log::error!("Can't load R modules: {err:?}");
445449
},
446450
Ok(namespace) => {
447-
r_main.positron_ns = Some(namespace);
451+
main.positron_ns = Some(namespace);
448452
},
449453
}
450454

@@ -477,7 +481,7 @@ impl RMain {
477481
log::info!(
478482
"R has started and ark handlers have been registered, completing initialization."
479483
);
480-
r_main.complete_initialization();
484+
main.complete_initialization();
481485
}
482486

483487
// Now that R has started and libr and ark have fully initialized, run site and user
@@ -585,33 +589,26 @@ impl RMain {
585589

586590
/// Access a reference to the singleton instance of this struct
587591
///
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.
590593
pub fn get() -> &'static Self {
591594
RMain::get_mut()
592595
}
593596

594597
/// Access a mutable reference to the singleton instance of this struct
595598
///
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.
598604
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();
609607

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+
})
615612
}
616613

617614
pub fn with<F, T>(f: F) -> T

0 commit comments

Comments
 (0)