mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-16 15:36:12 -04:00
Add Borrowable class.
Define Lender/Borrowable/Borrowed classes for sharing resources safely between threads. PiperOrigin-RevId: 489361079
This commit is contained in:
committed by
Copybara-Service
parent
e58dd0a597
commit
141f021140
@@ -515,6 +515,7 @@ let package = Package(
|
||||
"internal/platform/bluetooth_adapter_test.cc",
|
||||
"internal/platform/byte_utils_test.cc",
|
||||
"internal/platform/direct_executor_test.cc",
|
||||
"internal/platform/borrowable_test.cc",
|
||||
// simulation
|
||||
"connections/implementation/offline_simulation_user.cc",
|
||||
"connections/implementation/simulation_user.cc",
|
||||
|
||||
@@ -311,6 +311,7 @@ cc_library(
|
||||
hdrs = [
|
||||
"atomic_boolean.h",
|
||||
"atomic_reference.h",
|
||||
"borrowable.h",
|
||||
"cancelable.h",
|
||||
"cancelable_alarm.h",
|
||||
"cancellable_task.h",
|
||||
@@ -423,6 +424,7 @@ cc_test(
|
||||
"bluetooth_adapter_test.cc",
|
||||
"bluetooth_classic_test.cc",
|
||||
"bluetooth_connection_info_test.cc",
|
||||
"borrowable_test.cc",
|
||||
"cancelable_alarm_test.cc",
|
||||
"condition_variable_test.cc",
|
||||
"count_down_latch_test.cc",
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
// Copyright 2022 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// Defines an interface that allows a resource owner to share a resource with
|
||||
// tasks running on other thread, and protects against use-after-free errors.
|
||||
// When a resource is borrowed, the borrower has exclusive access to the
|
||||
// resource. The framework guarantees that the resource stays alive as long it
|
||||
// is borrowed. Typical usage:
|
||||
//
|
||||
// In resource owner:
|
||||
// Lender<DataType> lender(DataType{});
|
||||
// Give clients instances of `lender.GetBorrowable()`
|
||||
//
|
||||
// In clients:
|
||||
// Borrowed<DataType> borrowed = borrowable.Borrow();
|
||||
// if (borrowed) {
|
||||
// borrowed->SomeMethod();
|
||||
// }
|
||||
//
|
||||
// When `Lender` goes out of scope, all borrowable instances become invalid and
|
||||
// attempts to `Borrow()` will fail safely.
|
||||
|
||||
#ifndef THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_BORROWABLE_H_
|
||||
#define THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_BORROWABLE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "internal/platform/mutex.h"
|
||||
#include "internal/platform/mutex_lock.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
|
||||
template <typename T>
|
||||
struct BorrowableSharedData {
|
||||
explicit BorrowableSharedData(T resource) : resource(std::move(resource)) {}
|
||||
|
||||
Mutex mutex;
|
||||
T resource ABSL_GUARDED_BY(mutex);
|
||||
bool released ABSL_GUARDED_BY(mutex) = false;
|
||||
};
|
||||
|
||||
// RAII style accessor to a borrowed object. While the caller holds an instance
|
||||
// of `Borrowed`, they have exclusive access to the borrowed object. Borrowing
|
||||
// can fail if the object that we are trying to borrow is already destroyed. The
|
||||
// caller should check if `Borrowed` is valid before accessing.
|
||||
template <typename T>
|
||||
class Borrowed {
|
||||
public:
|
||||
Borrowed() = default;
|
||||
explicit Borrowed(std::shared_ptr<BorrowableSharedData<T>> data)
|
||||
: data_(data), lock_(std::make_unique<MutexLock>(&data->mutex)) {
|
||||
if (data_->released) {
|
||||
// We have been handed a half-destroyed object. The owner has already
|
||||
// marked it as `released` but the shared_ptr was still valid. Let's
|
||||
// release our copy right away.
|
||||
lock_.reset();
|
||||
data_.reset();
|
||||
}
|
||||
}
|
||||
Borrowed(const Borrowed&) = delete;
|
||||
Borrowed(Borrowed&&) = delete;
|
||||
Borrowed& operator=(Borrowed other) = delete;
|
||||
Borrowed& operator=(Borrowed&& other) = delete;
|
||||
|
||||
bool Ok() const { return data_ != nullptr; }
|
||||
explicit operator bool() const { return Ok(); }
|
||||
T* operator->() {
|
||||
CHECK(Ok());
|
||||
return &data_->resource;
|
||||
}
|
||||
T& operator*() {
|
||||
CHECK(Ok());
|
||||
return data_->resource;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<BorrowableSharedData<T>> data_;
|
||||
std::unique_ptr<MutexLock> lock_;
|
||||
};
|
||||
|
||||
// A weak reference to an object that can be borrowed.
|
||||
template <typename T>
|
||||
class Borrowable {
|
||||
public:
|
||||
explicit Borrowable(std::weak_ptr<BorrowableSharedData<T>> resource)
|
||||
: resource_(resource) {}
|
||||
|
||||
// Gives the caller exclusive access to the stored object. Borrowing fails if
|
||||
// the object has already been destroyed. The call will block if another
|
||||
// thread is already borrowing the object.
|
||||
Borrowed<T> Borrow() const {
|
||||
std::shared_ptr<BorrowableSharedData<T>> data = resource_.lock();
|
||||
if (!data) {
|
||||
return Borrowed<T>();
|
||||
}
|
||||
return Borrowed<T>(data);
|
||||
}
|
||||
|
||||
private:
|
||||
std::weak_ptr<BorrowableSharedData<T>> resource_;
|
||||
};
|
||||
|
||||
// The owner of a resource that can be borrowed.
|
||||
template <typename T>
|
||||
class Lender {
|
||||
public:
|
||||
explicit Lender(T resource)
|
||||
: shared_data_(
|
||||
std::make_shared<BorrowableSharedData<T>>(std::move(resource))) {}
|
||||
~Lender() { Release(); }
|
||||
|
||||
Borrowable<T> GetBorrowable() { return Borrowable<T>(shared_data_); }
|
||||
|
||||
// Destroys the stored resource. If the resource is currently borrowed, the
|
||||
// call will block until the resource is returned.
|
||||
// When the resource is released, calls to `Borrowable::Borrow()` will fail.
|
||||
void Release() {
|
||||
if (!shared_data_) {
|
||||
return;
|
||||
}
|
||||
{
|
||||
MutexLock lock(&shared_data_->mutex);
|
||||
// We can't destroy `shared_data_` while holding the mutex inside it.
|
||||
// Setting `released` fixes the race condition below.
|
||||
// Thread 1 | Thread 2
|
||||
// Lender::Release() | Borrowable::Borrow()
|
||||
// | grabs shared_ptr<T>
|
||||
// locks mutex |
|
||||
// sets `released` |
|
||||
// unlocks mutex |
|
||||
// | locks mutex
|
||||
// resets shared_data_ |
|
||||
// | `released` is set, so borrow fails - unlocks
|
||||
// | mutex, deletes shared_ptr and returns invalid
|
||||
// | `Borrowed` object.
|
||||
shared_data_->released = true;
|
||||
}
|
||||
shared_data_.reset();
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<BorrowableSharedData<T>> shared_data_;
|
||||
};
|
||||
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_BORROWABLE_H_
|
||||
@@ -0,0 +1,131 @@
|
||||
// Copyright 2022 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "internal/platform/borrowable.h"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "protobuf-matchers/protocol-buffer-matchers.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/time/clock.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "internal/platform/count_down_latch.h"
|
||||
#include "internal/platform/multi_thread_executor.h"
|
||||
#include "internal/platform/single_thread_executor.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kDefaultValue = -1;
|
||||
class Data {
|
||||
public:
|
||||
Data() = default;
|
||||
Data(const Data &) = delete;
|
||||
Data(Data &&) = default;
|
||||
void SetValue(int value) { value_ = value; }
|
||||
int GetValue() { return value_; }
|
||||
|
||||
private:
|
||||
volatile int value_ = kDefaultValue;
|
||||
};
|
||||
|
||||
TEST(Borrowable, CreateAndBorrow) {
|
||||
constexpr int kValue = 1;
|
||||
|
||||
Lender<int> lender(kValue);
|
||||
Borrowable<int> borrowable = lender.GetBorrowable();
|
||||
Borrowed<int> borrowed = borrowable.Borrow();
|
||||
|
||||
ASSERT_TRUE(borrowed);
|
||||
EXPECT_EQ(*borrowed, kValue);
|
||||
}
|
||||
|
||||
TEST(Borrowable, BorrowAccessesOriginalObject) {
|
||||
constexpr int kValue = 12;
|
||||
Lender<Data> lender(Data{});
|
||||
Borrowable<Data> borrowable = lender.GetBorrowable();
|
||||
{
|
||||
Borrowed<Data> borrowed = borrowable.Borrow();
|
||||
|
||||
ASSERT_TRUE(borrowed);
|
||||
borrowed->SetValue(kValue);
|
||||
}
|
||||
EXPECT_EQ(borrowable.Borrow()->GetValue(), kValue);
|
||||
}
|
||||
|
||||
TEST(Borrowable, BorrowAfterReleaseFails) {
|
||||
constexpr int kValue = 1;
|
||||
Lender<int> lender(kValue);
|
||||
Borrowable<int> borrowable = lender.GetBorrowable();
|
||||
|
||||
lender.Release();
|
||||
|
||||
EXPECT_FALSE(borrowable.Borrow());
|
||||
}
|
||||
|
||||
TEST(Borrowable, ReleaseWaitsForBorrowToEnd) {
|
||||
constexpr int kValue = 1;
|
||||
Data *data = new Data();
|
||||
CountDownLatch latch(1);
|
||||
Lender<Data *> lender(data);
|
||||
Borrowable<Data *> borrowable = lender.GetBorrowable();
|
||||
SingleThreadExecutor executor;
|
||||
Borrowed<Data *> borrowed = borrowable.Borrow();
|
||||
ASSERT_TRUE(borrowed);
|
||||
|
||||
executor.Execute([&]() {
|
||||
latch.CountDown();
|
||||
// Release() must wait until `borrowed` object goes out of scope.
|
||||
lender.Release();
|
||||
data->SetValue(kValue);
|
||||
delete data;
|
||||
});
|
||||
EXPECT_TRUE(latch.Await().Ok());
|
||||
// If `Release()` didn't wait, then we would access a dead object below. Short
|
||||
// sleep makes the crash more likely.
|
||||
absl::SleepFor(absl::Milliseconds(100));
|
||||
EXPECT_EQ((*borrowed)->GetValue(), kDefaultValue);
|
||||
}
|
||||
|
||||
TEST(Borrowable, BorrowIsExclusive) {
|
||||
constexpr int kThreads = 15;
|
||||
constexpr int kTasks = 30;
|
||||
CountDownLatch latch(kTasks);
|
||||
Lender<Data> lender(Data{});
|
||||
MultiThreadExecutor executor(kThreads);
|
||||
|
||||
for (int i = 0; i < kTasks; i++) {
|
||||
executor.Execute([&, value = i, borrowable = lender.GetBorrowable()]() {
|
||||
latch.CountDown();
|
||||
Borrowed<Data> borrowed = borrowable.Borrow();
|
||||
ASSERT_TRUE(borrowed);
|
||||
|
||||
// Verify that a task has exclusive access to the borrowed object.
|
||||
borrowed->SetValue(value);
|
||||
// Sleep yields to other threads, which increases the chance of a race
|
||||
// condition.
|
||||
absl::SleepFor(absl::Milliseconds(1));
|
||||
EXPECT_EQ(borrowed->GetValue(), value);
|
||||
});
|
||||
}
|
||||
EXPECT_TRUE(latch.Await().Ok());
|
||||
// Verify that the shared data was modified. We can't know for sure what the
|
||||
// final value is.
|
||||
EXPECT_NE(lender.GetBorrowable().Borrow()->GetValue(), kDefaultValue);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
Reference in New Issue
Block a user