Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

thread safe memoization #11781

Merged
merged 17 commits into from
Sep 13, 2023
12 changes: 6 additions & 6 deletions Firestore/core/src/core/composite_filter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -154,14 +154,14 @@ const model::FieldPath* CompositeFilter::Rep::GetFirstInequalityField() const {

const std::vector<FieldFilter>& CompositeFilter::Rep::GetFlattenedFilters()
const {
if (Filter::Rep::memoized_flattened_filters_.empty() && !filters().empty()) {
for (const auto& filter : filters()) {
return memoized_flattened_filters_->memoize([&]() {
dconeybe marked this conversation as resolved.
Show resolved Hide resolved
std::vector<FieldFilter> flattened_filters;
for (const auto& filter : filters())
std::copy(filter.GetFlattenedFilters().begin(),
filter.GetFlattenedFilters().end(),
std::back_inserter(Filter::Rep::memoized_flattened_filters_));
}
}
return Filter::Rep::memoized_flattened_filters_;
std::back_inserter(flattened_filters));
return flattened_filters;
});
}

} // namespace core
Expand Down
7 changes: 3 additions & 4 deletions Firestore/core/src/core/field_filter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,10 @@ FieldFilter::FieldFilter(std::shared_ptr<const Filter::Rep> rep)

const std::vector<FieldFilter>& FieldFilter::Rep::GetFlattenedFilters() const {
// This is already a field filter, so we return a vector of size one.
if (Filter::Rep::memoized_flattened_filters_.empty()) {
Filter::Rep::memoized_flattened_filters_ = std::vector<FieldFilter>{
return memoized_flattened_filters_->memoize([&]() {
return std::vector<FieldFilter>{
FieldFilter(std::make_shared<const Rep>(*this))};
}
return Filter::Rep::memoized_flattened_filters_;
});
}

std::vector<Filter> FieldFilter::Rep::GetFilters() const {
Expand Down
8 changes: 8 additions & 0 deletions Firestore/core/src/core/filter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@

#include <ostream>

#include "Firestore/core/src/core/field_filter.h"
#include "Firestore/core/src/util/thread_safe_memoizer.h"

namespace firebase {
namespace firestore {
namespace core {
Expand All @@ -32,6 +35,11 @@ std::ostream& operator<<(std::ostream& os, const Filter& filter) {
return os << filter.ToString();
}

Filter::Rep::Rep()
milaGGL marked this conversation as resolved.
Show resolved Hide resolved
: memoized_flattened_filters_(
std::make_shared<ThreadSafeMemoizer<std::vector<FieldFilter>>>()) {
}

} // namespace core
} // namespace firestore
} // namespace firebase
14 changes: 12 additions & 2 deletions Firestore/core/src/core/filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@
#include <vector>

#include "Firestore/core/src/model/model_fwd.h"
#include "Firestore/core/src/util/thread_safe_memoizer.h"

using firebase::firestore::util::ThreadSafeMemoizer;
dconeybe marked this conversation as resolved.
Show resolved Hide resolved

namespace firebase {
namespace firestore {

namespace core {

class FieldFilter;
Expand Down Expand Up @@ -122,6 +124,8 @@ class Filter {
protected:
class Rep {
public:
Rep();

virtual ~Rep() = default;

virtual Type type() const {
Expand Down Expand Up @@ -162,8 +166,14 @@ class Filter {
/**
* Memoized list of all field filters that can be found by
* traversing the tree of filters contained in this composite filter.
*
* Use a `std::shared_ptr<ThreadSafeMemoizer>` rather than using
* `ThreadSafeMemoizer` directly so that this class is copyable
* (`ThreadSafeMemoizer` is not copyable because of its `std::once_flag`
* member variable, which is not copyable).
*/
mutable std::vector<FieldFilter> memoized_flattened_filters_;
mutable std::shared_ptr<ThreadSafeMemoizer<std::vector<FieldFilter>>>
memoized_flattened_filters_;
};

explicit Filter(std::shared_ptr<const Rep>&& rep) : rep_(rep) {
Expand Down
39 changes: 16 additions & 23 deletions Firestore/core/src/core/query.cc
Original file line number Diff line number Diff line change
Expand Up @@ -90,19 +90,21 @@ absl::optional<Operator> Query::FindOpInsideFilters(
}

const std::vector<OrderBy>& Query::normalized_order_bys() const {
if (memoized_normalized_order_bys_.empty()) {
return memoized_normalized_order_bys_->memoize([&]() {
std::vector<OrderBy> result;
const FieldPath* inequality_field = InequalityFilterField();
const FieldPath* first_order_by_field = FirstOrderByField();

if (inequality_field && !first_order_by_field) {
// In order to implicitly add key ordering, we must also add the
// inequality filter field for it to be a valid query. Note that the
// default inequality field and key ordering is ascending.
if (inequality_field->IsKeyFieldPath()) {
memoized_normalized_order_bys_ = {
result = {
OrderBy(FieldPath::KeyFieldPath(), Direction::Ascending),
};
} else {
memoized_normalized_order_bys_ = {
result = {
OrderBy(*inequality_field, Direction::Ascending),
OrderBy(FieldPath::KeyFieldPath(), Direction::Ascending),
};
Expand All @@ -114,8 +116,7 @@ const std::vector<OrderBy>& Query::normalized_order_bys() const {
first_order_by_field->CanonicalString(),
inequality_field->CanonicalString());

std::vector<OrderBy> result = explicit_order_bys_;

result = explicit_order_bys_;
bool found_explicit_key_order = false;
for (const OrderBy& order_by : explicit_order_bys_) {
if (order_by.field().IsKeyFieldPath()) {
Expand All @@ -132,11 +133,10 @@ const std::vector<OrderBy>& Query::normalized_order_bys() const {
: explicit_order_bys_.back().direction();
result.emplace_back(FieldPath::KeyFieldPath(), last_direction);
}

memoized_normalized_order_bys_ = std::move(result);
}
}
return memoized_normalized_order_bys_;

return result;
});
}

const FieldPath* Query::FirstOrderByField() const {
Expand Down Expand Up @@ -329,23 +329,16 @@ std::string Query::ToString() const {
}

const Target& Query::ToTarget() const& {
if (memoized_target == nullptr) {
memoized_target = ToTarget(normalized_order_bys());
}

return *memoized_target;
return memoized_target_->memoize(
[&]() { return ToTarget(normalized_order_bys()); });
}

const Target& Query::ToAggregateTarget() const& {
if (memoized_aggregate_target == nullptr) {
memoized_aggregate_target = ToTarget(explicit_order_bys_);
}

return *memoized_aggregate_target;
return memoized_aggregate_target_->memoize(
[&]() { return ToTarget(explicit_order_bys_); });
}

const std::shared_ptr<const Target> Query::ToTarget(
const std::vector<OrderBy>& order_bys) const& {
Target Query::ToTarget(const std::vector<OrderBy>& order_bys) const {
if (limit_type_ == LimitType::Last) {
// Flip the orderBy directions since we want the last results
std::vector<OrderBy> new_order_bys;
Expand All @@ -368,11 +361,11 @@ const std::shared_ptr<const Target> Query::ToTarget(

Target target(path(), collection_group(), filters(), new_order_bys, limit_,
new_start_at, new_end_at);
return std::make_shared<Target>(std::move(target));
return target;
dconeybe marked this conversation as resolved.
Show resolved Hide resolved
} else {
Target target(path(), collection_group(), filters(), order_bys, limit_,
start_at(), end_at());
return std::make_shared<Target>(std::move(target));
return target;
}
}

Expand Down
27 changes: 19 additions & 8 deletions Firestore/core/src/core/query.h
Original file line number Diff line number Diff line change
Expand Up @@ -277,25 +277,36 @@ class Query {
// sort at the end.
std::vector<OrderBy> explicit_order_bys_;

// The memoized list of sort orders.
mutable std::vector<OrderBy> memoized_normalized_order_bys_;

int32_t limit_ = Target::kNoLimit;
LimitType limit_type_ = LimitType::None;

absl::optional<Bound> start_at_;
absl::optional<Bound> end_at_;

Target ToTarget(const std::vector<OrderBy>& order_bys) const;
dconeybe marked this conversation as resolved.
Show resolved Hide resolved

/**
dconeybe marked this conversation as resolved.
Show resolved Hide resolved
* For properties below, use a `std::shared_ptr<ThreadSafeMemoizer>` rather
* than using `ThreadSafeMemoizer` directly so that this class is copyable
* (`ThreadSafeMemoizer` is not copyable because of its `std::once_flag`
* member variable, which is not copyable).
*/

// The memoized list of sort orders.
mutable std::shared_ptr<ThreadSafeMemoizer<std::vector<OrderBy>>>
dconeybe marked this conversation as resolved.
Show resolved Hide resolved
memoized_normalized_order_bys_ =
std::make_shared<ThreadSafeMemoizer<std::vector<OrderBy>>>();

// The corresponding Target of this Query instance.
mutable std::shared_ptr<const Target> memoized_target;
mutable std::shared_ptr<ThreadSafeMemoizer<Target>> memoized_target_ =
std::make_shared<ThreadSafeMemoizer<Target>>();

// The corresponding aggregate Target of this Query instance. Unlike targets
// for non-aggregate queries, aggregate query targets do not contain
// normalized order-bys, they only contain explicit order-bys.
mutable std::shared_ptr<const Target> memoized_aggregate_target;

const std::shared_ptr<const Target> ToTarget(
const std::vector<OrderBy>& order_bys) const&;
mutable std::shared_ptr<ThreadSafeMemoizer<Target>>
memoized_aggregate_target_ =
std::make_shared<ThreadSafeMemoizer<Target>>();
};

bool operator==(const Query& lhs, const Query& rhs);
Expand Down
70 changes: 70 additions & 0 deletions Firestore/core/src/util/thread_safe_memoizer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright 2023 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.
*/

#ifndef FIRESTORE_CORE_SRC_UTIL_THREAD_SAFE_MEMOIZER_H_
#define FIRESTORE_CORE_SRC_UTIL_THREAD_SAFE_MEMOIZER_H_

#include <mutex> // NOLINT(build/c++11)
dconeybe marked this conversation as resolved.
Show resolved Hide resolved
#include <vector>

namespace firebase {
namespace firestore {
namespace util {

/**
* Stores a memoized value in a manner that is safe to be shared between
* multiple threads.
*/
template <typename T>
class ThreadSafeMemoizer {
dconeybe marked this conversation as resolved.
Show resolved Hide resolved
dconeybe marked this conversation as resolved.
Show resolved Hide resolved
public:
~ThreadSafeMemoizer() {
// Call `std::call_once` in order to synchronize with the "active"
// invocation of `memoize()`. Without this synchronization, there is a data
// race between this destructor, which "reads" `memoized_value_` to destroy
// it, and the write to `memoized_value_` done by the "active" invocation of
// `memoize()`.
std::call_once(once_, [&]() {});
dconeybe marked this conversation as resolved.
Show resolved Hide resolved
}

dconeybe marked this conversation as resolved.
Show resolved Hide resolved
/**
* Memoize a value.
*
* The std::function object specified by the first invocation of this
* function (the "active" invocation) will be invoked synchronously.
* None of the std::function objects specified by the subsequent
* invocations of this function (the "passive" invocations) will be
* invoked. All invocations, both "active" and "passive", will return a
* reference to the std::vector created by copying the return value from
* the std::function specified by the "active" invocation. It is,
* therefore, the "active" invocation's job to return the std::vector
* to memoize.
*/
const T& memoize(std::function<T()> func) {
std::call_once(once_, [&]() { memoized_value_ = func(); });
return memoized_value_;
}

private:
std::once_flag once_;
T memoized_value_;
};

} // namespace util
} // namespace firestore
} // namespace firebase

#endif // FIRESTORE_CORE_SRC_UTIL_THREAD_SAFE_MEMOIZER_H_
Loading