Blog Home

Sleeping != Synchronization

2008-08-21

Beware of sleep. sleep should never be used for synchronization, or in test.

Bad

class CoffeeMaker {
 public:
  virtual ~CoffeeMaker() = default;
  virtual void MakeCoffee(const std::function<void()> callback) = 0;
};

class Intern : public CoffeeMaker {
 public:
  void MakeCoffee(const std::function<void()> callback) {
    // make coffee, hopefully within 60 seconds.
    callback();
  }
};

class Employee {
 public:
  void DrinkCoffee() { caffeinated_ = true; }
  bool IsCaffeinated() { return caffeinated_; }

  void DemandCoffee(CoffeeMaker& cm) {
    std::thread t(&CoffeeMaker::MakeCoffee, &cm,
                  std::bind(&Employee::DrinkCoffee, this));
    t.detach();
  }

 private:
  bool caffeinated_ = false;
};

TEST(EmployeeTest, ShouldBeCaffeinatedOnlyAfterDrinkingCoffee) {
  Employee e;
  Intern i;
  e.DemandCoffee(i);
  EXPECT_FALSE(e.IsCaffeinated());
  std::this_thread::sleep_for(60s);
  EXPECT_TRUE(e.IsCaffeinated());
}

Code that sleeps can be improved by waiting on a std::future or a std::condition_variable. As always, if your waiting on a non-trivial operation, like Intern::MakeCoffee, use a fake.

Good

class FakeIntern : public CoffeeMaker {
 public:
  void MakeCoffee(const std::function<void()> callback) {
    std::unique_lock<std::mutex> lock(mut_);
    cv_.wait(lock, [this] { return ready_; });

    callback();
    done_ = true;

    lock.unlock();
    cv_.notify_one();
  }

  void SignalAndWait() {
    std::unique_lock<std::mutex> lock(mut_);
    ready_ = true;
    cv_.notify_one();

    cv_.wait(lock, [this] { return done_; });
  }

 private:
  bool ready_ = false;
  bool done_ = false;
  std::condition_variable cv_;
  std::mutex mut_;
};

TEST(EmployeeTest, ShouldBeCaffeinatedOnlyAfterDrinkingCoffee) {
  Employee e;
  FakeIntern i;
  e.DemandCoffee(i);
  EXPECT_FALSE(e.IsCaffeinated());
  i.SignalAndWait();
  EXPECT_TRUE(e.IsCaffeinated());
}