Skip to content

Commit f576aa1

Browse files
GC targets without deserializing Target model (#7026)
1 parent b4c77b6 commit f576aa1

15 files changed

+230
-73
lines changed

Firestore/CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
# Unreleased
2+
- [fixed] Fixed a crash that could happen when the SDK encountered invalid
3+
data during garbage collection (#6721).
4+
15
# v7.2.0
2-
- [added] Made emulator connection API consistent between Auth, Database, Firestore, and Functions (#5916).
6+
- [added] Made emulator connection API consistent between Auth, Database,
7+
Firestore, and Functions (#5916).
38

49
# v7.1.0
510
- [changed] Added the original query data to error messages for Queries that

Firestore/core/src/local/leveldb_lru_reference_delegate.cc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ void LevelDbLruReferenceDelegate::RemoveMutationReference(
7878
void LevelDbLruReferenceDelegate::RemoveTarget(const TargetData& target_data) {
7979
TargetData updated =
8080
target_data.WithSequenceNumber(current_sequence_number());
81-
db_->target_cache()->UpdateTarget(std::move(updated));
81+
db_->target_cache()->UpdateTarget(updated);
8282
}
8383

8484
void LevelDbLruReferenceDelegate::UpdateLimboDocument(const DocumentKey& key) {
@@ -119,9 +119,9 @@ size_t LevelDbLruReferenceDelegate::GetSequenceNumberCount() {
119119
return total_count;
120120
}
121121

122-
void LevelDbLruReferenceDelegate::EnumerateTargets(
123-
const TargetCallback& callback) {
124-
db_->target_cache()->EnumerateTargets(callback);
122+
void LevelDbLruReferenceDelegate::EnumerateTargetSequenceNumbers(
123+
const SequenceNumberCallback& callback) {
124+
db_->target_cache()->EnumerateSequenceNumbers(callback);
125125
}
126126

127127
void LevelDbLruReferenceDelegate::EnumerateOrphanedDocuments(

Firestore/core/src/local/leveldb_lru_reference_delegate.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ class LevelDbLruReferenceDelegate : public LruDelegate {
6060
util::StatusOr<int64_t> CalculateByteSize() override;
6161
size_t GetSequenceNumberCount() override;
6262

63-
void EnumerateTargets(const TargetCallback& callback) override;
63+
void EnumerateTargetSequenceNumbers(
64+
const SequenceNumberCallback& callback) override;
6465
void EnumerateOrphanedDocuments(
6566
const OrphanedDocumentCallback& callback) override;
6667

Firestore/core/src/local/leveldb_target_cache.cc

Lines changed: 65 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "Firestore/core/src/local/leveldb_target_cache.h"
1818

1919
#include <string>
20+
#include <unordered_set>
2021
#include <utility>
2122

2223
#include "Firestore/core/src/local/leveldb_key.h"
@@ -124,7 +125,7 @@ void LevelDbTargetCache::UpdateTarget(const TargetData& target_data) {
124125
void LevelDbTargetCache::RemoveTarget(const TargetData& target_data) {
125126
TargetId target_id = target_data.target_id();
126127

127-
RemoveAllKeysForTarget(target_id);
128+
RemoveAllDocumentKeysForTarget(target_id);
128129

129130
std::string key = LevelDbTargetKey::Key(target_id);
130131
db_->current_transaction()->Delete(key);
@@ -188,35 +189,57 @@ absl::optional<TargetData> LevelDbTargetCache::GetTarget(const Target& target) {
188189
return absl::nullopt;
189190
}
190191

191-
void LevelDbTargetCache::EnumerateTargets(const TargetCallback& callback) {
192+
void LevelDbTargetCache::EnumerateSequenceNumbers(
193+
const SequenceNumberCallback& callback) {
192194
// Enumerate all targets, give their sequence numbers.
193195
std::string target_prefix = LevelDbTargetKey::KeyPrefix();
194196
auto it = db_->current_transaction()->NewIterator();
195197
it->Seek(target_prefix);
196198
for (; it->Valid() && absl::StartsWith(it->key(), target_prefix);
197199
it->Next()) {
198-
TargetData target = DecodeTarget(it->value());
199-
callback(target);
200+
StringReader reader{it->value()};
201+
auto target_proto = DecodeTargetProto(&reader);
202+
callback(target_proto->last_listen_sequence_number);
200203
}
201204
}
202205

203-
int LevelDbTargetCache::RemoveTargets(
206+
size_t LevelDbTargetCache::RemoveTargets(
204207
ListenSequenceNumber upper_bound,
205208
const std::unordered_map<model::TargetId, TargetData>& live_targets) {
206-
int count = 0;
207209
std::string target_prefix = LevelDbTargetKey::KeyPrefix();
208210
auto it = db_->current_transaction()->NewIterator();
209211
it->Seek(target_prefix);
212+
213+
std::unordered_set<TargetId> removed_targets;
214+
215+
// In https://github.com/firebase/firebase-ios-sdk/issues/6721, a customer
216+
// reports that their client crashes when deserializing an invalid Target
217+
// during an LRU run. Instead of deserializing the value into a full Target
218+
// model, we only convert it into the underlying Protobuf message.
210219
for (; it->Valid() && absl::StartsWith(it->key(), target_prefix);
211220
it->Next()) {
212-
TargetData target_data = DecodeTarget(it->value());
213-
if (target_data.sequence_number() <= upper_bound &&
214-
live_targets.find(target_data.target_id()) == live_targets.end()) {
215-
RemoveTarget(target_data);
216-
count++;
221+
StringReader reader{it->value()};
222+
auto target_proto = DecodeTargetProto(&reader);
223+
if (target_proto->last_listen_sequence_number <= upper_bound &&
224+
live_targets.find(target_proto->target_id) == live_targets.end()) {
225+
TargetId target_id = target_proto->target_id;
226+
227+
// Remove the DocumentKey to TargetId mapping
228+
RemoveAllDocumentKeysForTarget(target_id);
229+
// Remove the TargetId to Target mapping
230+
db_->current_transaction()->Delete(it->key());
231+
232+
removed_targets.insert(target_id);
217233
}
218234
}
219-
return count;
235+
236+
// Remove the CanonicalId to TargetId mapping
237+
RemoveQueryTargetKeyForTargets(removed_targets);
238+
239+
metadata_->target_count -= removed_targets.size();
240+
SaveMetadata();
241+
242+
return removed_targets.size();
220243
}
221244

222245
void LevelDbTargetCache::AddMatchingKeys(const DocumentKeySet& keys,
@@ -247,7 +270,7 @@ void LevelDbTargetCache::RemoveMatchingKeys(const DocumentKeySet& keys,
247270
}
248271
}
249272

250-
void LevelDbTargetCache::RemoveAllKeysForTarget(TargetId target_id) {
273+
void LevelDbTargetCache::RemoveAllDocumentKeysForTarget(TargetId target_id) {
251274
std::string index_prefix = LevelDbTargetDocumentKey::KeyPrefix(target_id);
252275
auto index_iterator = db_->current_transaction()->NewIterator();
253276
index_iterator->Seek(index_prefix);
@@ -269,6 +292,24 @@ void LevelDbTargetCache::RemoveAllKeysForTarget(TargetId target_id) {
269292
}
270293
}
271294

295+
void LevelDbTargetCache::RemoveQueryTargetKeyForTargets(
296+
const std::unordered_set<TargetId>& target_ids) {
297+
std::string index_prefix = LevelDbQueryTargetKey::KeyPrefix();
298+
auto index_iterator = db_->current_transaction()->NewIterator();
299+
index_iterator->Seek(index_prefix);
300+
301+
LevelDbQueryTargetKey row_key;
302+
for (; index_iterator->Valid(); index_iterator->Next()) {
303+
if (!row_key.Decode(index_iterator->key())) {
304+
break;
305+
}
306+
307+
if (target_ids.find(row_key.target_id()) != target_ids.end()) {
308+
db_->current_transaction()->Delete(index_iterator->key());
309+
}
310+
}
311+
}
312+
272313
DocumentKeySet LevelDbTargetCache::GetMatchingKeys(TargetId target_id) {
273314
std::string index_prefix = LevelDbTargetDocumentKey::KeyPrefix(target_id);
274315
auto index_iterator = db_->current_transaction()->NewIterator();
@@ -386,12 +427,21 @@ void LevelDbTargetCache::SaveMetadata() {
386427
db_->current_transaction()->Put(LevelDbTargetGlobalKey::Key(), metadata_);
387428
}
388429

430+
nanopb::Message<firestore_client_Target> LevelDbTargetCache::DecodeTargetProto(
431+
nanopb::Reader* reader) {
432+
auto message = Message<firestore_client_Target>::TryParse(reader);
433+
if (!reader->ok()) {
434+
HARD_FAIL("Target proto failed to parse: %s", reader->status().ToString());
435+
}
436+
return message;
437+
}
438+
389439
TargetData LevelDbTargetCache::DecodeTarget(absl::string_view encoded) {
390440
StringReader reader{encoded};
391-
auto message = Message<firestore_client_Target>::TryParse(&reader);
441+
auto message = DecodeTargetProto(&reader);
392442
auto result = serializer_->DecodeTargetData(&reader, *message);
393443
if (!reader.ok()) {
394-
HARD_FAIL("Target proto failed to parse: %s, message: %s",
444+
HARD_FAIL("Target failed to parse: %s, message: %s",
395445
reader.status().ToString(), message.ToString());
396446
}
397447

Firestore/core/src/local/leveldb_target_cache.h

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#define FIRESTORE_CORE_SRC_LOCAL_LEVELDB_TARGET_CACHE_H_
1919

2020
#include <unordered_map>
21+
#include <unordered_set>
2122

2223
#include "Firestore/Protos/nanopb/firestore/local/target.nanopb.h"
2324
#include "Firestore/core/src/local/target_cache.h"
@@ -71,11 +72,12 @@ class LevelDbTargetCache : public TargetCache {
7172

7273
absl::optional<TargetData> GetTarget(const core::Target& target) override;
7374

74-
void EnumerateTargets(const TargetCallback& callback) override;
75+
void EnumerateSequenceNumbers(
76+
const SequenceNumberCallback& callback) override;
7577

76-
int RemoveTargets(model::ListenSequenceNumber upper_bound,
77-
const std::unordered_map<model::TargetId, TargetData>&
78-
live_targets) override;
78+
size_t RemoveTargets(model::ListenSequenceNumber upper_bound,
79+
const std::unordered_map<model::TargetId, TargetData>&
80+
live_targets) override;
7981

8082
// Key-related methods
8183

@@ -91,8 +93,8 @@ class LevelDbTargetCache : public TargetCache {
9193
void RemoveMatchingKeys(const model::DocumentKeySet& keys,
9294
model::TargetId target_id) override;
9395

94-
/** Removes all the keys in the query results of the given target ID. */
95-
void RemoveAllKeysForTarget(model::TargetId target_id);
96+
/** Removes all document keys in the query results of the given target ID. */
97+
void RemoveAllDocumentKeysForTarget(model::TargetId target_id);
9698

9799
model::DocumentKeySet GetMatchingKeys(model::TargetId target_id) override;
98100

@@ -128,12 +130,20 @@ class LevelDbTargetCache : public TargetCache {
128130
bool UpdateMetadata(const TargetData& target_data);
129131
void SaveMetadata();
130132

133+
/** Parses the given bytes as a `firestore_client_Target` protocol buffer. */
134+
nanopb::Message<firestore_client_Target> DecodeTargetProto(
135+
nanopb::Reader* reader);
136+
131137
/**
132138
* Parses the given bytes as a `firestore_client_Target` protocol buffer and
133139
* then converts to the equivalent target data.
134140
*/
135141
TargetData DecodeTarget(absl::string_view encoded);
136142

143+
/** Removes the given targets from the query to target mapping. */
144+
void RemoveQueryTargetKeyForTargets(
145+
const std::unordered_set<model::TargetId>& target_id);
146+
137147
// The LevelDbTargetCache is owned by LevelDbPersistence.
138148
LevelDbPersistence* db_;
139149
// Owned by LevelDbPersistence.

Firestore/core/src/local/lru_garbage_collector.cc

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,9 +197,10 @@ ListenSequenceNumber LruGarbageCollector::SequenceNumberForQueryCount(
197197

198198
RollingSequenceNumberBuffer buffer(query_count);
199199

200-
delegate_->EnumerateTargets([&buffer](const TargetData& target_data) {
201-
buffer.AddElement(target_data.sequence_number());
202-
});
200+
delegate_->EnumerateTargetSequenceNumbers(
201+
[&buffer](ListenSequenceNumber sequence_number) {
202+
buffer.AddElement(sequence_number);
203+
});
203204

204205
delegate_->EnumerateOrphanedDocuments(
205206
[&buffer](const DocumentKey&, ListenSequenceNumber sequence_number) {

Firestore/core/src/local/lru_garbage_collector.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,11 @@ class LruDelegate : public ReferenceDelegate {
7878
virtual size_t GetSequenceNumberCount() = 0;
7979

8080
/**
81-
* Enumerates all the targets that the delegate is aware of. This is typically
82-
* all of the targets in an TargetCache.
81+
* Enumerates the sequence numbers of all the targets that the delegate is
82+
* aware of. This is typically all sequence numbers in an TargetCache.
8383
*/
84-
virtual void EnumerateTargets(const TargetCallback& callback) = 0;
84+
virtual void EnumerateTargetSequenceNumbers(
85+
const SequenceNumberCallback& callback) = 0;
8586

8687
/**
8788
* Enumerates all of the outstanding mutations.

Firestore/core/src/local/memory_lru_reference_delegate.cc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,9 @@ void MemoryLruReferenceDelegate::OnTransactionCommitted() {
8686
current_sequence_number_ = kListenSequenceNumberInvalid;
8787
}
8888

89-
void MemoryLruReferenceDelegate::EnumerateTargets(
90-
const TargetCallback& callback) {
91-
return persistence_->target_cache()->EnumerateTargets(callback);
89+
void MemoryLruReferenceDelegate::EnumerateTargetSequenceNumbers(
90+
const SequenceNumberCallback& callback) {
91+
return persistence_->target_cache()->EnumerateSequenceNumbers(callback);
9292
}
9393

9494
void MemoryLruReferenceDelegate::EnumerateOrphanedDocuments(

Firestore/core/src/local/memory_lru_reference_delegate.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ class MemoryLruReferenceDelegate : public LruDelegate {
6868
util::StatusOr<int64_t> CalculateByteSize() override;
6969
size_t GetSequenceNumberCount() override;
7070

71-
void EnumerateTargets(const TargetCallback& callback) override;
71+
void EnumerateTargetSequenceNumbers(
72+
const SequenceNumberCallback& callback) override;
7273
void EnumerateOrphanedDocuments(
7374
const OrphanedDocumentCallback& callback) override;
7475

Firestore/core/src/local/memory_target_cache.cc

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,14 @@ absl::optional<TargetData> MemoryTargetCache::GetTarget(const Target& target) {
6868
return iter == targets_.end() ? absl::optional<TargetData>{} : iter->second;
6969
}
7070

71-
void MemoryTargetCache::EnumerateTargets(const TargetCallback& callback) {
71+
void MemoryTargetCache::EnumerateSequenceNumbers(
72+
const SequenceNumberCallback& callback) {
7273
for (const auto& kv : targets_) {
73-
callback(kv.second);
74+
callback(kv.second.sequence_number());
7475
}
7576
}
7677

77-
int MemoryTargetCache::RemoveTargets(
78+
size_t MemoryTargetCache::RemoveTargets(
7879
model::ListenSequenceNumber upper_bound,
7980
const std::unordered_map<TargetId, TargetData>& live_targets) {
8081
std::vector<const Target*> to_remove;
@@ -93,7 +94,7 @@ int MemoryTargetCache::RemoveTargets(
9394
for (const Target* element : to_remove) {
9495
targets_.erase(*element);
9596
}
96-
return static_cast<int>(to_remove.size());
97+
return to_remove.size();
9798
}
9899

99100
void MemoryTargetCache::AddMatchingKeys(const DocumentKeySet& keys,

0 commit comments

Comments
 (0)