upgrade_mutex icon indicating copy to clipboard operation
upgrade_mutex copied to clipboard

Fix bugs and improve upgrade_mutex

Open austin-beer opened this issue 3 years ago • 0 comments

I recently took another look at this upgrade_mutex implementation. This is a good implementation and it seems a shame that it isn't in the C++ standard library and probably won't be added anytime soon. However, in case it is reconsidered for addition at some point in the future, I believe the following changes make this upgrade_mutex implementation simpler, more robust, and more likely to be accepted by a C++ standards committee. I welcome your feedback.

  1. Remove dangerous functions:

    1. Remove try_unlock_shared_and_lock_until/for() from upgrade_mutex. These functions make it easy to create code that deadlocks and so should not be provided or implemented.

    2. Remove try_unlock_shared_and_lock_upgrade_until/for() from upgrade_mutex. These functions make it easy to create code that deadlocks and so should not be provided or implemented.

    3. Remove the associated timed upgrade_lock constructors that create an upgrade lock from a shared lock since they rely on the above functions.

    4. Eliminate the following bug due to removing try_unlock_shared_and_lock_until/for():

      • Two shared locks are being held.
      • One of the threads calls try_unlock_shared_and_lock_until() and blocks waiting on gate2_.
      • The other thread releases via unlock_shared().
      • Because write_entered_ is false and num_readers is 1, unlock_shared() does not call gate2_.notify_one() and so the blocked thread does not proceed even though it now can.
    5. Eliminate the following bug due to removing try_unlock_shared_and_lock_upgrade_until/for():

      • Two shared locks are being held.
      • One of the two shared locks is upgradable.
      • The non-upgradable thread calls try_unlock_shared_and_lock_upgrade_until() and blocks waiting on gate2_.
      • The upgradable lock releases via unlock_upgrade().
      • Because unlock_upgrade() calls gate1_.notify_all() but try_unlock_shared_and_lock_upgrade_until() is blocked on gate2_, the blocked thread does not proceed even though it now can.
  2. Change behavior of try_unlock_upgrade_and_lock_until/for():

    1. Change try_unlock_upgrade_and_lock_until() to take write_entered_ whenever it is called so that it prevents new shared locks from being taken. This matches the behavior of unlock_upgrade_and_lock(), and it ensures that the function blocks no longer than necessary.

    2. Fix the following bug:

      • Two shared locks are being held.
      • One of the two shared locks is upgradable.
      • The upgradable thread calls try_unlock_upgrade_and_lock_until() and blocks waiting on gate2_.
      • The other thread releases via unlock_shared().
      • Because write_entered_ is false and num_readers is 1, unlock_shared() does not call gate2_.notify_one() and so the blocked thread does not proceed even though it now can.
  3. Fix bug in unlock_shared():

    • The maximum number of shared locks are being held.
    • One of the shared locks is upgradable.
    • A thread has called lock() and blocks waiting on gate1_ for the shared upgradable lock to release.
    • A thread has called lock_shared() and blocks waiting on gate1_ for a a shared lock to release.
    • One of the threads releases via unlock_shared() which calls gate1_.notify_one().
    • One of two things happens:
      • The thread calling lock_shared() receives the signal and takes a shared lock.
      • The thread calling lock() receives the signal and does nothing because there is still an upgradable lock. The thread calling lock_shared() does nothing as well. This is a lost wake-up.
    • This behavior is inconsistent. The thread calling lock_shared() should always take a shared lock in this (admittedly rare) situation.
  4. Fix bug in try_lock_until():

    • One shared lock is being held.
    • A thread calls try_lock_until() and blocks waiting for the shared lock to release.
    • A second thread calls lock_shared() and blocks waiting on gate1_ for try_lock_until() to either time out or complete and then release.
    • try_lock_until() times out and exits without taking the lock, but lock_shared() does not proceed because gate1_ was never notified.
  5. Clean up code:

    1. Add brackets around single-line if/else/while blocks.

    2. Change < to != where functionally equivalent for consistency.

    3. Change == 0 to ! where functionally equivalent for consistency.

    4. Add a few explanatory comments.

    5. Remove trailing spaces.

austin-beer avatar Feb 21 '22 20:02 austin-beer