|
1 | 1 | /* |
2 | 2 | * console.rs |
3 | 3 | * |
4 | | - * Copyright (C) 2023 Posit Software, PBC. All rights reserved. |
| 4 | + * Copyright (C) 2023-2026 Posit Software, PBC. All rights reserved. |
5 | 5 | * |
6 | 6 | */ |
7 | 7 |
|
8 | 8 | use std::ffi::c_char; |
9 | 9 | use std::ffi::CStr; |
| 10 | +use std::sync::Condvar; |
| 11 | +use std::sync::Mutex; |
10 | 12 |
|
| 13 | +use libr::ptr_R_Busy; |
| 14 | +use libr::ptr_R_ReadConsole; |
| 15 | +use libr::ptr_R_ShowMessage; |
| 16 | +use libr::ptr_R_Suicide; |
| 17 | +use libr::ptr_R_WriteConsole; |
| 18 | +use libr::ptr_R_WriteConsoleEx; |
| 19 | +use libr::run_Rmainloop; |
| 20 | +use libr::setup_Rmainloop; |
| 21 | +use libr::R_Consolefile; |
| 22 | +use libr::R_HomeDir; |
| 23 | +use libr::R_InputHandlers; |
| 24 | +use libr::R_Interactive; |
| 25 | +use libr::R_Outputfile; |
| 26 | +use libr::R_PolledEvents; |
| 27 | +use libr::R_SignalHandlers; |
| 28 | +use libr::R_checkActivity; |
| 29 | +use libr::R_runHandlers; |
| 30 | +use libr::R_running_as_main_program; |
| 31 | +use libr::R_wait_usec; |
| 32 | +use libr::Rf_initialize_R; |
| 33 | + |
| 34 | +use crate::console::r_busy; |
| 35 | +use crate::console::r_polled_events; |
| 36 | +use crate::console::r_read_console; |
| 37 | +use crate::console::r_show_message; |
| 38 | +use crate::console::r_suicide; |
| 39 | +use crate::console::r_write_console; |
| 40 | +use crate::console::Console; |
| 41 | +use crate::signals::initialize_signal_handlers; |
| 42 | + |
| 43 | +// For shutdown signal in integration tests |
| 44 | +pub static CLEANUP_SIGNAL: (Mutex<bool>, Condvar) = (Mutex::new(false), Condvar::new()); |
| 45 | + |
| 46 | +pub fn setup_r(args: &Vec<String>) { |
| 47 | + unsafe { |
| 48 | + // Before `Rf_initialize_R()` |
| 49 | + libr::set(R_running_as_main_program, 1); |
| 50 | + |
| 51 | + libr::set(R_SignalHandlers, 0); |
| 52 | + |
| 53 | + let mut c_args = Console::build_ark_c_args(args); |
| 54 | + Rf_initialize_R(c_args.len() as i32, c_args.as_mut_ptr() as *mut *mut c_char); |
| 55 | + |
| 56 | + // Initialize the signal blocks and handlers (like interrupts). |
| 57 | + // Don't do that in tests because that makes them uninterruptible. |
| 58 | + if !stdext::IS_TESTING { |
| 59 | + initialize_signal_handlers(); |
| 60 | + } |
| 61 | + |
| 62 | + // Mark R session as interactive |
| 63 | + // (Should have also been set by call to `Rf_initialize_R()`) |
| 64 | + libr::set(R_Interactive, 1); |
| 65 | + |
| 66 | + // Log the value of R_HOME, so we can know if something hairy is afoot |
| 67 | + let home = CStr::from_ptr(R_HomeDir()); |
| 68 | + log::trace!("R_HOME: {:?}", home); |
| 69 | + |
| 70 | + // Redirect console |
| 71 | + libr::set(R_Consolefile, std::ptr::null_mut()); |
| 72 | + libr::set(R_Outputfile, std::ptr::null_mut()); |
| 73 | + |
| 74 | + libr::set(ptr_R_WriteConsole, None); |
| 75 | + libr::set(ptr_R_WriteConsoleEx, Some(r_write_console)); |
| 76 | + libr::set(ptr_R_ReadConsole, Some(r_read_console)); |
| 77 | + libr::set(ptr_R_ShowMessage, Some(r_show_message)); |
| 78 | + libr::set(ptr_R_Busy, Some(r_busy)); |
| 79 | + libr::set(ptr_R_Suicide, Some(r_suicide)); |
| 80 | + |
| 81 | + // Install a CleanUp hook for integration tests that test the shutdown process. |
| 82 | + // We confirm that shutdown occurs by waiting in the test until `CLEANUP_SIGNAL`'s |
| 83 | + // condition variable sends a notification, which occurs in this cleanup method |
| 84 | + // that is called during R's shutdown process. |
| 85 | + if stdext::IS_TESTING { |
| 86 | + libr::set(libr::ptr_R_CleanUp, Some(r_cleanup_for_tests)); |
| 87 | + } |
| 88 | + |
| 89 | + // In tests R may be run from various threads. This confuses R's stack |
| 90 | + // overflow checks so we disable those. This should not make it in |
| 91 | + // production builds as it causes stack overflows to crash R instead of |
| 92 | + // throwing an R error. |
| 93 | + // |
| 94 | + // This must be called _after_ `Rf_initialize_R()`, since that's where R |
| 95 | + // detects the stack size and sets the default limit. |
| 96 | + if stdext::IS_TESTING { |
| 97 | + libr::set(libr::R_CStackLimit, usize::MAX); |
| 98 | + } |
| 99 | + |
| 100 | + // Set up main loop |
| 101 | + setup_Rmainloop(); |
| 102 | + } |
| 103 | +} |
| 104 | + |
| 105 | +pub fn run_r() { |
| 106 | + unsafe { |
| 107 | + // Listen for polled events |
| 108 | + libr::set(R_wait_usec, 10000); |
| 109 | + libr::set(R_PolledEvents, Some(r_polled_events)); |
| 110 | + |
| 111 | + run_Rmainloop(); |
| 112 | + } |
| 113 | +} |
| 114 | + |
| 115 | +pub fn run_activity_handlers() { |
| 116 | + unsafe { |
| 117 | + // Run handlers if we have data available. This is necessary |
| 118 | + // for things like the HTML help server, which will listen |
| 119 | + // for requests on an open socket() which would then normally |
| 120 | + // be handled in a select() call when reading input from stdin. |
| 121 | + // |
| 122 | + // https://github.com/wch/r-source/blob/4ca6439c1ffc76958592455c44d83f95d5854b2a/src/unix/sys-std.c#L1084-L1086 |
| 123 | + // |
| 124 | + // We run this in a loop just to make sure the R help server can |
| 125 | + // be as responsive as possible when rendering help pages. |
| 126 | + // |
| 127 | + // Note that the later package also adds an input handler to `R_InputHandlers` |
| 128 | + // which runs the later event loop, so it's also important that we are fairly |
| 129 | + // responsive for that as well (posit-dev/positron#7235). |
| 130 | + let mut fdset = R_checkActivity(0, 1); |
| 131 | + |
| 132 | + while fdset != std::ptr::null_mut() { |
| 133 | + R_runHandlers(libr::get(R_InputHandlers), fdset); |
| 134 | + fdset = R_checkActivity(0, 1); |
| 135 | + } |
| 136 | + } |
| 137 | +} |
| 138 | + |
| 139 | +#[no_mangle] |
| 140 | +pub extern "C-unwind" fn r_cleanup_for_tests(_save_act: i32, _status: i32, _run_last: i32) { |
| 141 | + // Signal that cleanup has started |
| 142 | + let (lock, cvar) = &CLEANUP_SIGNAL; |
| 143 | + |
| 144 | + let mut started = lock.lock().unwrap(); |
| 145 | + *started = true; |
| 146 | + |
| 147 | + cvar.notify_all(); |
| 148 | + drop(started); |
| 149 | + |
| 150 | + // Sleep to give tests time to complete before we panic |
| 151 | + std::thread::sleep(std::time::Duration::from_secs(5)); |
| 152 | + |
| 153 | + // Fallthrough to R which will call `exit()`. Note that panicking from here |
| 154 | + // would be UB, we can't panic over a C stack. |
| 155 | +} |
11 | 156 | /// On Unix, we assume that the buffer to write to the console is |
12 | 157 | /// already in UTF-8 |
13 | 158 | pub fn console_to_utf8(x: *const c_char) -> anyhow::Result<String> { |
|
0 commit comments