From dbcd179788a4cce4e9e3ae545e736148438c5c6f Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Tue, 8 May 2018 19:37:20 -0400 Subject: Firestore C++: compile ExecutorLibdispatch in Objective-C++ mode (#1237) dispatch_queue_t is defined differently in libdispatch depending on whether the library header is being include from Objective-C (or Objective-C++) code, or else from C or C++ code. Make sure that all source files in Firestore that include executor_libdispatch.h are compiled in the same mode (Objective-C++) to avoid linker errors. --- .../src/firebase/firestore/util/CMakeLists.txt | 2 +- .../firestore/util/executor_libdispatch.cc | 296 --------------------- .../firebase/firestore/util/executor_libdispatch.h | 10 +- .../firestore/util/executor_libdispatch.mm | 296 +++++++++++++++++++++ 4 files changed, 305 insertions(+), 299 deletions(-) delete mode 100644 Firestore/core/src/firebase/firestore/util/executor_libdispatch.cc create mode 100644 Firestore/core/src/firebase/firestore/util/executor_libdispatch.mm (limited to 'Firestore/core/src') diff --git a/Firestore/core/src/firebase/firestore/util/CMakeLists.txt b/Firestore/core/src/firebase/firestore/util/CMakeLists.txt index 29d91c7..b2b015b 100644 --- a/Firestore/core/src/firebase/firestore/util/CMakeLists.txt +++ b/Firestore/core/src/firebase/firestore/util/CMakeLists.txt @@ -130,7 +130,7 @@ if(HAVE_LIBDISPATCH) cc_library( firebase_firestore_util_executor_libdispatch SOURCES - executor_libdispatch.cc + executor_libdispatch.mm executor_libdispatch.h executor.h DEPENDS diff --git a/Firestore/core/src/firebase/firestore/util/executor_libdispatch.cc b/Firestore/core/src/firebase/firestore/util/executor_libdispatch.cc deleted file mode 100644 index b40f0dd..0000000 --- a/Firestore/core/src/firebase/firestore/util/executor_libdispatch.cc +++ /dev/null @@ -1,296 +0,0 @@ -/* - * Copyright 2018 Google - * - * 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 - * - * http://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 "Firestore/core/src/firebase/firestore/util/executor_libdispatch.h" - -namespace firebase { -namespace firestore { -namespace util { -namespace internal { - -namespace { - -absl::string_view StringViewFromDispatchLabel(const char* const label) { - // Make sure string_view's data is not null, because it's used for logging. - return label ? absl::string_view{label} : absl::string_view{""}; -} - -void RunSynchronized(const ExecutorLibdispatch* const executor, - std::function&& work) { - if (executor->IsCurrentExecutor()) { - work(); - } else { - DispatchSync(executor->dispatch_queue(), std::move(work)); - } -} - -} // namespace - -void DispatchAsync(const dispatch_queue_t queue, std::function&& work) { - // Dynamically allocate the function to make sure the object is valid by the - // time libdispatch gets to it. - const auto wrap = new std::function{std::move(work)}; - - dispatch_async_f(queue, wrap, [](void* const raw_work) { - const auto unwrap = static_cast*>(raw_work); - (*unwrap)(); - delete unwrap; - }); -} - -void DispatchSync(const dispatch_queue_t queue, Executor::Operation work) { - // Unlike dispatch_async_f, dispatch_sync_f blocks until the work passed to it - // is done, so passing a reference to a local variable is okay. - dispatch_sync_f(queue, &work, [](void* const raw_work) { - const auto unwrap = static_cast(raw_work); - (*unwrap)(); - }); -} - -// Represents a "busy" time slot on the schedule. -// -// Since libdispatch doesn't provide a way to cancel a scheduled operation, once -// a slot is created, it will always stay in the schedule until the time is -// past. Consequently, it is more useful to think of a time slot than -// a particular scheduled operation -- by the time the slot comes, operation may -// or may not be there (imagine getting to a meeting and finding out it's been -// canceled). -// -// Precondition: all member functions, including the constructor, are *only* -// invoked on the Firestore queue. -// -// Ownership: -// -// - `TimeSlot` is exclusively owned by libdispatch; -// - `ExecutorLibdispatch` contains non-owning pointers to `TimeSlot`s; -// - invariant: if the executor contains a pointer to a `TimeSlot`, it is -// a valid object. It is achieved because when libdispatch invokes -// a `TimeSlot`, it always removes it from the executor before deleting it. -// The reverse is not true: a canceled time slot is removed from the executor, -// but won't be destroyed until its original due time is past. - -class TimeSlot { - public: - TimeSlot(ExecutorLibdispatch* executor, - Executor::Milliseconds delay, - Executor::TaggedOperation&& operation); - - // Returns the operation that was scheduled for this time slot and turns the - // slot into a no-op. - Executor::TaggedOperation Unschedule(); - - bool operator<(const TimeSlot& rhs) const { - return target_time_ < rhs.target_time_; - } - bool operator==(const Executor::Tag tag) const { - return tagged_.tag == tag; - } - - void MarkDone() { - done_ = true; - } - - static void InvokedByLibdispatch(void* const raw_self); - - private: - void Execute(); - void RemoveFromSchedule(); - - using TimePoint = std::chrono::time_point; - - ExecutorLibdispatch* const executor_; - const TimePoint target_time_; // Used for sorting - Executor::TaggedOperation tagged_; - - // True if the operation has either been run or canceled. - // - // Note on thread-safety: because the precondition is that all member - // functions of this class are executed on the dispatch queue, no - // synchronization is required for `done_`. - bool done_ = false; -}; - -TimeSlot::TimeSlot(ExecutorLibdispatch* const executor, - const Executor::Milliseconds delay, - Executor::TaggedOperation&& operation) - : executor_{executor}, - target_time_{std::chrono::time_point_cast( - std::chrono::system_clock::now()) + - delay}, - tagged_{std::move(operation)} { -} - -Executor::TaggedOperation TimeSlot::Unschedule() { - if (!done_) { - RemoveFromSchedule(); - } - return std::move(tagged_); -} - -void TimeSlot::InvokedByLibdispatch(void* const raw_self) { - auto const self = static_cast(raw_self); - self->Execute(); - delete self; -} - -void TimeSlot::Execute() { - if (done_) { - // `done_` might mean that the executor is already destroyed, so don't call - // `RemoveFromSchedule`. - return; - } - - RemoveFromSchedule(); - - FIREBASE_ASSERT_MESSAGE(tagged_.operation, - "TimeSlot contains an invalid function object"); - tagged_.operation(); -} - -void TimeSlot::RemoveFromSchedule() { - executor_->RemoveFromSchedule(this); -} - -// ExecutorLibdispatch - -ExecutorLibdispatch::ExecutorLibdispatch(const dispatch_queue_t dispatch_queue) - : dispatch_queue_{dispatch_queue} { -} -ExecutorLibdispatch::ExecutorLibdispatch() - : ExecutorLibdispatch{dispatch_queue_create("com.google.firebase.firestore", - DISPATCH_QUEUE_SERIAL)} { -} - -ExecutorLibdispatch::~ExecutorLibdispatch() { - // Turn any operations that might still be in the queue into no-ops, lest - // they try to access `ExecutorLibdispatch` after it gets destroyed. Because - // the queue is serial, by the time libdispatch gets to the newly-enqueued - // work, the pending operations that might have been in progress would have - // already finished. - RunSynchronized(this, [this] { - for (auto slot : schedule_) { - slot->MarkDone(); - } - }); -} - -bool ExecutorLibdispatch::IsCurrentExecutor() const { - return GetCurrentQueueLabel().data() == GetTargetQueueLabel().data(); -} -std::string ExecutorLibdispatch::CurrentExecutorName() const { - return GetCurrentQueueLabel().data(); -} -std::string ExecutorLibdispatch::Name() const { - return GetTargetQueueLabel().data(); -} - -void ExecutorLibdispatch::Execute(Operation&& operation) { - DispatchAsync(dispatch_queue(), std::move(operation)); -} -void ExecutorLibdispatch::ExecuteBlocking(Operation&& operation) { - DispatchSync(dispatch_queue(), std::move(operation)); -} - -DelayedOperation ExecutorLibdispatch::Schedule(const Milliseconds delay, - TaggedOperation&& operation) { - namespace chr = std::chrono; - const dispatch_time_t delay_ns = dispatch_time( - DISPATCH_TIME_NOW, chr::duration_cast(delay).count()); - - // Ownership is fully transferred to libdispatch -- because it's impossible - // to truly cancel work after it's been dispatched, libdispatch is - // guaranteed to outlive the executor, and it's possible for work to be - // invoked by libdispatch after the executor is destroyed. Executor only - // stores an observer pointer to the operation. - - auto const time_slot = new TimeSlot{this, delay, std::move(operation)}; - dispatch_after_f(delay_ns, dispatch_queue(), time_slot, - TimeSlot::InvokedByLibdispatch); - RunSynchronized(this, [this, time_slot] { schedule_.push_back(time_slot); }); - return DelayedOperation{[this, time_slot] { - // `time_slot` might be destroyed by the time cancellation function runs. - // Therefore, don't access any methods on `time_slot`, only use it as - // a handle to remove from `schedule_`. - RemoveFromSchedule(time_slot); - }}; -} - -void ExecutorLibdispatch::RemoveFromSchedule(const TimeSlot* const to_remove) { - RunSynchronized(this, [this, to_remove] { - const auto found = std::find_if( - schedule_.begin(), schedule_.end(), - [to_remove](const TimeSlot* op) { return op == to_remove; }); - // It's possible for the operation to be missing if libdispatch gets to run - // it after it was force-run, for example. - if (found != schedule_.end()) { - (*found)->MarkDone(); - schedule_.erase(found); - } - }); -} - -// GetLabel functions are guaranteed to never return a "null" string_view -// (i.e. data() != nullptr). -absl::string_view ExecutorLibdispatch::GetCurrentQueueLabel() const { - // Note: dispatch_queue_get_label may return nullptr if the queue wasn't - // initialized with a label. - return StringViewFromDispatchLabel( - dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)); -} - -absl::string_view ExecutorLibdispatch::GetTargetQueueLabel() const { - return StringViewFromDispatchLabel( - dispatch_queue_get_label(dispatch_queue())); -} - -// Test-only methods - -bool ExecutorLibdispatch::IsScheduled(const Tag tag) const { - bool result = false; - RunSynchronized(this, [this, tag, &result] { - result = std::find_if(schedule_.begin(), schedule_.end(), - [&tag](const TimeSlot* const operation) { - return *operation == tag; - }) != schedule_.end(); - }); - return result; -} - -absl::optional -ExecutorLibdispatch::PopFromSchedule() { - absl::optional result; - - RunSynchronized(this, [this, &result] { - if (schedule_.empty()) { - return; - } - // Sorting upon each call to `PopFromSchedule` is inefficient, which is - // consciously ignored because this function is only ever called from tests. - std::sort( - schedule_.begin(), schedule_.end(), - [](const TimeSlot* lhs, const TimeSlot* rhs) { return *lhs < *rhs; }); - const auto nearest = schedule_.begin(); - result = (*nearest)->Unschedule(); - }); - - return result; -} - -} // namespace internal -} // namespace util -} // namespace firestore -} // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/util/executor_libdispatch.h b/Firestore/core/src/firebase/firestore/util/executor_libdispatch.h index b32dbff..85c34f8 100644 --- a/Firestore/core/src/firebase/firestore/util/executor_libdispatch.h +++ b/Firestore/core/src/firebase/firestore/util/executor_libdispatch.h @@ -17,7 +17,6 @@ #ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_EXECUTOR_LIBDISPATCH_H_ #define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_EXECUTOR_LIBDISPATCH_H_ -#include #include // NOLINT(build/c++11) #include #include @@ -30,6 +29,13 @@ #include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" #include "absl/strings/string_view.h" +#if !defined(__OBJC__) +// `dispatch_queue_t` gets defined to different types when compiled in C++ or +// Objective-C mode. Source files including this header should all be compiled +// in the same mode to avoid linker errors. +#error "This header only supports Objective-C++ (see comment for more info)." +#endif // !defined(__OBJC__) + namespace firebase { namespace firestore { namespace util { @@ -78,7 +84,7 @@ class ExecutorLibdispatch : public Executor { absl::string_view GetCurrentQueueLabel() const; absl::string_view GetTargetQueueLabel() const; - std::atomic dispatch_queue_; + dispatch_queue_t dispatch_queue_; // Stores non-owned pointers to `TimeSlot`s. // Invariant: if a `TimeSlot` is in `schedule_`, it's a valid pointer. std::vector schedule_; diff --git a/Firestore/core/src/firebase/firestore/util/executor_libdispatch.mm b/Firestore/core/src/firebase/firestore/util/executor_libdispatch.mm new file mode 100644 index 0000000..b40f0dd --- /dev/null +++ b/Firestore/core/src/firebase/firestore/util/executor_libdispatch.mm @@ -0,0 +1,296 @@ +/* + * Copyright 2018 Google + * + * 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 + * + * http://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 "Firestore/core/src/firebase/firestore/util/executor_libdispatch.h" + +namespace firebase { +namespace firestore { +namespace util { +namespace internal { + +namespace { + +absl::string_view StringViewFromDispatchLabel(const char* const label) { + // Make sure string_view's data is not null, because it's used for logging. + return label ? absl::string_view{label} : absl::string_view{""}; +} + +void RunSynchronized(const ExecutorLibdispatch* const executor, + std::function&& work) { + if (executor->IsCurrentExecutor()) { + work(); + } else { + DispatchSync(executor->dispatch_queue(), std::move(work)); + } +} + +} // namespace + +void DispatchAsync(const dispatch_queue_t queue, std::function&& work) { + // Dynamically allocate the function to make sure the object is valid by the + // time libdispatch gets to it. + const auto wrap = new std::function{std::move(work)}; + + dispatch_async_f(queue, wrap, [](void* const raw_work) { + const auto unwrap = static_cast*>(raw_work); + (*unwrap)(); + delete unwrap; + }); +} + +void DispatchSync(const dispatch_queue_t queue, Executor::Operation work) { + // Unlike dispatch_async_f, dispatch_sync_f blocks until the work passed to it + // is done, so passing a reference to a local variable is okay. + dispatch_sync_f(queue, &work, [](void* const raw_work) { + const auto unwrap = static_cast(raw_work); + (*unwrap)(); + }); +} + +// Represents a "busy" time slot on the schedule. +// +// Since libdispatch doesn't provide a way to cancel a scheduled operation, once +// a slot is created, it will always stay in the schedule until the time is +// past. Consequently, it is more useful to think of a time slot than +// a particular scheduled operation -- by the time the slot comes, operation may +// or may not be there (imagine getting to a meeting and finding out it's been +// canceled). +// +// Precondition: all member functions, including the constructor, are *only* +// invoked on the Firestore queue. +// +// Ownership: +// +// - `TimeSlot` is exclusively owned by libdispatch; +// - `ExecutorLibdispatch` contains non-owning pointers to `TimeSlot`s; +// - invariant: if the executor contains a pointer to a `TimeSlot`, it is +// a valid object. It is achieved because when libdispatch invokes +// a `TimeSlot`, it always removes it from the executor before deleting it. +// The reverse is not true: a canceled time slot is removed from the executor, +// but won't be destroyed until its original due time is past. + +class TimeSlot { + public: + TimeSlot(ExecutorLibdispatch* executor, + Executor::Milliseconds delay, + Executor::TaggedOperation&& operation); + + // Returns the operation that was scheduled for this time slot and turns the + // slot into a no-op. + Executor::TaggedOperation Unschedule(); + + bool operator<(const TimeSlot& rhs) const { + return target_time_ < rhs.target_time_; + } + bool operator==(const Executor::Tag tag) const { + return tagged_.tag == tag; + } + + void MarkDone() { + done_ = true; + } + + static void InvokedByLibdispatch(void* const raw_self); + + private: + void Execute(); + void RemoveFromSchedule(); + + using TimePoint = std::chrono::time_point; + + ExecutorLibdispatch* const executor_; + const TimePoint target_time_; // Used for sorting + Executor::TaggedOperation tagged_; + + // True if the operation has either been run or canceled. + // + // Note on thread-safety: because the precondition is that all member + // functions of this class are executed on the dispatch queue, no + // synchronization is required for `done_`. + bool done_ = false; +}; + +TimeSlot::TimeSlot(ExecutorLibdispatch* const executor, + const Executor::Milliseconds delay, + Executor::TaggedOperation&& operation) + : executor_{executor}, + target_time_{std::chrono::time_point_cast( + std::chrono::system_clock::now()) + + delay}, + tagged_{std::move(operation)} { +} + +Executor::TaggedOperation TimeSlot::Unschedule() { + if (!done_) { + RemoveFromSchedule(); + } + return std::move(tagged_); +} + +void TimeSlot::InvokedByLibdispatch(void* const raw_self) { + auto const self = static_cast(raw_self); + self->Execute(); + delete self; +} + +void TimeSlot::Execute() { + if (done_) { + // `done_` might mean that the executor is already destroyed, so don't call + // `RemoveFromSchedule`. + return; + } + + RemoveFromSchedule(); + + FIREBASE_ASSERT_MESSAGE(tagged_.operation, + "TimeSlot contains an invalid function object"); + tagged_.operation(); +} + +void TimeSlot::RemoveFromSchedule() { + executor_->RemoveFromSchedule(this); +} + +// ExecutorLibdispatch + +ExecutorLibdispatch::ExecutorLibdispatch(const dispatch_queue_t dispatch_queue) + : dispatch_queue_{dispatch_queue} { +} +ExecutorLibdispatch::ExecutorLibdispatch() + : ExecutorLibdispatch{dispatch_queue_create("com.google.firebase.firestore", + DISPATCH_QUEUE_SERIAL)} { +} + +ExecutorLibdispatch::~ExecutorLibdispatch() { + // Turn any operations that might still be in the queue into no-ops, lest + // they try to access `ExecutorLibdispatch` after it gets destroyed. Because + // the queue is serial, by the time libdispatch gets to the newly-enqueued + // work, the pending operations that might have been in progress would have + // already finished. + RunSynchronized(this, [this] { + for (auto slot : schedule_) { + slot->MarkDone(); + } + }); +} + +bool ExecutorLibdispatch::IsCurrentExecutor() const { + return GetCurrentQueueLabel().data() == GetTargetQueueLabel().data(); +} +std::string ExecutorLibdispatch::CurrentExecutorName() const { + return GetCurrentQueueLabel().data(); +} +std::string ExecutorLibdispatch::Name() const { + return GetTargetQueueLabel().data(); +} + +void ExecutorLibdispatch::Execute(Operation&& operation) { + DispatchAsync(dispatch_queue(), std::move(operation)); +} +void ExecutorLibdispatch::ExecuteBlocking(Operation&& operation) { + DispatchSync(dispatch_queue(), std::move(operation)); +} + +DelayedOperation ExecutorLibdispatch::Schedule(const Milliseconds delay, + TaggedOperation&& operation) { + namespace chr = std::chrono; + const dispatch_time_t delay_ns = dispatch_time( + DISPATCH_TIME_NOW, chr::duration_cast(delay).count()); + + // Ownership is fully transferred to libdispatch -- because it's impossible + // to truly cancel work after it's been dispatched, libdispatch is + // guaranteed to outlive the executor, and it's possible for work to be + // invoked by libdispatch after the executor is destroyed. Executor only + // stores an observer pointer to the operation. + + auto const time_slot = new TimeSlot{this, delay, std::move(operation)}; + dispatch_after_f(delay_ns, dispatch_queue(), time_slot, + TimeSlot::InvokedByLibdispatch); + RunSynchronized(this, [this, time_slot] { schedule_.push_back(time_slot); }); + return DelayedOperation{[this, time_slot] { + // `time_slot` might be destroyed by the time cancellation function runs. + // Therefore, don't access any methods on `time_slot`, only use it as + // a handle to remove from `schedule_`. + RemoveFromSchedule(time_slot); + }}; +} + +void ExecutorLibdispatch::RemoveFromSchedule(const TimeSlot* const to_remove) { + RunSynchronized(this, [this, to_remove] { + const auto found = std::find_if( + schedule_.begin(), schedule_.end(), + [to_remove](const TimeSlot* op) { return op == to_remove; }); + // It's possible for the operation to be missing if libdispatch gets to run + // it after it was force-run, for example. + if (found != schedule_.end()) { + (*found)->MarkDone(); + schedule_.erase(found); + } + }); +} + +// GetLabel functions are guaranteed to never return a "null" string_view +// (i.e. data() != nullptr). +absl::string_view ExecutorLibdispatch::GetCurrentQueueLabel() const { + // Note: dispatch_queue_get_label may return nullptr if the queue wasn't + // initialized with a label. + return StringViewFromDispatchLabel( + dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)); +} + +absl::string_view ExecutorLibdispatch::GetTargetQueueLabel() const { + return StringViewFromDispatchLabel( + dispatch_queue_get_label(dispatch_queue())); +} + +// Test-only methods + +bool ExecutorLibdispatch::IsScheduled(const Tag tag) const { + bool result = false; + RunSynchronized(this, [this, tag, &result] { + result = std::find_if(schedule_.begin(), schedule_.end(), + [&tag](const TimeSlot* const operation) { + return *operation == tag; + }) != schedule_.end(); + }); + return result; +} + +absl::optional +ExecutorLibdispatch::PopFromSchedule() { + absl::optional result; + + RunSynchronized(this, [this, &result] { + if (schedule_.empty()) { + return; + } + // Sorting upon each call to `PopFromSchedule` is inefficient, which is + // consciously ignored because this function is only ever called from tests. + std::sort( + schedule_.begin(), schedule_.end(), + [](const TimeSlot* lhs, const TimeSlot* rhs) { return *lhs < *rhs; }); + const auto nearest = schedule_.begin(); + result = (*nearest)->Unschedule(); + }); + + return result; +} + +} // namespace internal +} // namespace util +} // namespace firestore +} // namespace firebase -- cgit v1.2.3