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<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<FieldFilter>>
memoized_flattened_filters_;
};

explicit Filter(std::shared_ptr<const Rep>&& rep) : rep_(rep) {
Expand Down
18 changes: 9 additions & 9 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
13 changes: 11 additions & 2 deletions Firestore/core/src/core/query.h
Original file line number Diff line number Diff line change
Expand Up @@ -277,8 +277,17 @@ 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_;
/**
dconeybe marked this conversation as resolved.
Show resolved Hide resolved
* The memoized list of sort orders.
*
* 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::shared_ptr<ThreadSafeMemoizer<OrderBy>>
memoized_normalized_order_bys_ =
std::make_shared<ThreadSafeMemoizer<OrderBy>>();

int32_t limit_ = Target::kNoLimit;
LimitType limit_type_ = LimitType::None;
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 std::vector<T>& memoize(std::function<std::vector<T>()> func) {
std::call_once(once_, [&]() { memoized_value_ = func(); });
return memoized_value_;
}

private:
std::once_flag once_;
std::vector<T> memoized_value_;
dconeybe marked this conversation as resolved.
Show resolved Hide resolved
};

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

#endif // FIRESTORE_CORE_SRC_UTIL_THREAD_SAFE_MEMOIZER_H_
Loading