Commit a01e9302 authored by Kyle Qian's avatar Kyle Qian Committed by Commit Bot

Implement Lock and ConditionVariable for CrOS Nearby.

This CL includes a concrete implementation for the abstract classes
Lock (which must be recursive) and ConditionVariable defined in the
Nearby library.

A related CL was previously abandoned
(https://chromium-review.googlesource.com/c/chromium/src/+/1138871)
because the Nearby C++ library already includes a DefaultLock and
DefaultConditionVariable. However, that CL is being revived as this CL
because a Chromium-specific Lock/CV implementation is required. For
instance, Chromium requires a ScopedBlockingCall to be instantiated in
scopes where blocking calls are made. DefaultConditionVariable does not
include an instantiation of ScopedBlockingCall. This implementation of
ConditionVariable uses a base::WaitableEvent, which instantiates a
ScopedBlockingCall. This CL also includes a Lock implementation that is
recursive, which is a requirement of Nearby, but is not originally
supported by base::Lock.

Because the actual Nearby library has yet to be merged into the CrOS
directory, this CL includes stand-in Nearby abstract classes under
the temporary directory //chromeos/components/nearby/library.
This directory will be removed after the Nearby library gets imported
to //third_party.

Bug: 861813
Change-Id: I72660ed302e239d8f38a21728fc715b000057c13
Reviewed-on: https://chromium-review.googlesource.com/1145730
Commit-Queue: Kyle Qian <kyleqian@google.com>
Reviewed-by: default avatarFrançois Doray <fdoray@chromium.org>
Reviewed-by: default avatarKyle Horimoto <khorimoto@chromium.org>
Cr-Commit-Position: refs/heads/master@{#586070}
parent 6e14f68e
# Copyright 2016 The Chromium Authors. All rights reserved.
# Copyright 2018 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
......@@ -9,10 +9,15 @@ static_library("nearby") {
"atomic_boolean_impl.cc",
"atomic_boolean_impl.h",
"atomic_reference_impl.h",
"condition_variable_impl.cc",
"condition_variable_impl.h",
"count_down_latch_impl.cc",
"count_down_latch_impl.h",
"hash_utils_impl.cc",
"hash_utils_impl.h",
"lock_base.h",
"lock_impl.cc",
"lock_impl.h",
"settable_future_impl.h",
"system_clock_impl.cc",
"system_clock_impl.h",
......@@ -33,8 +38,10 @@ source_set("unit_tests") {
sources = [
"atomic_boolean_impl_unittest.cc",
"atomic_reference_impl_unittest.cc",
"condition_variable_impl_unittest.cc",
"count_down_latch_impl_unittest.cc",
"hash_utils_impl_unittest.cc",
"lock_impl_unittest.cc",
"settable_future_impl_unittest.cc",
]
......
// Copyright (c) 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/components/nearby/condition_variable_impl.h"
#include "chromeos/components/nearby/lock_base.h"
namespace chromeos {
namespace nearby {
ConditionVariableImpl::ConditionVariableImpl(location::nearby::Lock* lock)
: lock_(lock),
notify_has_been_called_(base::WaitableEvent::ResetPolicy::AUTOMATIC) {}
ConditionVariableImpl::~ConditionVariableImpl() = default;
void ConditionVariableImpl::notify() {
// Signal() will signal a single outstanding call to Wait(), since it's set to
// ResetPolicy::AUTOMATIC.
notify_has_been_called_.Signal();
}
location::nearby::Exception::Value ConditionVariableImpl::wait() {
lock_->unlock();
// If |lock_| is still held by the current thread here (e.g., due to it being
// a recursive lock), then no other thread will be able to acquire |lock_| in
// order to modify whatever condition this ConditionVariable is waiting on.
// In that case, the following call to WaitableEvent::Wait() will likely block
// forever.
DCHECK(!(static_cast<LockBase* const>(lock_)->IsHeldByCurrentThread()));
notify_has_been_called_.Wait();
lock_->lock();
// Because |notify_has_been_called_| is set to ResetPolicy::AUTOMATIC,
// Signal() will only wake up ONE outstanding call to Wait().
// ConditionVariable::notify() is expected to notify ALL outstanding threads,
// but since WaitableEvent does not currently have a public broadcast method,
// this Signal() will make sure to wake up the next outstanding thread.
//
// NOTE: There exists a small inefficiency here where if this is the last
// waiting thread, then the Signal() will persist until the next call to
// notify_has_been_called_.Wait(), causing that next call to return
// immediately. This should be fine since calling ConditionVariable::wait()
// should be done within a while-loop anyways, as in:
//
// lock.lock();
// while (!condition_is_true)
// condition_variable.wait();
// // do stuff while condition is true
// lock.unlock();
//
// The persisting Signal() should merely cause one extra loop around, properly
// blocking the second time ConditionVariable::wait() is called.
notify_has_been_called_.Signal();
return location::nearby::Exception::NONE;
}
} // namespace nearby
} // namespace chromeos
// Copyright (c) 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROMEOS_COMPONENTS_NEARBY_CONDITION_VARIABLE_IMPL_H_
#define CHROMEOS_COMPONENTS_NEARBY_CONDITION_VARIABLE_IMPL_H_
#include <memory>
#include "base/macros.h"
#include "base/synchronization/waitable_event.h"
#include "chromeos/components/nearby/library/condition_variable.h"
#include "chromeos/components/nearby/library/exception.h"
#include "chromeos/components/nearby/library/lock.h"
namespace chromeos {
namespace nearby {
// Concrete location::nearby::ConditionVariable implementation.
class ConditionVariableImpl : public location::nearby::ConditionVariable {
public:
explicit ConditionVariableImpl(location::nearby::Lock* lock);
~ConditionVariableImpl() override;
private:
// location::nearby::ConditionVariable:
void notify() override;
// Expects the calling thread to already own |lock_|. Calling this without
// ownership of |lock_| results in undefined behavior, or at least behavior
// specified by the specific implementation of |lock_|.
location::nearby::Exception::Value wait() override;
location::nearby::Lock* const lock_;
base::WaitableEvent notify_has_been_called_;
DISALLOW_COPY_AND_ASSIGN(ConditionVariableImpl);
};
} // namespace nearby
} // namespace chromeos
#endif // CHROMEOS_COMPONENTS_NEARBY_CONDITION_VARIABLE_IMPL_H_
// Copyright (c) 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/components/nearby/condition_variable_impl.h"
#include <memory>
#include "base/single_thread_task_runner.h"
#include "base/task/post_task.h"
#include "base/test/gtest_util.h"
#include "base/test/scoped_task_environment.h"
#include "base/test/test_timeouts.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread_restrictions.h"
#include "chromeos/components/nearby/lock_base.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromeos {
namespace nearby {
namespace {
class FakeLock : public LockBase {
public:
FakeLock() : condition_variable_(&lock_) {}
~FakeLock() override = default;
// location::nearby::Lock:
void lock() override {
base::AutoLock al(lock_);
++num_locks_;
condition_variable_.Signal();
}
void unlock() override {
base::AutoLock al(lock_);
++num_unlocks_;
condition_variable_.Signal();
}
// chromeos::nearby::LockBase:
bool IsHeldByCurrentThread() override { return is_held_by_current_thread_; }
void set_is_held_by_current_thread(bool value) {
is_held_by_current_thread_ = value;
}
int num_locks() {
base::AutoLock al(lock_);
return num_locks_;
}
int num_unlocks() {
base::AutoLock al(lock_);
return num_unlocks_;
}
void WaitForLocksAndUnlocks(int expected_num_locks,
int expected_num_unlocks) {
base::AutoLock al(lock_);
while (expected_num_locks != num_locks_ ||
expected_num_unlocks != num_unlocks_) {
condition_variable_.Wait();
}
}
private:
base::Lock lock_;
base::ConditionVariable condition_variable_;
bool is_held_by_current_thread_ = false;
int num_locks_ = 0;
int num_unlocks_ = 0;
DISALLOW_COPY_AND_ASSIGN(FakeLock);
};
} // namespace
class ConditionVariableImplTest : public testing::Test {
protected:
ConditionVariableImplTest()
: fake_lock_(std::make_unique<FakeLock>()),
condition_variable_(
std::make_unique<ConditionVariableImpl>(fake_lock_.get())) {}
// testing::Test
void TearDown() override {
scoped_task_environment_.RunUntilIdle();
EXPECT_EQ(fake_lock_->num_locks(), fake_lock_->num_unlocks());
}
location::nearby::ConditionVariable* condition_variable() {
return condition_variable_.get();
}
FakeLock* fake_lock() { return fake_lock_.get(); }
void WaitOnConditionVariableFromParallelSequence(bool should_succeed) {
base::PostTask(
FROM_HERE,
base::BindOnce(&ConditionVariableImplTest::WaitOnConditionVariable,
base::Unretained(this), should_succeed));
}
// Invoked whenever attempting to verify that a parallel task has indeed
// blocked, since there's no way to deterministically find out if that task
// will ever unblock.
void TinyTimeout() {
base::PlatformThread::Sleep(TestTimeouts::tiny_timeout());
}
void VerifyNumLocksAndUnlocks(int expected_num_locks,
int expected_num_unlocks) {
EXPECT_EQ(expected_num_locks, fake_lock_->num_locks());
EXPECT_EQ(expected_num_unlocks, fake_lock_->num_unlocks());
}
// To ensure that |condition_variable_| is blocking as expected, wait until
// its |fake_lock_| has been locked and unlocked once per blocked sequence.
// Then manually wait, and verify afterwards that the number of locks and
// unlocks remain unchanged.
void VerifyBlockedConditionVariable(int expected_num_blocked_sequences) {
fake_lock()->WaitForLocksAndUnlocks(
expected_num_blocked_sequences /* expected_num_locks */,
expected_num_blocked_sequences /* expected_num_unlocks */);
TinyTimeout();
VerifyNumLocksAndUnlocks(
expected_num_blocked_sequences /* expected_num_locks */,
expected_num_blocked_sequences /* expected_num_unlocks */);
}
base::test::ScopedTaskEnvironment scoped_task_environment_;
private:
void WaitOnConditionVariable(bool should_succeed) {
const base::ScopedAllowBaseSyncPrimitivesForTesting allow_sync_primitives;
// ConditionVariable::wait() should only be called by a thread that already
// owns the associated Lock. This is not tested as the expected behavior is
// either undefined or specified by the particular Lock implementation.
fake_lock_->lock();
if (should_succeed)
condition_variable_->wait();
else
EXPECT_DCHECK_DEATH(condition_variable_->wait());
// ConditionVariable::wait() will call Lock::lock() after unblocking, so
// this undoes that.
fake_lock_->unlock();
}
std::unique_ptr<FakeLock> fake_lock_;
std::unique_ptr<location::nearby::ConditionVariable> condition_variable_;
DISALLOW_COPY_AND_ASSIGN(ConditionVariableImplTest);
};
TEST_F(ConditionVariableImplTest,
SingleSequence_BlocksOnWaitAndUnblocksOnNotify) {
WaitOnConditionVariableFromParallelSequence(true /* should_succeed */);
VerifyBlockedConditionVariable(1 /* expected_num_blocked_sequences */);
// Should unblock after notify().
condition_variable()->notify();
scoped_task_environment_.RunUntilIdle();
VerifyNumLocksAndUnlocks(2 /* expected_num_locks */,
2 /* expected_num_unlocks */);
}
TEST_F(ConditionVariableImplTest,
MultipleSequences_BlocksOnWaitAndUnblocksOnNotify) {
WaitOnConditionVariableFromParallelSequence(true /* should_succeed */);
WaitOnConditionVariableFromParallelSequence(true /* should_succeed */);
WaitOnConditionVariableFromParallelSequence(true /* should_succeed */);
VerifyBlockedConditionVariable(3 /* expected_num_blocked_sequences */);
// All should unblock after notify().
condition_variable()->notify();
scoped_task_environment_.RunUntilIdle();
VerifyNumLocksAndUnlocks(6 /* expected_num_locks */,
6 /* expected_num_unlocks */);
}
TEST_F(ConditionVariableImplTest, ThreadCannotWaitIfStillOwnsLock) {
fake_lock()->set_is_held_by_current_thread(true);
WaitOnConditionVariableFromParallelSequence(false /* should_succeed */);
}
} // namespace nearby
} // namespace chromeos
# Copyright 2016 The Chromium Authors. All rights reserved.
# Copyright 2018 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
......@@ -9,10 +9,12 @@ static_library("library") {
"atomic_boolean.h",
"atomic_reference.h",
"byte_array.h",
"condition_variable.h",
"count_down_latch.h",
"exception.h",
"future.h",
"hash_utils.h",
"lock.h",
"settable_future.h",
"system_clock.h",
"thread_utils.h",
......
// Copyright (c) 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROMEOS_COMPONENTS_NEARBY_LIBRARY_CONDITION_VARIABLE_H_
#define CHROMEOS_COMPONENTS_NEARBY_LIBRARY_CONDITION_VARIABLE_H_
#include "chromeos/components/nearby/library/exception.h"
namespace location {
namespace nearby {
// The ConditionVariable class is a synchronization primitive that can be used
// to block a thread, or multiple threads at the same time, until another thread
// both modifies a shared variable (the condition), and notifies the
// ConditionVariable.
class ConditionVariable {
public:
virtual ~ConditionVariable() {}
// https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notifyAll--
virtual void notify() = 0;
// https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#wait--
virtual Exception::Value wait() = 0; // throws Exception::INTERRUPTED
};
} // namespace nearby
} // namespace location
#endif // CHROMEOS_COMPONENTS_NEARBY_LIBRARY_CONDITION_VARIABLE_H_
// Copyright (c) 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROMEOS_COMPONENTS_NEARBY_LIBRARY_LOCK_H_
#define CHROMEOS_COMPONENTS_NEARBY_LIBRARY_LOCK_H_
namespace location {
namespace nearby {
// A lock is a tool for controlling access to a shared resource by multiple
// threads.
//
// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/Lock.html
class Lock {
public:
virtual ~Lock() {}
virtual void lock() = 0;
virtual void unlock() = 0;
};
} // namespace nearby
} // namespace location
#endif // CHROMEOS_COMPONENTS_NEARBY_LIBRARY_LOCK_H_
// Copyright (c) 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROMEOS_COMPONENTS_NEARBY_LOCK_BASE_H_
#define CHROMEOS_COMPONENTS_NEARBY_LOCK_BASE_H_
#include "base/macros.h"
#include "chromeos/components/nearby/library/lock.h"
namespace chromeos {
namespace nearby {
class LockBase : public location::nearby::Lock {
private:
friend class ConditionVariableImpl;
// Returns whether the invoking thread is the same one that currently holds
// this Lock.
virtual bool IsHeldByCurrentThread() = 0;
};
} // namespace nearby
} // namespace chromeos
#endif // CHROMEOS_COMPONENTS_NEARBY_LOCK_BASE_H_
// Copyright (c) 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/components/nearby/lock_impl.h"
namespace chromeos {
namespace nearby {
LockImpl::LockImpl() = default;
LockImpl::~LockImpl() {
#if DCHECK_IS_ON()
base::AutoLock al(bookkeeping_lock_);
DCHECK_EQ(0u, num_acquisitions_);
DCHECK_EQ(base::kInvalidThreadId, owning_thread_id_);
#endif
}
void LockImpl::lock() {
{
base::AutoLock al(bookkeeping_lock_);
if (num_acquisitions_ > 0u &&
owning_thread_id_ == base::PlatformThread::CurrentId()) {
real_lock_.AssertAcquired();
++num_acquisitions_;
return;
}
}
// At this point, either no thread currently holds |real_lock_|, in which case
// the current thread should be able to immediately acquire it, or a different
// thread holds it, in which case Acquire() will block. It's necessary that
// Acquire() happens outside the critical sections of |bookkeeping_lock_|,
// otherwise any future calls to unlock() will block on acquiring
// |bookkeeping_lock_|, which would prevent Release() from ever running on
// |real_lock_|, resulting in deadlock.
real_lock_.Acquire();
{
base::AutoLock al(bookkeeping_lock_);
DCHECK_EQ(0u, num_acquisitions_);
owning_thread_id_ = base::PlatformThread::CurrentId();
num_acquisitions_ = 1;
}
}
void LockImpl::unlock() {
base::AutoLock al(bookkeeping_lock_);
CHECK_GT(num_acquisitions_, 0u);
DCHECK_EQ(base::PlatformThread::CurrentId(), owning_thread_id_);
real_lock_.AssertAcquired();
--num_acquisitions_;
if (num_acquisitions_ == 0u) {
owning_thread_id_ = base::kInvalidThreadId;
real_lock_.Release();
}
}
bool LockImpl::IsHeldByCurrentThread() {
base::AutoLock al(bookkeeping_lock_);
return owning_thread_id_ == base::PlatformThread::CurrentId();
}
} // namespace nearby
} // namespace chromeos
// Copyright (c) 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROMEOS_COMPONENTS_NEARBY_LOCK_IMPL_H_
#define CHROMEOS_COMPONENTS_NEARBY_LOCK_IMPL_H_
#include "base/macros.h"
#include "base/synchronization/lock.h"
#include "base/threading/platform_thread.h"
#include "chromeos/components/nearby/lock_base.h"
namespace chromeos {
namespace nearby {
// Concrete location::nearby::Lock implementation.
class LockImpl : public LockBase {
public:
LockImpl();
~LockImpl() override;
private:
friend class LockImplTest;
// location::nearby::Lock:
void lock() override;
void unlock() override;
// chromeos::nearby::LockBase:
bool IsHeldByCurrentThread() override;
base::Lock real_lock_;
base::Lock bookkeeping_lock_;
base::PlatformThreadId owning_thread_id_ = base::kInvalidThreadId;
size_t num_acquisitions_ = 0;
DISALLOW_COPY_AND_ASSIGN(LockImpl);
};
} // namespace nearby
} // namespace chromeos
#endif // CHROMEOS_COMPONENTS_NEARBY_LOCK_IMPL_H_
// Copyright (c) 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/components/nearby/lock_impl.h"
#include <memory>
#include "base/containers/flat_set.h"
#include "base/single_thread_task_runner.h"
#include "base/stl_util.h"
#include "base/task/post_task.h"
#include "base/test/gtest_util.h"
#include "base/test/scoped_task_environment.h"
#include "base/test/test_timeouts.h"
#include "base/threading/platform_thread.h"
#include "base/unguessable_token.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromeos {
namespace nearby {
class LockImplTest : public testing::Test {
protected:
LockImplTest()
: lock_(std::make_unique<LockImpl>()),
different_thread_task_runner_(
base::CreateSingleThreadTaskRunnerWithTraits(base::MayBlock())) {}
// testing::Test
void SetUp() override {
// |different_thread_task_runner_| is expected to run on a different thread
// than the main test thread.
EXPECT_FALSE(different_thread_task_runner_->BelongsToCurrentThread());
}
void TearDown() override {
// Releases the test thread's ownership of |lock_|.
int times_to_unlock;
{
base::AutoLock al(lock_->bookkeeping_lock_);
times_to_unlock = lock_->num_acquisitions_;
}
for (int i = 0; i < times_to_unlock; ++i)
lock_->unlock();
// Makes sure that outstanding LockAndUnlockFromDifferentThread() tasks in
// |different_thread_task_runner_| finish running after the test thread
// relinquishes its ownership of |lock_|.
scoped_task_environment_.RunUntilIdle();
base::AutoLock al(lock_->bookkeeping_lock_);
EXPECT_EQ(0u, lock_->num_acquisitions_);
EXPECT_EQ(base::kInvalidThreadId, lock_->owning_thread_id_);
}
void PostLockAndUnlockFromDifferentThread(
const base::UnguessableToken& attempt_id) {
different_thread_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&LockImplTest::LockAndUnlockFromDifferentThread,
base::Unretained(this), attempt_id));
}
// Invoked whenever attempting to verify that a parallel task has indeed
// blocked, since there's no way to deterministically find out if that task
// will ever unblock.
void TinyTimeout() {
base::PlatformThread::Sleep(TestTimeouts::tiny_timeout());
}
bool HasSuccessfullyLockedWithAttemptId(
const base::UnguessableToken& attempt_id) {
lock_->lock();
bool contains_key =
base::ContainsKey(successful_lock_attempts_, attempt_id);
lock_->unlock();
return contains_key;
}
location::nearby::Lock* lock() { return lock_.get(); }
base::test::ScopedTaskEnvironment scoped_task_environment_;
private:
// Only meant to be posted via PostLockAndUnlockFromDifferentThread() on
// |different_thread_task_runner_|.
//
// This method will only insert |attempt_id| into |successful_lock_attempts_|
// if it succeeds in acquiring |lock_|. It will also immediately unlock()
// after doing so because unlock() may only be called from the same thread
// that originally called lock().
void LockAndUnlockFromDifferentThread(
const base::UnguessableToken& attempt_id) {
lock_->lock();
successful_lock_attempts_.insert(attempt_id);
lock_->unlock();
}
std::unique_ptr<LockImpl> lock_;
scoped_refptr<base::SingleThreadTaskRunner> different_thread_task_runner_;
base::flat_set<base::UnguessableToken> successful_lock_attempts_;
DISALLOW_COPY_AND_ASSIGN(LockImplTest);
};
TEST_F(LockImplTest, LockOnce_UnlockOnce) {
lock()->lock();
lock()->unlock();
}
TEST_F(LockImplTest, LockThrice_UnlockThrice) {
lock()->lock();
lock()->lock();
lock()->lock();
lock()->unlock();
lock()->unlock();
lock()->unlock();
}
TEST_F(LockImplTest,
LockOnce_DisallowRelockingFromDifferentThreadUntilCurrentThreadUnlocks) {
// Lock on current thread.
lock()->lock();
// Try to lock again, but on different thread.
base::UnguessableToken attempt_id = base::UnguessableToken::Create();
PostLockAndUnlockFromDifferentThread(attempt_id);
// Wait for a little, then check to see if the lock attempt failed.
TinyTimeout();
EXPECT_FALSE(HasSuccessfullyLockedWithAttemptId(attempt_id));
// Outstanding lock attempt succeed after unlocking from current thread.
lock()->unlock();
scoped_task_environment_.RunUntilIdle();
EXPECT_TRUE(HasSuccessfullyLockedWithAttemptId(attempt_id));
}
TEST_F(
LockImplTest,
LockThrice_DisallowRelockingFromDifferentThreadUntilCurrentThreadUnlocks) {
// Lock on current thread.
lock()->lock();
lock()->lock();
lock()->lock();
// Try to lock again, but on different thread.
base::UnguessableToken attempt_id = base::UnguessableToken::Create();
PostLockAndUnlockFromDifferentThread(attempt_id);
// Wait for a little, then check to see if the lock attempt failed.
TinyTimeout();
EXPECT_FALSE(HasSuccessfullyLockedWithAttemptId(attempt_id));
// Outstanding lock attempt succeed after unlocking from current thread.
lock()->unlock();
lock()->unlock();
lock()->unlock();
scoped_task_environment_.RunUntilIdle();
EXPECT_TRUE(HasSuccessfullyLockedWithAttemptId(attempt_id));
}
TEST_F(LockImplTest, InterweavedLocking) {
base::UnguessableToken attempt_id1 = base::UnguessableToken::Create();
base::UnguessableToken attempt_id2 = base::UnguessableToken::Create();
base::UnguessableToken attempt_id3 = base::UnguessableToken::Create();
lock()->lock();
PostLockAndUnlockFromDifferentThread(attempt_id1);
lock()->lock();
PostLockAndUnlockFromDifferentThread(attempt_id2);
lock()->lock();
PostLockAndUnlockFromDifferentThread(attempt_id3);
TinyTimeout();
EXPECT_FALSE(HasSuccessfullyLockedWithAttemptId(attempt_id1));
EXPECT_FALSE(HasSuccessfullyLockedWithAttemptId(attempt_id2));
EXPECT_FALSE(HasSuccessfullyLockedWithAttemptId(attempt_id3));
lock()->unlock();
lock()->unlock();
lock()->unlock();
scoped_task_environment_.RunUntilIdle();
EXPECT_TRUE(HasSuccessfullyLockedWithAttemptId(attempt_id1));
EXPECT_TRUE(HasSuccessfullyLockedWithAttemptId(attempt_id2));
EXPECT_TRUE(HasSuccessfullyLockedWithAttemptId(attempt_id3));
}
TEST_F(LockImplTest, CannotUnlockBeforeAnyLocks) {
EXPECT_DCHECK_DEATH(lock()->unlock());
}
} // namespace nearby
} // namespace chromeos
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment