diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj index ac9c117034c..cea40812f94 100644 --- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj +++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj @@ -73,6 +73,7 @@ 095A878BB33211AB52BFAD9F /* leveldb_document_overlay_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AE89CFF09C6804573841397F /* leveldb_document_overlay_cache_test.cc */; }; 0963F6D7B0F9AE1E24B82866 /* path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 403DBF6EFB541DFD01582AA3 /* path_test.cc */; }; 096BA3A3703AC1491F281618 /* index.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 395E8B07639E69290A929695 /* index.pb.cc */; }; + 09B83B26E47B6F6668DF54B8 /* thread_safe_memoizer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1A8141230C7E3986EACEF0B6 /* thread_safe_memoizer_test.cc */; }; 09BE8C01EC33D1FD82262D5D /* aggregate_query_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AF924C79F49F793992A84879 /* aggregate_query_test.cc */; }; 0A4E1B5E3E853763AE6ED7AE /* grpc_stream_tester.cc in Sources */ = {isa = PBXBuildFile; fileRef = 87553338E42B8ECA05BA987E /* grpc_stream_tester.cc */; }; 0A52B47C43B7602EE64F53A7 /* cc_compilation_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1B342370EAE3AA02393E33EB /* cc_compilation_test.cc */; }; @@ -229,6 +230,7 @@ 205601D1C6A40A4DD3BBAA04 /* target_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 526D755F65AC676234F57125 /* target_test.cc */; }; 20814A477D00EA11D0E76631 /* FIRDocumentSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04B202154AA00B64F25 /* FIRDocumentSnapshotTests.mm */; }; 20A26E9D0336F7F32A098D05 /* Pods_Firestore_IntegrationTests_tvOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2220F583583EFC28DE792ABE /* Pods_Firestore_IntegrationTests_tvOS.framework */; }; + 20A93AC59CD5A7AC41F10412 /* thread_safe_memoizer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1A8141230C7E3986EACEF0B6 /* thread_safe_memoizer_test.cc */; }; 211A60ECA3976D27C0BF59BB /* md5_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 3D050936A2D52257FD17FB6E /* md5_test.cc */; }; 21836C4D9D48F962E7A3A244 /* ordered_code_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380D03201BC6E400D97691 /* ordered_code_test.cc */; }; 21A2A881F71CB825299DF06E /* hard_assert_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 444B7AB3F5A2929070CB1363 /* hard_assert_test.cc */; }; @@ -500,6 +502,7 @@ 50454F81EC4584D4EB5F5ED5 /* serializer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 61F72C5520BC48FD001A68CB /* serializer_test.cc */; }; 50B749CA98365368AE34B71C /* filter_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F02F734F272C3C70D1307076 /* filter_test.cc */; }; 50C852E08626CFA7DC889EEA /* field_index_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = BF76A8DA34B5B67B4DD74666 /* field_index_test.cc */; }; + 51018EA27CF914DD1CC79CB3 /* thread_safe_memoizer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1A8141230C7E3986EACEF0B6 /* thread_safe_memoizer_test.cc */; }; 513D34C9964E8C60C5C2EE1C /* leveldb_bundle_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8E9CD82E60893DDD7757B798 /* leveldb_bundle_cache_test.cc */; }; 5150E9F256E6E82D6F3CB3F1 /* bundle_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F7FC06E0A47D393DE1759AE1 /* bundle_cache_test.cc */; }; 518BF03D57FBAD7C632D18F8 /* FIRQueryUnitTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = FF73B39D04D1760190E6B84A /* FIRQueryUnitTests.mm */; }; @@ -662,6 +665,7 @@ 5B4391097A6DF86EC3801DEE /* string_win_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 79507DF8378D3C42F5B36268 /* string_win_test.cc */; }; 5B62003FEA9A3818FDF4E2DD /* document_key_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6152AD5202A5385000E5744 /* document_key_test.cc */; }; 5B89B1BA0AD400D9BF581420 /* listen_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A01F315EE100DD57A1 /* listen_spec_test.json */; }; + 5BB33F0BC7960D26062B07D3 /* thread_safe_memoizer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1A8141230C7E3986EACEF0B6 /* thread_safe_memoizer_test.cc */; }; 5BC8406FD842B2FC2C200B2F /* stream_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5B5414D28802BC76FDADABD6 /* stream_test.cc */; }; 5BE49546D57C43DDFCDB6FBD /* to_string_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = B68B1E002213A764008977EF /* to_string_apple_test.mm */; }; 5C9B5696644675636A052018 /* token_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = A082AFDD981B07B5AD78FDE8 /* token_test.cc */; }; @@ -789,6 +793,7 @@ 6D7F70938662E8CA334F11C2 /* target_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B5C37696557C81A6C2B7271A /* target_cache_test.cc */; }; 6DBB3DB3FD6B4981B7F26A55 /* FIRQuerySnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04F202154AA00B64F25 /* FIRQuerySnapshotTests.mm */; }; 6DCA8E54E652B78EFF3EEDAC /* XCTestCase+Await.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0372021401E00B64F25 /* XCTestCase+Await.mm */; }; + 6DFD49CCE2281CE243FEBB63 /* thread_safe_memoizer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1A8141230C7E3986EACEF0B6 /* thread_safe_memoizer_test.cc */; }; 6E10507432E1D7AE658D16BD /* FSTSpecTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E03020213FFC00B64F25 /* FSTSpecTests.mm */; }; 6E4854B19B120C6F0F8192CC /* FSTAPIHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04E202154AA00B64F25 /* FSTAPIHelpers.mm */; }; 6E59498D20F55BA800ECD9A5 /* FuzzingResources in Resources */ = {isa = PBXBuildFile; fileRef = 6ED6DEA120F5502700FC6076 /* FuzzingResources */; }; @@ -1162,6 +1167,7 @@ B2554A2BA211D10823646DBE /* Validation_BloomFilterTest_MD5_500_01_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BD051DBE754950FEAC7A446 /* Validation_BloomFilterTest_MD5_500_01_bloom_filter_proto.json */; }; B28ACC69EB1F232AE612E77B /* async_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 872C92ABD71B12784A1C5520 /* async_testing.cc */; }; B2A9965ED0114E39A911FD09 /* Validation_BloomFilterTest_MD5_5000_1_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = 4375BDCDBCA9938C7F086730 /* Validation_BloomFilterTest_MD5_5000_1_bloom_filter_proto.json */; }; + B31B5E0D4EA72C5916CC71F5 /* thread_safe_memoizer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1A8141230C7E3986EACEF0B6 /* thread_safe_memoizer_test.cc */; }; B371628DA91E80B64AE53085 /* FIRFieldPathTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04C202154AA00B64F25 /* FIRFieldPathTests.mm */; }; B384E0F90D4CCC15C88CAF30 /* target_index_matcher_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 63136A2371C0C013EC7A540C /* target_index_matcher_test.cc */; }; B3A309CCF5D75A555C7196E1 /* path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 403DBF6EFB541DFD01582AA3 /* path_test.cc */; }; @@ -1675,6 +1681,7 @@ 132E32997D781B896672D30A /* reference_set_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = reference_set_test.cc; sourceTree = ""; }; 166CE73C03AB4366AAC5201C /* leveldb_index_manager_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = leveldb_index_manager_test.cc; sourceTree = ""; }; 1A7D48A017ECB54FD381D126 /* Validation_BloomFilterTest_MD5_5000_1_membership_test_result.json */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.json; name = Validation_BloomFilterTest_MD5_5000_1_membership_test_result.json; path = bloom_filter_golden_test_data/Validation_BloomFilterTest_MD5_5000_1_membership_test_result.json; sourceTree = ""; }; + 1A8141230C7E3986EACEF0B6 /* thread_safe_memoizer_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; path = thread_safe_memoizer_test.cc; sourceTree = ""; }; 1B342370EAE3AA02393E33EB /* cc_compilation_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; name = cc_compilation_test.cc; path = api/cc_compilation_test.cc; sourceTree = ""; }; 1B9F95EC29FAD3F100EEC075 /* FIRAggregateQueryUnitTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRAggregateQueryUnitTests.mm; sourceTree = ""; }; 1C01D8CE367C56BB2624E299 /* index.pb.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = index.pb.h; path = admin/index.pb.h; sourceTree = ""; }; @@ -2361,6 +2368,7 @@ 79507DF8378D3C42F5B36268 /* string_win_test.cc */, 899FC22684B0F7BEEAE13527 /* task_test.cc */, A002425BC4FC4E805F4175B6 /* testing_hooks_test.cc */, + 1A8141230C7E3986EACEF0B6 /* thread_safe_memoizer_test.cc */, B68B1E002213A764008977EF /* to_string_apple_test.mm */, B696858D2214B53900271095 /* to_string_test.cc */, ); @@ -4260,6 +4268,7 @@ 88929ED628DA8DD9592974ED /* task_test.cc in Sources */, 9B2C6A48A4DBD36080932B4E /* testing_hooks_test.cc in Sources */, 32A95242C56A1A230231DB6A /* testutil.cc in Sources */, + 51018EA27CF914DD1CC79CB3 /* thread_safe_memoizer_test.cc in Sources */, 5497CB78229DECDE000FB92F /* time_testing.cc in Sources */, ACC9369843F5ED3BD2284078 /* timestamp_test.cc in Sources */, 2AAEABFD550255271E3BAC91 /* to_string_apple_test.mm in Sources */, @@ -4477,6 +4486,7 @@ 67CF9FAA890307780731E1DA /* task_test.cc in Sources */, 24B75C63BDCD5551B2F69901 /* testing_hooks_test.cc in Sources */, 8388418F43042605FB9BFB92 /* testutil.cc in Sources */, + 5BB33F0BC7960D26062B07D3 /* thread_safe_memoizer_test.cc in Sources */, 5497CB79229DECDE000FB92F /* time_testing.cc in Sources */, 26CB3D7C871BC56456C6021E /* timestamp_test.cc in Sources */, 5BE49546D57C43DDFCDB6FBD /* to_string_apple_test.mm in Sources */, @@ -4715,6 +4725,7 @@ 76A5447D76F060E996555109 /* task_test.cc in Sources */, D0DA42DC66C4FE508A63B269 /* testing_hooks_test.cc in Sources */, 409C0F2BFC2E1BECFFAC4D32 /* testutil.cc in Sources */, + B31B5E0D4EA72C5916CC71F5 /* thread_safe_memoizer_test.cc in Sources */, 6300709ECDE8E0B5A8645F8D /* time_testing.cc in Sources */, 0CEE93636BA4852D3C5EC428 /* timestamp_test.cc in Sources */, 95DCD082374F871A86EF905F /* to_string_apple_test.mm in Sources */, @@ -4953,6 +4964,7 @@ 93C8F772F4DC5A985FA3D815 /* task_test.cc in Sources */, F6738D3B72352BBEFB87172C /* testing_hooks_test.cc in Sources */, A17DBC8F24127DA8A381F865 /* testutil.cc in Sources */, + 09B83B26E47B6F6668DF54B8 /* thread_safe_memoizer_test.cc in Sources */, A25FF76DEF542E01A2DF3B0E /* time_testing.cc in Sources */, 1E42CD0F60EB22A5D0C86D1F /* timestamp_test.cc in Sources */, F9705E595FC3818F13F6375A /* to_string_apple_test.mm in Sources */, @@ -5180,6 +5192,7 @@ 662793139A36E5CFC935B949 /* task_test.cc in Sources */, F184E5367DF3CA158EDE8532 /* testing_hooks_test.cc in Sources */, 54A0352A20A3B3BD003E0143 /* testutil.cc in Sources */, + 20A93AC59CD5A7AC41F10412 /* thread_safe_memoizer_test.cc in Sources */, 5497CB77229DECDE000FB92F /* time_testing.cc in Sources */, ABF6506C201131F8005F2C74 /* timestamp_test.cc in Sources */, B68B1E012213A765008977EF /* to_string_apple_test.mm in Sources */, @@ -5437,6 +5450,7 @@ C57B15CADD8C3E806B154C19 /* task_test.cc in Sources */, 5360D52DCAD1069B1E4B0B9D /* testing_hooks_test.cc in Sources */, CA989C0E6020C372A62B7062 /* testutil.cc in Sources */, + 6DFD49CCE2281CE243FEBB63 /* thread_safe_memoizer_test.cc in Sources */, 2D220B9ABFA36CD7AC43D0A7 /* time_testing.cc in Sources */, D91D86B29B86A60C05879A48 /* timestamp_test.cc in Sources */, 60260A06871DCB1A5F3448D3 /* to_string_apple_test.mm in Sources */, diff --git a/Firestore/core/src/core/composite_filter.cc b/Firestore/core/src/core/composite_filter.cc index 6b6ac7607b3..5c917d4e2a1 100644 --- a/Firestore/core/src/core/composite_filter.cc +++ b/Firestore/core/src/core/composite_filter.cc @@ -154,14 +154,14 @@ const model::FieldPath* CompositeFilter::Rep::GetFirstInequalityField() const { const std::vector& CompositeFilter::Rep::GetFlattenedFilters() const { - if (Filter::Rep::memoized_flattened_filters_.empty() && !filters().empty()) { - for (const auto& filter : filters()) { + return memoized_flattened_filters_->memoize([&]() { + std::vector 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 diff --git a/Firestore/core/src/core/field_filter.cc b/Firestore/core/src/core/field_filter.cc index b6686465501..eb882e0db77 100644 --- a/Firestore/core/src/core/field_filter.cc +++ b/Firestore/core/src/core/field_filter.cc @@ -124,11 +124,10 @@ FieldFilter::FieldFilter(std::shared_ptr rep) const std::vector& 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{ + return memoized_flattened_filters_->memoize([&]() { + return std::vector{ FieldFilter(std::make_shared(*this))}; - } - return Filter::Rep::memoized_flattened_filters_; + }); } std::vector FieldFilter::Rep::GetFilters() const { diff --git a/Firestore/core/src/core/filter.cc b/Firestore/core/src/core/filter.cc index e180a8ce568..a77ccc55e34 100644 --- a/Firestore/core/src/core/filter.cc +++ b/Firestore/core/src/core/filter.cc @@ -18,6 +18,9 @@ #include +#include "Firestore/core/src/core/field_filter.h" +#include "Firestore/core/src/util/thread_safe_memoizer.h" + namespace firebase { namespace firestore { namespace core { @@ -32,6 +35,12 @@ std::ostream& operator<<(std::ostream& os, const Filter& filter) { return os << filter.ToString(); } +Filter::Rep::Rep() + : memoized_flattened_filters_( + std::make_shared< + util::ThreadSafeMemoizer>>()) { +} + } // namespace core } // namespace firestore } // namespace firebase diff --git a/Firestore/core/src/core/filter.h b/Firestore/core/src/core/filter.h index 1785ff5a9d0..1da4888f826 100644 --- a/Firestore/core/src/core/filter.h +++ b/Firestore/core/src/core/filter.h @@ -23,10 +23,10 @@ #include #include "Firestore/core/src/model/model_fwd.h" +#include "Firestore/core/src/util/thread_safe_memoizer.h" namespace firebase { namespace firestore { - namespace core { class FieldFilter; @@ -122,6 +122,8 @@ class Filter { protected: class Rep { public: + Rep(); + virtual ~Rep() = default; virtual Type type() const { @@ -162,8 +164,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` 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 memoized_flattened_filters_; + mutable std::shared_ptr>> + memoized_flattened_filters_; }; explicit Filter(std::shared_ptr&& rep) : rep_(rep) { diff --git a/Firestore/core/src/core/query.cc b/Firestore/core/src/core/query.cc index d6a48bb9c69..eb089ff2359 100644 --- a/Firestore/core/src/core/query.cc +++ b/Firestore/core/src/core/query.cc @@ -90,19 +90,21 @@ absl::optional Query::FindOpInsideFilters( } const std::vector& Query::normalized_order_bys() const { - if (memoized_normalized_order_bys_.empty()) { + return memoized_normalized_order_bys_->memoize([&]() { + std::vector 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), }; @@ -114,8 +116,7 @@ const std::vector& Query::normalized_order_bys() const { first_order_by_field->CanonicalString(), inequality_field->CanonicalString()); - std::vector 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()) { @@ -132,11 +133,10 @@ const std::vector& 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 { @@ -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 Query::ToTarget( - const std::vector& order_bys) const& { +Target Query::ToTarget(const std::vector& order_bys) const { if (limit_type_ == LimitType::Last) { // Flip the orderBy directions since we want the last results std::vector new_order_bys; @@ -366,13 +359,11 @@ const std::shared_ptr Query::ToTarget( start_at_->position(), start_at_->inclusive())} : absl::nullopt; - Target target(path(), collection_group(), filters(), new_order_bys, limit_, + return Target(path(), collection_group(), filters(), new_order_bys, limit_, new_start_at, new_end_at); - return std::make_shared(std::move(target)); } else { - Target target(path(), collection_group(), filters(), order_bys, limit_, + return Target(path(), collection_group(), filters(), order_bys, limit_, start_at(), end_at()); - return std::make_shared(std::move(target)); } } diff --git a/Firestore/core/src/core/query.h b/Firestore/core/src/core/query.h index d85d99a4824..047f2e24bbb 100644 --- a/Firestore/core/src/core/query.h +++ b/Firestore/core/src/core/query.h @@ -30,6 +30,7 @@ #include "Firestore/core/src/core/target.h" #include "Firestore/core/src/model/model_fwd.h" #include "Firestore/core/src/model/resource_path.h" +#include "Firestore/core/src/util/thread_safe_memoizer.h" namespace firebase { namespace firestore { @@ -277,25 +278,34 @@ class Query { // sort at the end. std::vector explicit_order_bys_; - // The memoized list of sort orders. - mutable std::vector memoized_normalized_order_bys_; - int32_t limit_ = Target::kNoLimit; LimitType limit_type_ = LimitType::None; absl::optional start_at_; absl::optional end_at_; + Target ToTarget(const std::vector& order_bys) const; + + // For properties below, use a `std::shared_ptr` 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>> + memoized_normalized_order_bys_{ + std::make_shared>>()}; + // The corresponding Target of this Query instance. - mutable std::shared_ptr memoized_target; + mutable std::shared_ptr> memoized_target_{ + std::make_shared>()}; // 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 memoized_aggregate_target; - - const std::shared_ptr ToTarget( - const std::vector& order_bys) const&; + mutable std::shared_ptr> + memoized_aggregate_target_{ + std::make_shared>()}; }; bool operator==(const Query& lhs, const Query& rhs); diff --git a/Firestore/core/src/util/thread_safe_memoizer.h b/Firestore/core/src/util/thread_safe_memoizer.h new file mode 100644 index 00000000000..ba06312136c --- /dev/null +++ b/Firestore/core/src/util/thread_safe_memoizer.h @@ -0,0 +1,82 @@ +/* + * 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 +#include // NOLINT(build/c++11) +#include + +namespace firebase { +namespace firestore { +namespace util { + +/** + * Stores a memoized value in a manner that is safe to be shared between + * multiple threads. + * + * TODO(b/299933587) Make `ThreadSafeMemoizer` copyable and moveable. + */ +template +class ThreadSafeMemoizer { + public: + ThreadSafeMemoizer() = default; + + ~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_, [&]() {}); + } + + // This class cannot be copied or moved, because it has `std::once_flag` + // member. + ThreadSafeMemoizer(const ThreadSafeMemoizer&) = delete; + ThreadSafeMemoizer(ThreadSafeMemoizer&&) = delete; + ThreadSafeMemoizer& operator=(const ThreadSafeMemoizer&) = delete; + ThreadSafeMemoizer& operator=(ThreadSafeMemoizer&&) = delete; + + /** + * 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 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_ diff --git a/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc new file mode 100644 index 00000000000..89a1d94dc25 --- /dev/null +++ b/Firestore/core/test/unit/util/thread_safe_memoizer_test.cc @@ -0,0 +1,62 @@ +/* + * 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. + */ + +#include "Firestore/core/src/util/thread_safe_memoizer.h" + +#include // NOLINT(build/c++11) +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace util { + +TEST(ThreadSafeMemoizerTest, MultiThreadedMemoization) { + std::atomic global_int{77}; + + auto expensive_lambda = [&]() { + // Simulate an expensive operation + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + // If the lambda gets executed multiple times, threads will see incremented + // `global_int`. + global_int++; + return global_int.load(); + }; + + const int num_threads = 5; + const int expected_result = 78; + + // Create a thread safe memoizer and multiple threads. + util::ThreadSafeMemoizer memoized_result; + std::vector threads; + + for (int i = 0; i < num_threads; ++i) { + threads.emplace_back( + [&memoized_result, expected_result, &expensive_lambda]() { + const int& actual_result = memoized_result.memoize(expensive_lambda); + + // Verify that all threads get the same memoized result. + EXPECT_EQ(actual_result, expected_result); + }); + } + + for (auto& thread : threads) { + thread.join(); + } +} + +} // namespace util +} // namespace firestore +} // namespace firebase