Create ReadHolder::unlock
authorAndrew Birchall <abirchall@fb.com>
Thu, 28 Apr 2016 23:39:10 +0000 (16:39 -0700)
committerFacebook Github Bot 8 <facebook-github-bot-8-bot@fb.com>
Thu, 28 Apr 2016 23:50:30 +0000 (16:50 -0700)
commit95e7a3bad41357c9f78d0a993b542b002f17e00e
tree0911b00cbd444ac028b83ad74178dab54f441121
parentab389fe4f77be8d4e25796d2bb6e8f491018f17e
Create ReadHolder::unlock

Summary:
Currently you need to depend on the destructor of `ReadHolder` (using closures as in code block #1 below or empty assignment as in code block #2 below) to ensure that a `ReadHolder` goes out of scope (and unlocks) in order to subsequently acquire a write lock via `WriteHolder` without deadlocking.
This diff introduces a way of unlocking a `ReadHolder` while it's still in scope such that a `WriteHolder` can be acquired. This makes the code more straight forward (reducing the risk of deadlock due to a programmer's misunderstanding of how `SharedMutex` / the holder framework works) => see code block # 3 below
Also add some documentation about why `WriteHolder::WriteHolder(ReadHolder&&)` doesn't exist

Code Block #1 : Use of closures
```
class foo {
 public:
  std::string getMemoizedData() {
    {
      folly::SharedMutex::ReadHolder readHolder(lock_);
      if (!data_.empty()) {
        // important to return by value, otherwise caller might access
        // data_ after we release the read lock
        return data_;
      }
    }
    {
      // try again with a write lock
      folly::SharedMutex::WriteHolder writeHolder(lock_);
      if (data_.empty()) {
        data_ = "my awesome string";
      }
      return data_;
    }
  }

 private:
  folly::SharedMutex lock_;
  std::string data_;
};
```

Code Block #2 : Use of empty assignment
```
class foo {
 public:
  std::string getMemoizedData() {
    folly::SharedMutex::ReadHolder readHolder(lock_);
    if (!data_.empty()) {
      // important to return by value, otherwise caller might access
      // data_ after we release the read lock
      return data_;
    }
    readHolder = {};

    // try again with a write lock
    folly::SharedMutex::WriteHolder writeHolder(lock_);
    if (data_.empty()) {
      data_ = "my awesome string";
    }
    return data_;
  }

 private:
  folly::SharedMutex lock_;
  std::string data_;
};
```

Code Block #3 : Use of unlock()
```
class foo {
 public:
  std::string getMemoizedData() {
    folly::SharedMutex::ReadHolder readHolder(lock_);
    if (!data_.empty()) {
      // important to return by value, otherwise caller might access
      // data_ after we release the read lock
      return data_;
    }
    readHolder->unlock();

    // try again with a write lock
    folly::SharedMutex::WriteHolder writeHolder(lock_);
    if (data_.empty()) {
      data_ = "my awesome string";
    }
    return data_;
  }

 private:
  folly::SharedMutex lock_;
  std::string data_;
};
```

Reviewed By: yfeldblum

Differential Revision: D3176025

fb-gh-sync-id: c7d47ca71df08673c7c1f1fd5ed9e01a663c1797
fbshipit-source-id: c7d47ca71df08673c7c1f1fd5ed9e01a663c1797
folly/SharedMutex.h
folly/test/SharedMutexTest.cpp