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