From: Shaft Wu Date: Wed, 11 Nov 2015 04:40:17 +0000 (-0800) Subject: UNSYNCHRONIZED does NOT unlock the mutex X-Git-Tag: deprecate-dynamic-initializer~263 X-Git-Url: http://plrg.eecs.uci.edu/git/?a=commitdiff_plain;h=6762f08b48558f5afbdce1a435ab0e82deab837b;p=folly.git UNSYNCHRONIZED does NOT unlock the mutex Summary: My colleague tuomaspelkonen discovered a weird UNSYNCHRONIZED issue a few weeks ago and we ever since stopped using it. Now I finally have some time to root cause it. It turns out UNSYNCHRONIZED unlock the mutex then lock the mutex again, because it copy constructs LockedPtr for overriding the name within the scope, and copy construct locks the mutex again. A one character fix here is to take a reference of LockedPtr instead of copy construct it. However since this is my first time look at the code here, please advise if this is horribly wrong or propose better fix. Also added a test to reproduce the issue without the fix as well as verify the fix. Reviewed By: yfeldblum Differential Revision: D2633028 fb-gh-sync-id: a9e8d39b08d4d1265979f8bdaae83619566d10a0 --- diff --git a/folly/Synchronized.h b/folly/Synchronized.h index 8a2f9b95..109cea7c 100644 --- a/folly/Synchronized.h +++ b/folly/Synchronized.h @@ -722,7 +722,7 @@ void swap(Synchronized& lhs, Synchronized& rhs) { for (decltype(SYNCHRONIZED_lockedPtr.typeHackDoNotUse()) \ SYNCHRONIZED_state3(&SYNCHRONIZED_lockedPtr); \ !SYNCHRONIZED_state; SYNCHRONIZED_state = true) \ - for (auto name = *SYNCHRONIZED_state3.operator->(); \ + for (auto& name = *SYNCHRONIZED_state3.operator->(); \ !SYNCHRONIZED_state; SYNCHRONIZED_state = true) /** diff --git a/folly/test/SynchronizedTest.cpp b/folly/test/SynchronizedTest.cpp index afd87f91..d9a45d29 100644 --- a/folly/test/SynchronizedTest.cpp +++ b/folly/test/SynchronizedTest.cpp @@ -121,4 +121,110 @@ TYPED_TEST(SynchronizedTest, InPlaceConstruction) { testInPlaceConstruction(); } +using CountPair = std::pair; +// This class is specialized only to be uesed in SynchronizedLockTest +class FakeMutex { + public: + bool lock() { + ++lockCount_; + return true; + } + + bool unlock() { + ++unlockCount_; + return true; + } + + static CountPair getLockUnlockCount() { + return CountPair{lockCount_, unlockCount_}; + } + + static void resetLockUnlockCount() { + lockCount_ = 0; + unlockCount_ = 0; + } + private: + // Keep these two static for test access + // Keep them thread_local in case of tests are run in parallel within one + // process + static thread_local int lockCount_; + static thread_local int unlockCount_; + + // Adapters for Synchronized<> + friend void acquireReadWrite(FakeMutex& lock) { lock.lock(); } + friend void releaseReadWrite(FakeMutex& lock) { lock.unlock(); } +}; +thread_local int FakeMutex::lockCount_{0}; +thread_local int FakeMutex::unlockCount_{0}; + +// SynchronizedLockTest is used to verify the correct lock unlock behavior +// happens per design +class SynchronizedLockTest : public testing::Test { + public: + void SetUp() override { + FakeMutex::resetLockUnlockCount(); + } +}; + +// Single level of SYNCHRONIZED and UNSYNCHRONIZED, although nested test are +// super set of it, it is possible single level test passes while nested tests +// fail +TEST_F(SynchronizedLockTest, SyncUnSync) { + folly::Synchronized, FakeMutex> obj; + EXPECT_EQ((CountPair{0, 0}), FakeMutex::getLockUnlockCount()); + SYNCHRONIZED(obj) { + EXPECT_EQ((CountPair{1, 0}), FakeMutex::getLockUnlockCount()); + UNSYNCHRONIZED(obj) { + EXPECT_EQ((CountPair{1, 1}), FakeMutex::getLockUnlockCount()); + } + EXPECT_EQ((CountPair{2, 1}), FakeMutex::getLockUnlockCount()); + } + EXPECT_EQ((CountPair{2, 2}), FakeMutex::getLockUnlockCount()); +} + +// Nested SYNCHRONIZED UNSYNCHRONIZED test, 2 levels for each are used here +TEST_F(SynchronizedLockTest, NestedSyncUnSync) { + folly::Synchronized, FakeMutex> obj; + EXPECT_EQ((CountPair{0, 0}), FakeMutex::getLockUnlockCount()); + SYNCHRONIZED(objCopy, obj) { + EXPECT_EQ((CountPair{1, 0}), FakeMutex::getLockUnlockCount()); + SYNCHRONIZED(obj) { + EXPECT_EQ((CountPair{2, 0}), FakeMutex::getLockUnlockCount()); + UNSYNCHRONIZED(obj) { + EXPECT_EQ((CountPair{2, 1}), FakeMutex::getLockUnlockCount()); + UNSYNCHRONIZED(obj) { + EXPECT_EQ((CountPair{2, 2}), + FakeMutex::getLockUnlockCount()); + } + EXPECT_EQ((CountPair{3, 2}), FakeMutex::getLockUnlockCount()); + } + EXPECT_EQ((CountPair{4, 2}), FakeMutex::getLockUnlockCount()); + } + EXPECT_EQ((CountPair{4, 3}), FakeMutex::getLockUnlockCount()); + } + EXPECT_EQ((CountPair{4, 4}), FakeMutex::getLockUnlockCount()); +} + +// Different nesting behavior, UNSYNCHRONIZED called on differen depth of +// SYNCHRONIZED +TEST_F(SynchronizedLockTest, NestedSyncUnSync2) { + folly::Synchronized, FakeMutex> obj; + EXPECT_EQ((CountPair{0, 0}), FakeMutex::getLockUnlockCount()); + SYNCHRONIZED(objCopy, obj) { + EXPECT_EQ((CountPair{1, 0}), FakeMutex::getLockUnlockCount()); + SYNCHRONIZED(obj) { + EXPECT_EQ((CountPair{2, 0}), FakeMutex::getLockUnlockCount()); + UNSYNCHRONIZED(obj) { + EXPECT_EQ((CountPair{2, 1}), FakeMutex::getLockUnlockCount()); + } + EXPECT_EQ((CountPair{3, 1}), FakeMutex::getLockUnlockCount()); + } + EXPECT_EQ((CountPair{3, 2}), FakeMutex::getLockUnlockCount()); + UNSYNCHRONIZED(obj) { + EXPECT_EQ((CountPair{3, 3}), FakeMutex::getLockUnlockCount()); + } + EXPECT_EQ((CountPair{4, 3}), FakeMutex::getLockUnlockCount()); + } + EXPECT_EQ((CountPair{4, 4}), FakeMutex::getLockUnlockCount()); +} }