1 use anyhow::Error; 2 use std::any::Any; 3 use std::cell::Cell; 4 use std::io; 5 use std::marker::PhantomData; 6 use std::ops::Range; 7 use std::panic::{self, AssertUnwindSafe}; 8 9 cfg_if::cfg_if! { 10 if #[cfg(windows)] { 11 mod windows; 12 use windows as imp; 13 } else if #[cfg(unix)] { 14 mod unix; 15 use unix as imp; 16 } else { 17 compile_error!("fibers are not supported on this platform"); 18 } 19 } 20 21 /// Represents an execution stack to use for a fiber. 22 pub struct FiberStack(imp::FiberStack); 23 24 impl FiberStack { 25 /// Creates a new fiber stack of the given size. 26 pub fn new(size: usize) -> io::Result<Self> { 27 Ok(Self(imp::FiberStack::new(size)?)) 28 } 29 30 /// Creates a new fiber stack of the given size. 31 pub fn from_custom(custom: Box<dyn RuntimeFiberStack>) -> io::Result<Self> { 32 Ok(Self(imp::FiberStack::from_custom(custom)?)) 33 } 34 35 /// Creates a new fiber stack with the given pointer to the bottom of the 36 /// stack plus how large the guard size and stack size are. 37 /// 38 /// The bytes from `bottom` to `bottom.add(guard_size)` should all be 39 /// guaranteed to be unmapped. The bytes from `bottom.add(guard_size)` to 40 /// `bottom.add(guard_size + len)` should be addressable. 41 /// 42 /// # Safety 43 /// 44 /// This is unsafe because there is no validation of the given pointer. 45 /// 46 /// The caller must properly allocate the stack space with a guard page and 47 /// make the pages accessible for correct behavior. 48 pub unsafe fn from_raw_parts( 49 bottom: *mut u8, 50 guard_size: usize, 51 len: usize, 52 ) -> io::Result<Self> { 53 Ok(Self(imp::FiberStack::from_raw_parts( 54 bottom, guard_size, len, 55 )?)) 56 } 57 58 /// Gets the top of the stack. 59 /// 60 /// Returns `None` if the platform does not support getting the top of the 61 /// stack. 62 pub fn top(&self) -> Option<*mut u8> { 63 self.0.top() 64 } 65 66 /// Returns the range of where this stack resides in memory if the platform 67 /// supports it. 68 pub fn range(&self) -> Option<Range<usize>> { 69 self.0.range() 70 } 71 72 /// Is this a manually-managed stack created from raw parts? If so, it is up 73 /// to whoever created it to manage the stack's memory allocation. 74 pub fn is_from_raw_parts(&self) -> bool { 75 self.0.is_from_raw_parts() 76 } 77 78 /// Returns the range of memory that the guard page(s) reside in. 79 pub fn guard_range(&self) -> Option<Range<*mut u8>> { 80 self.0.guard_range() 81 } 82 } 83 84 /// A creator of RuntimeFiberStacks. 85 pub unsafe trait RuntimeFiberStackCreator: Send + Sync { 86 /// Creates a new RuntimeFiberStack with the specified size, guard pages should be included, 87 /// memory should be zeroed. 88 /// 89 /// This is useful to plugin previously allocated memory instead of mmap'ing a new stack for 90 /// every instance. 91 fn new_stack(&self, size: usize) -> Result<Box<dyn RuntimeFiberStack>, Error>; 92 } 93 94 /// A fiber stack backed by custom memory. 95 pub unsafe trait RuntimeFiberStack: Send + Sync { 96 /// The top of the allocated stack. 97 fn top(&self) -> *mut u8; 98 /// The valid range of the stack without guard pages. 99 fn range(&self) -> Range<usize>; 100 /// The range of the guard page(s) 101 fn guard_range(&self) -> Range<*mut u8>; 102 } 103 104 pub struct Fiber<'a, Resume, Yield, Return> { 105 stack: Option<FiberStack>, 106 inner: imp::Fiber, 107 done: Cell<bool>, 108 _phantom: PhantomData<&'a (Resume, Yield, Return)>, 109 } 110 111 pub struct Suspend<Resume, Yield, Return> { 112 inner: imp::Suspend, 113 _phantom: PhantomData<(Resume, Yield, Return)>, 114 } 115 116 enum RunResult<Resume, Yield, Return> { 117 Executing, 118 Resuming(Resume), 119 Yield(Yield), 120 Returned(Return), 121 Panicked(Box<dyn Any + Send>), 122 } 123 124 impl<'a, Resume, Yield, Return> Fiber<'a, Resume, Yield, Return> { 125 /// Creates a new fiber which will execute `func` on the given stack. 126 /// 127 /// This function returns a `Fiber` which, when resumed, will execute `func` 128 /// to completion. When desired the `func` can suspend itself via 129 /// `Fiber::suspend`. 130 pub fn new( 131 stack: FiberStack, 132 func: impl FnOnce(Resume, &mut Suspend<Resume, Yield, Return>) -> Return + 'a, 133 ) -> io::Result<Self> { 134 let inner = imp::Fiber::new(&stack.0, func)?; 135 136 Ok(Self { 137 stack: Some(stack), 138 inner, 139 done: Cell::new(false), 140 _phantom: PhantomData, 141 }) 142 } 143 144 /// Resumes execution of this fiber. 145 /// 146 /// This function will transfer execution to the fiber and resume from where 147 /// it last left off. 148 /// 149 /// Returns `true` if the fiber finished or `false` if the fiber was 150 /// suspended in the middle of execution. 151 /// 152 /// # Panics 153 /// 154 /// Panics if the current thread is already executing a fiber or if this 155 /// fiber has already finished. 156 /// 157 /// Note that if the fiber itself panics during execution then the panic 158 /// will be propagated to this caller. 159 pub fn resume(&self, val: Resume) -> Result<Return, Yield> { 160 assert!(!self.done.replace(true), "cannot resume a finished fiber"); 161 let result = Cell::new(RunResult::Resuming(val)); 162 self.inner.resume(&self.stack().0, &result); 163 match result.into_inner() { 164 RunResult::Resuming(_) | RunResult::Executing => unreachable!(), 165 RunResult::Yield(y) => { 166 self.done.set(false); 167 Err(y) 168 } 169 RunResult::Returned(r) => Ok(r), 170 RunResult::Panicked(payload) => std::panic::resume_unwind(payload), 171 } 172 } 173 174 /// Returns whether this fiber has finished executing. 175 pub fn done(&self) -> bool { 176 self.done.get() 177 } 178 179 /// Gets the stack associated with this fiber. 180 pub fn stack(&self) -> &FiberStack { 181 self.stack.as_ref().unwrap() 182 } 183 184 /// When this fiber has finished executing, reclaim its stack. 185 pub fn into_stack(mut self) -> FiberStack { 186 assert!(self.done()); 187 self.stack.take().unwrap() 188 } 189 } 190 191 impl<Resume, Yield, Return> Suspend<Resume, Yield, Return> { 192 /// Suspend execution of a currently running fiber. 193 /// 194 /// This function will switch control back to the original caller of 195 /// `Fiber::resume`. This function will then return once the `Fiber::resume` 196 /// function is called again. 197 /// 198 /// # Panics 199 /// 200 /// Panics if the current thread is not executing a fiber from this library. 201 pub fn suspend(&mut self, value: Yield) -> Resume { 202 self.inner 203 .switch::<Resume, Yield, Return>(RunResult::Yield(value)) 204 } 205 206 fn execute( 207 inner: imp::Suspend, 208 initial: Resume, 209 func: impl FnOnce(Resume, &mut Suspend<Resume, Yield, Return>) -> Return, 210 ) { 211 let mut suspend = Suspend { 212 inner, 213 _phantom: PhantomData, 214 }; 215 let result = panic::catch_unwind(AssertUnwindSafe(|| (func)(initial, &mut suspend))); 216 suspend.inner.switch::<Resume, Yield, Return>(match result { 217 Ok(result) => RunResult::Returned(result), 218 Err(panic) => RunResult::Panicked(panic), 219 }); 220 } 221 } 222 223 impl<A, B, C> Drop for Fiber<'_, A, B, C> { 224 fn drop(&mut self) { 225 debug_assert!(self.done.get(), "fiber dropped without finishing"); 226 } 227 } 228 229 #[cfg(test)] 230 mod tests { 231 use super::{Fiber, FiberStack}; 232 use std::cell::Cell; 233 use std::panic::{self, AssertUnwindSafe}; 234 use std::rc::Rc; 235 236 #[test] 237 fn small_stacks() { 238 Fiber::<(), (), ()>::new(FiberStack::new(0).unwrap(), |_, _| {}) 239 .unwrap() 240 .resume(()) 241 .unwrap(); 242 Fiber::<(), (), ()>::new(FiberStack::new(1).unwrap(), |_, _| {}) 243 .unwrap() 244 .resume(()) 245 .unwrap(); 246 } 247 248 #[test] 249 fn smoke() { 250 let hit = Rc::new(Cell::new(false)); 251 let hit2 = hit.clone(); 252 let fiber = Fiber::<(), (), ()>::new(FiberStack::new(1024 * 1024).unwrap(), move |_, _| { 253 hit2.set(true); 254 }) 255 .unwrap(); 256 assert!(!hit.get()); 257 fiber.resume(()).unwrap(); 258 assert!(hit.get()); 259 } 260 261 #[test] 262 fn suspend_and_resume() { 263 let hit = Rc::new(Cell::new(false)); 264 let hit2 = hit.clone(); 265 let fiber = Fiber::<(), (), ()>::new(FiberStack::new(1024 * 1024).unwrap(), move |_, s| { 266 s.suspend(()); 267 hit2.set(true); 268 s.suspend(()); 269 }) 270 .unwrap(); 271 assert!(!hit.get()); 272 assert!(fiber.resume(()).is_err()); 273 assert!(!hit.get()); 274 assert!(fiber.resume(()).is_err()); 275 assert!(hit.get()); 276 assert!(fiber.resume(()).is_ok()); 277 assert!(hit.get()); 278 } 279 280 #[test] 281 fn backtrace_traces_to_host() { 282 #[inline(never)] // try to get this to show up in backtraces 283 fn look_for_me() { 284 run_test(); 285 } 286 fn assert_contains_host() { 287 let trace = backtrace::Backtrace::new(); 288 println!("{trace:?}"); 289 assert!( 290 trace 291 .frames() 292 .iter() 293 .flat_map(|f| f.symbols()) 294 .filter_map(|s| Some(s.name()?.to_string())) 295 .any(|s| s.contains("look_for_me")) 296 // TODO: apparently windows unwind routines don't unwind through fibers, so this will always fail. Is there a way we can fix that? 297 || cfg!(windows) 298 // TODO: the system libunwind is broken (#2808) 299 || cfg!(all(target_os = "macos", target_arch = "aarch64")) 300 // TODO: see comments in `arm.rs` about how this seems to work 301 // in gdb but not at runtime, unsure why at this time. 302 || cfg!(target_arch = "arm") 303 ); 304 } 305 306 fn run_test() { 307 let fiber = 308 Fiber::<(), (), ()>::new(FiberStack::new(1024 * 1024).unwrap(), move |(), s| { 309 assert_contains_host(); 310 s.suspend(()); 311 assert_contains_host(); 312 s.suspend(()); 313 assert_contains_host(); 314 }) 315 .unwrap(); 316 assert!(fiber.resume(()).is_err()); 317 assert!(fiber.resume(()).is_err()); 318 assert!(fiber.resume(()).is_ok()); 319 } 320 321 look_for_me(); 322 } 323 324 #[test] 325 fn panics_propagated() { 326 let a = Rc::new(Cell::new(false)); 327 let b = SetOnDrop(a.clone()); 328 let fiber = 329 Fiber::<(), (), ()>::new(FiberStack::new(1024 * 1024).unwrap(), move |(), _s| { 330 let _ = &b; 331 panic!(); 332 }) 333 .unwrap(); 334 assert!(panic::catch_unwind(AssertUnwindSafe(|| fiber.resume(()))).is_err()); 335 assert!(a.get()); 336 337 struct SetOnDrop(Rc<Cell<bool>>); 338 339 impl Drop for SetOnDrop { 340 fn drop(&mut self) { 341 self.0.set(true); 342 } 343 } 344 } 345 346 #[test] 347 fn suspend_and_resume_values() { 348 let fiber = Fiber::new(FiberStack::new(1024 * 1024).unwrap(), move |first, s| { 349 assert_eq!(first, 2.0); 350 assert_eq!(s.suspend(4), 3.0); 351 "hello".to_string() 352 }) 353 .unwrap(); 354 assert_eq!(fiber.resume(2.0), Err(4)); 355 assert_eq!(fiber.resume(3.0), Ok("hello".to_string())); 356 } 357 } 358