1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
//! Hiatus is a concurrency debugging library for Rust. It allows you to sprinkle breakpoints
//! in your programs so that blocks of code execute in the order you choose. If you suspect that a
//! specific interleaving of blocks is buggy, you can use Hiatus to invoke that ordering and
//! confirm the existence of the bug.
//!
//! This library is **experimental**!
use lazy_static::lazy_static;
use parking_lot::{Condvar, Mutex, MutexGuard};
use std::sync::atomic::{AtomicBool, Ordering};

lazy_static! {
    static ref CURRENT_STEP: Mutex<u64> = Mutex::new(1);
    static ref CONDVAR: Condvar = Condvar::new();
    static ref ENABLED: AtomicBool = AtomicBool::new(false);
}

/// Breakpoint object returned by `step`, with drop semantics.
///
/// See the docs for [`step`](./fn.step.html) for usage.
#[must_use]
pub enum Step<'a> {
    /// Step variant used when Hiatus is enabled.
    Real {
        n: u64,
        current_step: MutexGuard<'a, u64>,
    },
    /// Step variant used when Hiatus is disabled.
    Dummy,
}

/// Enable Hiatus (it is disabled by default).
///
/// You should call `enable` when your program has initialised and you are ready for it
/// to start executing according to the breakpoints you inserted using [`step`](./fn.step.html).
pub fn enable() {
    ENABLED.store(true, Ordering::SeqCst)
}

/// Disable Hiatus, causing future `step` calls to do nothing.
pub fn disable() {
    ENABLED.store(false, Ordering::SeqCst)
}

/// Check whether Hiatus is currently enabled.
///
/// Hiatus is disabled by default and needs to be enabled by calling [`enable`](./fn.enable.html).
pub fn is_enabled() -> bool {
    ENABLED.load(Ordering::SeqCst)
}

/// Set a breakpoint in your program to control its execution.
///
/// Calling `step(n)` will block the program until all previous `step` calls have resolved.
///
/// The first step of your program should be `step(1)`.
///
/// ## How it works
///
/// Hiatus maintains a global step counter, which is incremented each time a `Step` object
/// returned by `step` is dropped. When `step(n)` is called, it blocks the current thread
/// until the global step count is equal to `n`, at which point that thread is unblocked
/// and allowed to execute. To signal to other threads that the current step is complete,
/// you should drop the `Step` object. You _can_ do this immediately if you want, or you can
/// wait until some block of code has finished executing.
/// See [`Step::then`](./enum.Step.html#method.then).
///
/// ## Warnings
///
/// Make sure you enable Hiatus by calling [`enable`](./fn.enable.html) first!
///
/// It's probably not a good idea to have multiple calls for the same step count in the
/// same program. It likely won't do anything sensible (I haven't tried it).
pub fn step<'a>(n: u64) -> Step<'a> {
    assert_ne!(n, 0, "steps start from 1");
    if is_enabled() {
        real_step(n)
    } else {
        Step::Dummy
    }
}

fn real_step<'a>(n: u64) -> Step<'a> {
    // Use the condition variable to wait for the step count to reach `n`.
    let mut current_step = CURRENT_STEP.lock();
    while *current_step != n {
        CONDVAR.wait(&mut current_step);
    }
    // Step count has reached `n`, and we hold the mutex.
    // Return the value and let the caller execute their critical section.
    // When they're done, they should drop the `Step` to indicate that the next step is
    // allowed to run.
    Step::Real { n, current_step }
}

impl<'a> Step<'a> {
    /// Shorthand for dropping this step and moving to a new step `n`.
    pub fn then(self, n: u64) -> Step<'a> {
        drop(self);
        step(n)
    }
}

impl<'a> Drop for Step<'a> {
    /// Increment the global step count, and signal the condition variable to wake up waiters.
    fn drop(&mut self) {
        if let Step::Real { current_step, .. } = self {
            // Increment the step count.
            **current_step += 1;
            // Signal all the other waiters (a little inefficient -- but the alternative is one
            // condition variable per step, which seems unwieldy).
            CONDVAR.notify_all();
        }
    }
}