1#[cfg(not(shuttle))]
11pub mod sync {
12 pub use std::sync::atomic;
13 pub use std::sync::mpsc;
14 #[allow(unused_imports)]
15 pub use std::sync::{Arc, Barrier, Mutex, Weak};
16}
17
18#[cfg(not(shuttle))]
19pub mod thread {
20 #[allow(unused_imports)]
21 pub use std::thread::{JoinHandle, sleep, spawn};
22
23 pub fn spawn_named<F, T>(name: &str, f: F) -> JoinHandle<T>
26 where
27 F: FnOnce() -> T + Send + 'static,
28 T: Send + 'static,
29 {
30 std::thread::Builder::new()
31 .name(name.into())
32 .spawn(f)
33 .expect("failed to spawn thread")
34 }
35}
36
37#[cfg(not(shuttle))]
38#[macro_export]
39macro_rules! define_thread_local {
40 ($($tt:tt)*) => { std::thread_local! { $($tt)* } };
41}
42#[cfg(not(shuttle))]
43pub use crate::define_thread_local as thread_local;
44
45#[cfg(all(not(shuttle), feature = "pipeline"))]
46pub mod time {
47 pub use tokio::time::error::Elapsed;
48 pub use tokio::time::{Instant, sleep, sleep_until, timeout};
49
50 pub fn now() -> Instant {
51 Instant::now()
52 }
53
54 pub fn elapsed_since(t: std::time::SystemTime) -> std::time::Duration {
55 t.elapsed().unwrap_or_default()
56 }
57}
58
59#[cfg(shuttle)]
62pub mod sync {
63 pub use shuttle::sync::atomic;
64 #[allow(unused_imports)]
65 pub use shuttle::sync::{Arc, Barrier, Mutex, Weak};
66
67 pub mod mpsc {
72 pub use shuttle::sync::mpsc::{RecvTimeoutError, SyncSender};
73
74 pub struct Receiver<T> {
75 inner: shuttle::sync::mpsc::Receiver<T>,
76 }
77
78 unsafe impl<T: Send> Send for Receiver<T> {}
80
81 impl<T> Receiver<T> {
82 pub fn recv_timeout(
83 &self,
84 _timeout: std::time::Duration,
85 ) -> Result<T, RecvTimeoutError> {
86 if shuttle::rand::thread_rng().gen_bool(0.8) {
89 match self.inner.try_recv() {
90 Ok(val) => Ok(val),
91 Err(shuttle::sync::mpsc::TryRecvError::Empty) => {
92 Err(RecvTimeoutError::Timeout)
93 }
94 Err(shuttle::sync::mpsc::TryRecvError::Disconnected) => {
95 Err(RecvTimeoutError::Disconnected)
96 }
97 }
98 } else {
99 self.inner
102 .recv()
103 .map_err(|_| RecvTimeoutError::Disconnected)
104 }
105 }
106
107 pub fn recv(&self) -> Result<T, shuttle::sync::mpsc::RecvError> {
108 self.inner.recv()
109 }
110 }
111
112 use shuttle::rand::Rng;
113
114 pub fn sync_channel<T>(bound: usize) -> (SyncSender<T>, Receiver<T>) {
116 let (tx, rx) = shuttle::sync::mpsc::sync_channel(bound);
117 (tx, Receiver { inner: rx })
118 }
119 }
120}
121
122#[cfg(shuttle)]
123pub mod thread {
124 #[allow(unused_imports)]
125 pub use shuttle::thread::{JoinHandle, sleep, spawn};
126
127 pub fn spawn_named<F, T>(_name: &str, f: F) -> JoinHandle<T>
128 where
129 F: FnOnce() -> T + Send + 'static,
130 T: Send + 'static,
131 {
132 spawn(f)
133 }
134}
135
136#[cfg(shuttle)]
137#[macro_export]
138macro_rules! define_thread_local {
139 ($($tt:tt)*) => { shuttle::thread_local! { $($tt)* } };
140}
141#[cfg(shuttle)]
142pub use crate::define_thread_local as thread_local;
143
144#[cfg(all(shuttle, feature = "pipeline"))]
145pub mod time {
146 use std::cell::Cell;
147 use std::future::Future;
148 use std::pin::Pin;
149 use std::sync::atomic::{AtomicUsize, Ordering};
150 use std::task::{Context, Poll};
151
152 pub use tokio::time::Instant;
153
154 std::thread_local! {
159 static LOGICAL_CLOCK: (Instant, Cell<u64>) = (Instant::now(), Cell::new(0));
160 }
161
162 const LOGICAL_TICK: std::time::Duration = std::time::Duration::from_millis(10);
165
166 pub fn now() -> Instant {
167 LOGICAL_CLOCK.with(|(base, nanos)| *base + std::time::Duration::from_nanos(nanos.get()))
168 }
169
170 pub fn elapsed_since(_t: std::time::SystemTime) -> std::time::Duration {
175 std::time::Duration::ZERO
176 }
177
178 pub fn sleep(_duration: std::time::Duration) -> Yield {
181 Yield::default()
182 }
183
184 pub fn sleep_until(_deadline: Instant) -> Yield {
185 Yield::default()
186 }
187
188 #[derive(Debug, Default)]
189 pub struct Yield {
190 yielded: bool,
191 }
192
193 static YIELD_PENDING_POLLS: AtomicUsize = AtomicUsize::new(0);
197
198 pub fn take_yield_pending_polls() -> usize {
205 YIELD_PENDING_POLLS.swap(0, Ordering::Relaxed)
206 }
207
208 impl Future for Yield {
209 type Output = ();
210 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
211 if self.yielded {
212 Poll::Ready(())
213 } else {
214 self.yielded = true;
215 YIELD_PENDING_POLLS.fetch_add(1, Ordering::Relaxed);
216 LOGICAL_CLOCK
217 .with(|(_, nanos)| nanos.set(nanos.get() + LOGICAL_TICK.as_nanos() as u64));
218 cx.waker().wake_by_ref();
219 Poll::Pending
220 }
221 }
222 }
223
224 pub fn timeout<F: Future + Unpin>(_duration: std::time::Duration, future: F) -> Timeout<F> {
229 Timeout { future }
230 }
231
232 #[derive(Debug)]
233 pub struct Elapsed(());
234
235 pub struct Timeout<F> {
236 future: F,
237 }
238
239 const FIRE_PROBABILITY_PER_PENDING_POLL: f64 = 0.02;
242
243 impl<F: Future + Unpin> Future for Timeout<F> {
244 type Output = Result<F::Output, Elapsed>;
245 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
246 use shuttle::rand::Rng;
247 match Pin::new(&mut self.future).poll(cx) {
248 Poll::Ready(v) => Poll::Ready(Ok(v)),
249 Poll::Pending => {
250 if shuttle::rand::thread_rng().gen_bool(FIRE_PROBABILITY_PER_PENDING_POLL) {
251 Poll::Ready(Err(Elapsed(())))
252 } else {
253 Poll::Pending
254 }
255 }
256 }
257 }
258 }
259}
260
261#[cfg(all(shuttle, feature = "pipeline"))]
270#[macro_export]
271macro_rules! shuttle_select {
272 ($($arms:tt)*) => {
273 shuttle_tokio_impl_inner::select! { $($arms)* }
274 };
275}
276#[cfg(all(not(shuttle), feature = "pipeline"))]
277#[macro_export]
278macro_rules! shuttle_select {
279 ($($arms:tt)*) => {
280 tokio::select! { $($arms)* }
281 };
282}
283#[cfg(feature = "pipeline")]
284pub use crate::shuttle_select;
285
286#[cfg(all(not(shuttle), feature = "pipeline"))]
291pub mod runtime {
292 pub use tokio::runtime::Builder;
293}
294#[cfg(all(shuttle, feature = "pipeline"))]
295pub mod runtime {
296 pub use shuttle_tokio_impl_inner::runtime::Builder;
297}
298
299#[cfg(shuttle)]
303pub const SHUTTLE_TOKIO_STACK_SIZE: usize = 0x000F_0000;
304
305#[cfg(shuttle)]
348#[macro_export]
349macro_rules! shuttle_test {
350 (default; $(#[$attr:meta])* fn $name:ident() $body:block) => {
353 $crate::shuttle_test! {
354 num_iters = 5_000, depth = 3;
355 $(#[$attr])* fn $name() $body
356 }
357 };
358 (default, determinism_only; $(#[$attr:meta])* fn $name:ident() $body:block) => {
359 $crate::shuttle_test! {
360 num_iters = 100, determinism_only;
361 $(#[$attr])* fn $name() $body
362 }
363 };
364 (default, verify_faults_triggered; $(#[$attr:meta])* fn $name:ident() $body:block) => {
365 $crate::shuttle_test! {
366 num_iters = 10_000, depth = 3, verify_faults_triggered;
367 $(#[$attr])* fn $name() $body
368 }
369 };
370 (num_iters = $num_iters:expr, depth = $depth:expr; $(#[$attr:meta])* fn $name:ident() $body:block) => {
371 mod $name {
372 use super::*;
373
374 $(#[$attr])*
375 fn $name() $body
376
377 #[test]
378 fn pct() {
379 shuttle::check_pct($name, $num_iters, $depth);
380 }
381
382 #[test]
383 fn determinism() {
384 shuttle::check_uncontrolled_nondeterminism($name, $num_iters);
385 }
386 }
387 };
388 (num_iters = $num_iters:expr, determinism_only; $(#[$attr:meta])* fn $name:ident() $body:block) => {
394 mod $name {
395 use super::*;
396
397 $(#[$attr])*
398 fn $name() $body
399
400 #[test]
401 fn determinism() {
402 shuttle::check_uncontrolled_nondeterminism($name, $num_iters);
403 }
404 }
405 };
406 (num_iters = $num_iters:expr, depth = $depth:expr, should_panic $(, expect_panic = $msg:expr)? $(, replay = $schedule:expr)?; $(#[$attr:meta])* fn $name:ident() $body:block) => {
407 mod $name {
408 use super::*;
409
410 $(#[$attr])*
411 fn $name() $body
412
413 #[test]
414 #[should_panic $((expected = $msg))?]
415 fn pct() {
416 shuttle::check_pct($name, $num_iters, $depth);
417 }
418
419 #[test]
420 #[should_panic $((expected = $msg))?]
421 fn determinism() {
422 shuttle::check_uncontrolled_nondeterminism($name, $num_iters);
423 }
424
425 $(
426 #[test]
432 #[should_panic]
433 fn replay_known_failure() {
434 shuttle::replay($name, $schedule);
435 }
436 )?
437 }
438 };
439 (num_iters = $num_iters:expr, depth = $depth:expr, should_panic, flaky_sigabrt_determinism_only $(, expect_panic = $msg:expr)? $(, replay = $schedule:expr)?; $(#[$attr:meta])* fn $name:ident() $body:block) => {
460 mod $name {
461 use super::*;
462
463 $(#[$attr])*
464 fn $name() $body
465
466 #[test]
467 #[should_panic $((expected = $msg))?]
468 fn pct() {
469 shuttle::check_pct($name, $num_iters, $depth);
470 }
471
472 #[test]
473 #[should_panic $((expected = $msg))?]
474 #[ignore = "can SIGABRT the whole process under shuttle -- see shuttle_test!'s flaky_sigabrt_determinism_only arm; run manually with --ignored"]
475 fn determinism() {
476 shuttle::check_uncontrolled_nondeterminism($name, $num_iters);
477 }
478
479 $(
480 #[test]
486 #[should_panic]
487 fn replay_known_failure() {
488 shuttle::replay($name, $schedule);
489 }
490 )?
491 }
492 };
493 (num_iters = $num_iters:expr, depth = $depth:expr, verify_faults_triggered; $(#[$attr:meta])* fn $name:ident() $body:block) => {
499 mod $name {
500 use super::*;
501
502 $(#[$attr])*
503 fn $name() $body
504
505 fn assert_faults_were_triggered() {
506 assert!(
507 $crate::primitives::fs::take_faults_triggered() > 0,
508 "no run across {} iterations triggered a single fault; fault injection is \
509 not reaching the flush thread (e.g. a broken fault-visibility thread-local), \
510 so this test is not exercising any error path.",
511 $num_iters,
512 );
513 }
514
515 #[test]
516 fn pct() {
517 $crate::primitives::fs::take_faults_triggered(); shuttle::check_pct($name, $num_iters, $depth);
519 assert_faults_were_triggered();
520 }
521
522 #[test]
523 fn determinism() {
524 $crate::primitives::fs::take_faults_triggered(); shuttle::check_uncontrolled_nondeterminism($name, $num_iters);
526 assert_faults_were_triggered();
527 }
528 }
529 };
530 (num_iters = $num_iters:expr, depth = $depth:expr, stack_size = $stack_size:expr; $(#[$attr:meta])* fn $name:ident() $body:block) => {
534 mod $name {
535 use super::*;
536
537 $(#[$attr])*
538 fn $name() $body
539
540 fn config() -> shuttle::Config {
541 let mut config = shuttle::Config::new();
542 config.stack_size = $stack_size;
543 config
544 }
545
546 #[test]
547 fn pct() {
548 use shuttle::scheduler::PctScheduler;
549 let scheduler = PctScheduler::new($depth, $num_iters);
550 shuttle::Runner::new(scheduler, config()).run($name);
551 }
552
553 #[test]
554 fn determinism() {
555 use shuttle::scheduler::{RandomScheduler, UncontrolledNondeterminismCheckScheduler};
556 let scheduler =
557 UncontrolledNondeterminismCheckScheduler::new(RandomScheduler::new($num_iters));
558 shuttle::Runner::new(scheduler, config()).run($name);
559 }
560 }
561 };
562}
563
564#[cfg(not(shuttle))]
565pub mod fs {
566 use std::io::{self, Write};
567 use std::path::Path;
568
569 pub fn create_dir_all(path: &Path) -> io::Result<()> {
570 std::fs::create_dir_all(path)
571 }
572 pub fn rename(from: &Path, to: &Path) -> io::Result<()> {
573 std::fs::rename(from, to)
574 }
575 pub fn remove_file(path: &Path) -> io::Result<()> {
576 std::fs::remove_file(path)
577 }
578 pub fn remove_dir(path: &Path) -> io::Result<()> {
579 std::fs::remove_dir(path)
580 }
581 pub fn read_dir(path: &Path) -> io::Result<std::fs::ReadDir> {
582 std::fs::read_dir(path)
583 }
584 pub fn metadata(path: &Path) -> io::Result<std::fs::Metadata> {
585 std::fs::metadata(path)
586 }
587 pub fn read(path: &Path) -> io::Result<Vec<u8>> {
588 std::fs::read(path)
589 }
590
591 #[derive(Debug)]
593 pub struct File(std::fs::File);
594
595 impl File {
596 pub fn create(path: &Path) -> io::Result<File> {
597 std::fs::File::create(path).map(File)
598 }
599 }
600
601 impl Write for File {
602 #[inline]
603 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
604 self.0.write(buf)
605 }
606 #[inline]
607 fn flush(&mut self) -> io::Result<()> {
608 self.0.flush()
609 }
610 }
611}
612
613#[cfg(shuttle)]
614pub mod fs {
615 use std::cell::Cell;
616 use std::io::{self, ErrorKind, Write};
617 use std::path::Path;
618
619 use shuttle::rand::Rng;
620
621 #[derive(Clone, Copy, Debug)]
623 pub enum FaultPolicy {
624 None,
626 FailAll,
628 FailProb(f64),
631 }
632
633 std::thread_local! {
638 static FAULT: Cell<FaultPolicy> = const { Cell::new(FaultPolicy::None) };
639 }
640
641 #[must_use]
644 pub fn set_fault(policy: FaultPolicy) -> FaultGuard {
645 let prev = FAULT.with(|f| f.replace(policy));
646 FaultGuard { prev }
647 }
648
649 pub struct FaultGuard {
650 prev: FaultPolicy,
651 }
652
653 impl Drop for FaultGuard {
654 fn drop(&mut self) {
655 FAULT.with(|f| f.set(self.prev));
656 }
657 }
658
659 fn check() -> io::Result<()> {
660 let fail = match FAULT.with(|f| f.get()) {
661 FaultPolicy::None => false,
662 FaultPolicy::FailAll => true,
663 FaultPolicy::FailProb(p) => shuttle::rand::thread_rng().gen_bool(p),
664 };
665 if fail {
666 Err(io::Error::from(ErrorKind::PermissionDenied))
667 } else {
668 Ok(())
669 }
670 }
671
672 pub fn create_dir_all(path: &Path) -> io::Result<()> {
673 check()?;
674 std::fs::create_dir_all(path)
675 }
676 pub fn rename(from: &Path, to: &Path) -> io::Result<()> {
677 check()?;
678 std::fs::rename(from, to)
679 }
680 pub fn remove_file(path: &Path) -> io::Result<()> {
681 check()?;
682 std::fs::remove_file(path)
683 }
684 pub fn remove_dir(path: &Path) -> io::Result<()> {
685 check()?;
686 std::fs::remove_dir(path)
687 }
688 pub fn read_dir(path: &Path) -> io::Result<std::fs::ReadDir> {
689 check()?;
690 std::fs::read_dir(path)
691 }
692 pub fn metadata(path: &Path) -> io::Result<std::fs::Metadata> {
693 check()?;
694 std::fs::metadata(path)
695 }
696 pub fn read(path: &Path) -> io::Result<Vec<u8>> {
697 check()?;
698 std::fs::read(path)
699 }
700
701 #[derive(Debug)]
704 pub struct File(std::fs::File);
705
706 impl File {
707 pub fn create(path: &Path) -> io::Result<File> {
708 std::fs::File::create(path).map(File)
709 }
710 }
711
712 impl Write for File {
713 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
714 check()?;
715 self.0.write(buf)
716 }
717 fn flush(&mut self) -> io::Result<()> {
718 check()?;
719 self.0.flush()
720 }
721 }
722}
723
724#[cfg(not(shuttle))]
730pub struct BoundedQueue<T> {
731 inner: crossbeam_queue::ArrayQueue<T>,
732}
733
734#[cfg(not(shuttle))]
735impl<T> std::fmt::Debug for BoundedQueue<T> {
736 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
737 f.debug_struct("BoundedQueue")
738 .field("len", &self.inner.len())
739 .field("capacity", &self.inner.capacity())
740 .finish()
741 }
742}
743
744#[cfg(not(shuttle))]
745impl<T> BoundedQueue<T> {
746 pub fn new(capacity: usize) -> Self {
747 Self {
748 inner: crossbeam_queue::ArrayQueue::new(capacity),
749 }
750 }
751
752 pub fn force_push(&self, value: T) -> Option<T> {
754 self.inner.force_push(value)
755 }
756
757 pub fn pop(&self) -> Option<T> {
758 self.inner.pop()
759 }
760}
761
762#[cfg(shuttle)]
763pub struct BoundedQueue<T> {
764 inner: shuttle::sync::Mutex<std::collections::VecDeque<T>>,
765 capacity: usize,
766}
767
768#[cfg(shuttle)]
769impl<T> std::fmt::Debug for BoundedQueue<T> {
770 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
771 f.debug_struct("BoundedQueue")
772 .field("capacity", &self.capacity)
773 .finish_non_exhaustive()
774 }
775}
776
777#[cfg(shuttle)]
778impl<T> BoundedQueue<T> {
779 pub fn new(capacity: usize) -> Self {
780 Self {
781 inner: shuttle::sync::Mutex::new(std::collections::VecDeque::with_capacity(capacity)),
782 capacity,
783 }
784 }
785
786 pub fn force_push(&self, value: T) -> Option<T> {
787 let mut q = self.inner.lock().unwrap();
788 let evicted = if q.len() >= self.capacity {
789 q.pop_front()
790 } else {
791 None
792 };
793 q.push_back(value);
794 evicted
795 }
796
797 pub fn pop(&self) -> Option<T> {
798 self.inner.lock().unwrap().pop_front()
799 }
800}