diff options
author | Jade Lovelace <lix@jade.fyi> | 2024-06-14 19:13:53 -0700 |
---|---|---|
committer | Jade Lovelace <lix@jade.fyi> | 2024-06-18 15:11:31 -0700 |
commit | 3626738b9b801b2922b668ede7f7b83330d99018 (patch) | |
tree | aace3ea4cbf2f4e3b785f44b6383d980cbaffb2b | |
parent | ce2b48aa41ed8e6f3eed60a20e3e2afb244457b6 (diff) |
libutil: tidy Sync and fix its move constructor
There was a previously-unused move constructor that just called abort,
which makes no sense since it ought to just be `= delete` if you don't
want one (commit history says it was Eelco doing an optimistic
performance optimisation in 2016, so it probably would not pass review
today).
However, a Lock has some great reasons to be moved! You might need to
unlock it early, for instance, or give it to someone else. So we change
the move constructor to instead hollow out the moved-from object and
make it unusable.
Change-Id: Iff2a4c2f7ebd0a558c4866d4dfe526bc8558bed7
-rw-r--r-- | src/libutil/sync.hh | 76 |
1 files changed, 66 insertions, 10 deletions
diff --git a/src/libutil/sync.hh b/src/libutil/sync.hh index 47e4512b1..2c5424f2a 100644 --- a/src/libutil/sync.hh +++ b/src/libutil/sync.hh @@ -40,50 +40,106 @@ public: class Lock { private: + // Non-owning pointer. This would be an + // optional<reference_wrapper<Sync>> if it didn't break gdb accessing + // Lock values (as of 2024-06-15, gdb 14.2) Sync * s; std::unique_lock<M> lk; friend Sync; - Lock(Sync * s) : s(s), lk(s->mutex) { } + Lock(Sync &s) : s(&s), lk(s.mutex) { } + + inline void checkLockingInvariants() + { + assert(s); + assert(lk.owns_lock()); + } + public: - Lock(Lock && l) : s(l.s) { abort(); } + Lock(Lock && l) : s(l.s), lk(std::move(l.lk)) + { + l.s = nullptr; + } + + Lock & operator=(Lock && other) + { + if (this != &other) { + s = other.s; + lk = std::move(other.lk); + other.s = nullptr; + } + return *this; + } + Lock(const Lock & l) = delete; - ~Lock() { } - T * operator -> () { return &s->data; } - T & operator * () { return s->data; } + ~Lock() = default; + + T * operator -> () + { + checkLockingInvariants(); + return &s->data; + } + + T & operator * () + { + checkLockingInvariants(); + return s->data; + } + + /** + * Wait for the given condition variable with no timeout. + * + * May spuriously wake up. + */ void wait(std::condition_variable & cv) { - assert(s); + checkLockingInvariants(); cv.wait(lk); } + /** + * Wait for the given condition variable for a maximum elapsed time of \p duration. + * + * May spuriously wake up. + */ template<class Rep, class Period> std::cv_status wait_for(std::condition_variable & cv, const std::chrono::duration<Rep, Period> & duration) { - assert(s); + checkLockingInvariants(); return cv.wait_for(lk, duration); } + /** + * Wait for the given condition variable for a maximum elapsed time of \p duration. + * Calls \p pred to check if the wakeup should be heeded: \p pred + * returning false will ignore the wakeup. + */ template<class Rep, class Period, class Predicate> bool wait_for(std::condition_variable & cv, const std::chrono::duration<Rep, Period> & duration, Predicate pred) { - assert(s); + checkLockingInvariants(); return cv.wait_for(lk, duration, pred); } + /** + * Wait for the given condition variable or until the time point \p duration. + */ template<class Clock, class Duration> std::cv_status wait_until(std::condition_variable & cv, const std::chrono::time_point<Clock, Duration> & duration) { - assert(s); + checkLockingInvariants(); return cv.wait_until(lk, duration); } }; - Lock lock() { return Lock(this); } + /** + * Lock this Sync and return a RAII guard object. + */ + Lock lock() { return Lock(*this); } }; } |