453 lines
12 KiB
C++
453 lines
12 KiB
C++
// Copyright 2016 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "base/task/common/checked_lock.h"
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include "base/compiler_specific.h"
|
|
#include "base/memory/raw_ptr.h"
|
|
#include "base/rand_util.h"
|
|
#include "base/synchronization/waitable_event.h"
|
|
#include "base/test/gtest_util.h"
|
|
#include "base/threading/platform_thread.h"
|
|
#include "base/threading/simple_thread.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
|
|
namespace base {
|
|
namespace internal {
|
|
namespace {
|
|
|
|
// Adapted from base::Lock's BasicLockTestThread to make sure
|
|
// Acquire()/Release() don't crash.
|
|
class BasicLockTestThread : public SimpleThread {
|
|
public:
|
|
explicit BasicLockTestThread(CheckedLock* lock)
|
|
: SimpleThread("BasicLockTestThread"), lock_(lock), acquired_(0) {}
|
|
|
|
BasicLockTestThread(const BasicLockTestThread&) = delete;
|
|
BasicLockTestThread& operator=(const BasicLockTestThread&) = delete;
|
|
|
|
int acquired() const { return acquired_; }
|
|
|
|
private:
|
|
void Run() override {
|
|
for (int i = 0; i < 10; i++) {
|
|
lock_->Acquire();
|
|
acquired_++;
|
|
lock_->Release();
|
|
}
|
|
for (int i = 0; i < 10; i++) {
|
|
lock_->Acquire();
|
|
acquired_++;
|
|
PlatformThread::Sleep(Milliseconds(base::RandInt(0, 19)));
|
|
lock_->Release();
|
|
}
|
|
}
|
|
|
|
const raw_ptr<CheckedLock> lock_;
|
|
int acquired_;
|
|
};
|
|
|
|
class BasicLockAcquireAndWaitThread : public SimpleThread {
|
|
public:
|
|
explicit BasicLockAcquireAndWaitThread(CheckedLock* lock)
|
|
: SimpleThread("BasicLockAcquireAndWaitThread"),
|
|
lock_(lock),
|
|
lock_acquire_event_(WaitableEvent::ResetPolicy::AUTOMATIC,
|
|
WaitableEvent::InitialState::NOT_SIGNALED),
|
|
main_thread_continue_event_(WaitableEvent::ResetPolicy::AUTOMATIC,
|
|
WaitableEvent::InitialState::NOT_SIGNALED) {
|
|
}
|
|
|
|
BasicLockAcquireAndWaitThread(const BasicLockAcquireAndWaitThread&) = delete;
|
|
BasicLockAcquireAndWaitThread& operator=(
|
|
const BasicLockAcquireAndWaitThread&) = delete;
|
|
|
|
void WaitForLockAcquisition() { lock_acquire_event_.Wait(); }
|
|
|
|
void ContinueMain() { main_thread_continue_event_.Signal(); }
|
|
|
|
private:
|
|
void Run() override {
|
|
lock_->Acquire();
|
|
lock_acquire_event_.Signal();
|
|
main_thread_continue_event_.Wait();
|
|
lock_->Release();
|
|
}
|
|
|
|
const raw_ptr<CheckedLock> lock_;
|
|
WaitableEvent lock_acquire_event_;
|
|
WaitableEvent main_thread_continue_event_;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
TEST(CheckedLockTest, Basic) {
|
|
CheckedLock lock;
|
|
BasicLockTestThread thread(&lock);
|
|
|
|
thread.Start();
|
|
|
|
int acquired = 0;
|
|
for (int i = 0; i < 5; i++) {
|
|
lock.Acquire();
|
|
acquired++;
|
|
lock.Release();
|
|
}
|
|
for (int i = 0; i < 10; i++) {
|
|
lock.Acquire();
|
|
acquired++;
|
|
PlatformThread::Sleep(Milliseconds(base::RandInt(0, 19)));
|
|
lock.Release();
|
|
}
|
|
for (int i = 0; i < 5; i++) {
|
|
lock.Acquire();
|
|
acquired++;
|
|
PlatformThread::Sleep(Milliseconds(base::RandInt(0, 19)));
|
|
lock.Release();
|
|
}
|
|
|
|
thread.Join();
|
|
|
|
EXPECT_EQ(acquired, 20);
|
|
EXPECT_EQ(thread.acquired(), 20);
|
|
}
|
|
|
|
TEST(CheckedLockTest, AcquirePredecessor) {
|
|
CheckedLock predecessor;
|
|
CheckedLock lock(&predecessor);
|
|
predecessor.Acquire();
|
|
lock.Acquire();
|
|
lock.Release();
|
|
predecessor.Release();
|
|
}
|
|
|
|
// Here and below, disable thread safety analysis, otherwise our death tests do
|
|
// not compile (the issues are caught at compile time).
|
|
TEST(CheckedLockTest, AcquirePredecessorWrongOrder)
|
|
NO_THREAD_SAFETY_ANALYSIS {
|
|
CheckedLock predecessor;
|
|
CheckedLock lock(&predecessor);
|
|
EXPECT_DCHECK_DEATH({
|
|
lock.Acquire();
|
|
predecessor.Acquire();
|
|
});
|
|
}
|
|
|
|
TEST(CheckedLockTest, AcquireNonPredecessor) NO_THREAD_SAFETY_ANALYSIS {
|
|
CheckedLock lock1;
|
|
CheckedLock lock2;
|
|
EXPECT_DCHECK_DEATH({
|
|
lock1.Acquire();
|
|
lock2.Acquire();
|
|
});
|
|
}
|
|
|
|
TEST(CheckedLockTest, AcquireMultipleLocksInOrder) {
|
|
CheckedLock lock1;
|
|
CheckedLock lock2(&lock1);
|
|
CheckedLock lock3(&lock2);
|
|
lock1.Acquire();
|
|
lock2.Acquire();
|
|
lock3.Acquire();
|
|
lock3.Release();
|
|
lock2.Release();
|
|
lock1.Release();
|
|
}
|
|
|
|
TEST(CheckedLockTest, AcquireMultipleLocksInTheMiddleOfAChain) {
|
|
CheckedLock lock1;
|
|
CheckedLock lock2(&lock1);
|
|
CheckedLock lock3(&lock2);
|
|
lock2.Acquire();
|
|
lock3.Acquire();
|
|
lock3.Release();
|
|
lock2.Release();
|
|
}
|
|
|
|
TEST(CheckedLockTest, AcquireMultipleLocksNoTransitivity)
|
|
NO_THREAD_SAFETY_ANALYSIS {
|
|
CheckedLock lock1;
|
|
CheckedLock lock2(&lock1);
|
|
CheckedLock lock3(&lock2);
|
|
EXPECT_DCHECK_DEATH({
|
|
lock1.Acquire();
|
|
lock3.Acquire();
|
|
});
|
|
}
|
|
|
|
TEST(CheckedLockTest, AcquireLocksDifferentThreadsSafely) {
|
|
CheckedLock lock1;
|
|
CheckedLock lock2;
|
|
BasicLockAcquireAndWaitThread thread(&lock1);
|
|
thread.Start();
|
|
|
|
lock2.Acquire();
|
|
thread.WaitForLockAcquisition();
|
|
thread.ContinueMain();
|
|
thread.Join();
|
|
lock2.Release();
|
|
}
|
|
|
|
TEST(CheckedLockTest,
|
|
AcquireLocksWithPredecessorDifferentThreadsSafelyPredecessorFirst) {
|
|
// A lock and its predecessor may be safely acquired on different threads.
|
|
// This Thread Other Thread
|
|
// predecessor.Acquire()
|
|
// lock.Acquire()
|
|
// predecessor.Release()
|
|
// lock.Release()
|
|
CheckedLock predecessor;
|
|
CheckedLock lock(&predecessor);
|
|
predecessor.Acquire();
|
|
BasicLockAcquireAndWaitThread thread(&lock);
|
|
thread.Start();
|
|
thread.WaitForLockAcquisition();
|
|
predecessor.Release();
|
|
thread.ContinueMain();
|
|
thread.Join();
|
|
}
|
|
|
|
TEST(CheckedLockTest,
|
|
AcquireLocksWithPredecessorDifferentThreadsSafelyPredecessorLast) {
|
|
// A lock and its predecessor may be safely acquired on different threads.
|
|
// This Thread Other Thread
|
|
// lock.Acquire()
|
|
// predecessor.Acquire()
|
|
// lock.Release()
|
|
// predecessor.Release()
|
|
CheckedLock predecessor;
|
|
CheckedLock lock(&predecessor);
|
|
lock.Acquire();
|
|
BasicLockAcquireAndWaitThread thread(&predecessor);
|
|
thread.Start();
|
|
thread.WaitForLockAcquisition();
|
|
lock.Release();
|
|
thread.ContinueMain();
|
|
thread.Join();
|
|
}
|
|
|
|
TEST(CheckedLockTest,
|
|
AcquireLocksWithPredecessorDifferentThreadsSafelyNoInterference) {
|
|
// Acquisition of an unrelated lock on another thread should not affect a
|
|
// legal lock acquisition with a predecessor on this thread.
|
|
// This Thread Other Thread
|
|
// predecessor.Acquire()
|
|
// unrelated.Acquire()
|
|
// lock.Acquire()
|
|
// unrelated.Release()
|
|
// lock.Release()
|
|
// predecessor.Release();
|
|
CheckedLock predecessor;
|
|
CheckedLock lock(&predecessor);
|
|
predecessor.Acquire();
|
|
CheckedLock unrelated;
|
|
BasicLockAcquireAndWaitThread thread(&unrelated);
|
|
thread.Start();
|
|
thread.WaitForLockAcquisition();
|
|
lock.Acquire();
|
|
thread.ContinueMain();
|
|
thread.Join();
|
|
lock.Release();
|
|
predecessor.Release();
|
|
}
|
|
|
|
TEST(CheckedLockTest, SelfReferentialLock) {
|
|
struct SelfReferentialLock {
|
|
SelfReferentialLock() : lock(&lock) {}
|
|
|
|
CheckedLock lock;
|
|
};
|
|
|
|
EXPECT_DCHECK_DEATH({ SelfReferentialLock lock; });
|
|
}
|
|
|
|
TEST(CheckedLockTest, PredecessorCycle) {
|
|
struct LockCycle {
|
|
LockCycle() : lock1(&lock2), lock2(&lock1) {}
|
|
|
|
CheckedLock lock1;
|
|
CheckedLock lock2;
|
|
};
|
|
|
|
EXPECT_DCHECK_DEATH({ LockCycle cycle; });
|
|
}
|
|
|
|
TEST(CheckedLockTest, PredecessorLongerCycle) {
|
|
struct LockCycle {
|
|
LockCycle()
|
|
: lock1(&lock5),
|
|
lock2(&lock1),
|
|
lock3(&lock2),
|
|
lock4(&lock3),
|
|
lock5(&lock4) {}
|
|
|
|
CheckedLock lock1;
|
|
CheckedLock lock2;
|
|
CheckedLock lock3;
|
|
CheckedLock lock4;
|
|
CheckedLock lock5;
|
|
};
|
|
|
|
EXPECT_DCHECK_DEATH({ LockCycle cycle; });
|
|
}
|
|
|
|
TEST(CheckedLockTest, AcquireLockAfterUniversalPredecessor) {
|
|
// Acquisition of a universal-predecessor lock should not prevent acquisition
|
|
// of a CheckedLock after it.
|
|
CheckedLock universal_predecessor((UniversalPredecessor()));
|
|
CheckedLock lock;
|
|
|
|
universal_predecessor.Acquire();
|
|
lock.Acquire();
|
|
lock.Release();
|
|
universal_predecessor.Release();
|
|
}
|
|
|
|
TEST(CheckedLockTest, AcquireMultipleLocksAfterUniversalPredecessor)
|
|
NO_THREAD_SAFETY_ANALYSIS {
|
|
// Acquisition of a universal-predecessor lock does not affect acquisition
|
|
// rules for locks beyond the one acquired directly after it.
|
|
CheckedLock universal_predecessor{UniversalPredecessor()};
|
|
CheckedLock lock;
|
|
CheckedLock lock2(&lock);
|
|
CheckedLock lock3;
|
|
|
|
universal_predecessor.Acquire();
|
|
lock.Acquire();
|
|
lock2.Acquire();
|
|
lock2.Release();
|
|
lock.Release();
|
|
universal_predecessor.Release();
|
|
|
|
EXPECT_DCHECK_DEATH({
|
|
universal_predecessor.Acquire();
|
|
lock.Acquire();
|
|
lock3.Acquire();
|
|
});
|
|
}
|
|
|
|
TEST(CheckedLockTest, AcquireUniversalPredecessorAfterLock)
|
|
NO_THREAD_SAFETY_ANALYSIS {
|
|
// A universal-predecessor lock may not be acquired after any other lock.
|
|
CheckedLock universal_predecessor{UniversalPredecessor()};
|
|
CheckedLock lock;
|
|
|
|
EXPECT_DCHECK_DEATH({
|
|
lock.Acquire();
|
|
universal_predecessor.Acquire();
|
|
});
|
|
}
|
|
|
|
TEST(CheckedLockTest, AcquireUniversalPredecessorAfterUniversalPredecessor)
|
|
NO_THREAD_SAFETY_ANALYSIS {
|
|
// A universal-predecessor lock may not be acquired after any other lock, not
|
|
// even another universal predecessor.
|
|
CheckedLock universal_predecessor{UniversalPredecessor()};
|
|
CheckedLock universal_predecessor2{UniversalPredecessor()};
|
|
|
|
EXPECT_DCHECK_DEATH({
|
|
universal_predecessor.Acquire();
|
|
universal_predecessor2.Acquire();
|
|
});
|
|
}
|
|
|
|
TEST(CheckedLockTest, AcquireLockBeforeUniversalSuccessor) {
|
|
// Acquisition of a universal-successor lock should be allowed
|
|
// after any other acquisition.
|
|
CheckedLock universal_successor{UniversalSuccessor()};
|
|
CheckedLock lock;
|
|
|
|
lock.Acquire();
|
|
universal_successor.Acquire();
|
|
universal_successor.Release();
|
|
lock.Release();
|
|
}
|
|
|
|
TEST(CheckedLockTest, AcquireMultipleLocksBeforeAndAfterUniversalSuccessor)
|
|
NO_THREAD_SAFETY_ANALYSIS {
|
|
// Acquisition of a universal-successor lock does not affect acquisition
|
|
// rules for locks beyond the one acquired directly after it.
|
|
CheckedLock lock;
|
|
CheckedLock universal_successor{UniversalSuccessor()};
|
|
CheckedLock lock2;
|
|
|
|
lock.Acquire();
|
|
universal_successor.Acquire();
|
|
universal_successor.Release();
|
|
lock.Release();
|
|
|
|
EXPECT_DCHECK_DEATH({
|
|
universal_successor.Acquire();
|
|
lock2.Acquire();
|
|
});
|
|
}
|
|
|
|
TEST(CheckedLockTest, AcquireUniversalSuccessorBeforeLock)
|
|
NO_THREAD_SAFETY_ANALYSIS {
|
|
// A universal-successor lock may not be acquired before any other lock.
|
|
CheckedLock universal_successor{UniversalSuccessor()};
|
|
CheckedLock lock;
|
|
|
|
EXPECT_DCHECK_DEATH({
|
|
universal_successor.Acquire();
|
|
lock.Acquire();
|
|
});
|
|
}
|
|
|
|
TEST(CheckedLockTest, AcquireUniversalSuccessorAfterUniversalSuccessor)
|
|
NO_THREAD_SAFETY_ANALYSIS {
|
|
// A universal-successor lock may not be acquired before any other lock, not
|
|
// even another universal successor.
|
|
CheckedLock universal_successor{UniversalSuccessor()};
|
|
CheckedLock universal_successor2{UniversalSuccessor()};
|
|
|
|
EXPECT_DCHECK_DEATH({
|
|
universal_successor.Acquire();
|
|
universal_successor2.Acquire();
|
|
});
|
|
}
|
|
|
|
TEST(CheckedLockTest, UniversalSuccessorAsPredecessor)
|
|
NO_THREAD_SAFETY_ANALYSIS {
|
|
// A universal-successor lock cannot be declared as a predecessor to
|
|
// any other lock.
|
|
CheckedLock universal_successor{UniversalSuccessor()};
|
|
EXPECT_DCHECK_DEATH({ CheckedLock banned_successor(&universal_successor); });
|
|
}
|
|
|
|
TEST(CheckedLockTest, AssertNoLockHeldOnCurrentThread) {
|
|
// AssertNoLockHeldOnCurrentThread() shouldn't fail when no lock is acquired.
|
|
CheckedLock::AssertNoLockHeldOnCurrentThread();
|
|
|
|
// AssertNoLockHeldOnCurrentThread() should fail when a lock is acquired.
|
|
CheckedLock lock;
|
|
{
|
|
CheckedAutoLock auto_lock(lock);
|
|
EXPECT_DCHECK_DEATH({ CheckedLock::AssertNoLockHeldOnCurrentThread(); });
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
|
|
class MemberGuardedByLock {
|
|
public:
|
|
CheckedLock lock_;
|
|
int value GUARDED_BY(lock_) = 0;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
TEST(CheckedLockTest, AnnotateAcquiredLockAlias) {
|
|
MemberGuardedByLock member_guarded_by_lock;
|
|
CheckedLock* acquired = &member_guarded_by_lock.lock_;
|
|
CheckedAutoLock auto_lock(*acquired);
|
|
AnnotateAcquiredLockAlias annotate(*acquired, member_guarded_by_lock.lock_);
|
|
member_guarded_by_lock.value = 42; // Doesn't compile without |annotate|.
|
|
}
|
|
|
|
} // namespace internal
|
|
} // namespace base
|