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

Log warning instead of failing SDK when discovering dangling target. #6685

Merged
merged 3 commits into from
Oct 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Firestore/core/src/local/leveldb_target_cache.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "Firestore/core/src/model/document_key_set.h"
#include "Firestore/core/src/nanopb/byte_string.h"
#include "Firestore/core/src/nanopb/reader.h"
#include "Firestore/core/src/util/log.h"
#include "Firestore/core/src/util/string_apple.h"
#include "absl/strings/match.h"

Expand Down Expand Up @@ -168,11 +169,12 @@ absl::optional<TargetData> LevelDbTargetCache::GetTarget(const Target& target) {
std::string target_key = LevelDbTargetKey::Key(row_key.target_id());
target_iterator->Seek(target_key);
if (!target_iterator->Valid() || target_iterator->key() != target_key) {
HARD_FAIL(
LOG_WARN(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HARD_FAIL could not return, which effectively ensured that the loop terminated and that the target_iterator would be both valid and point to a record that matched target_key. Changing this to LOG_WARN allows the code to continue, breaking this condition.

I think the right thing to do is add a continue below this to avoid trying to parse target_iterator->value().

It's probably also worth adding a unit test to validate that this works. You can do this by writing a dummy row using LevelDbQueryTargetKey with a canonical ID that matches the query but has an invalid target ID.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

"Dangling query-target reference found: "
"%s points to %s; seeking there found %s",
DescribeKey(index_iterator), DescribeKey(target_key),
DescribeKey(target_iterator));
continue;
}

// Finally after finding a potential match, check that the target is
Expand Down
21 changes: 21 additions & 0 deletions Firestore/core/test/unit/local/leveldb_target_cache_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "Firestore/core/src/local/leveldb_target_cache.h"

#include "Firestore/core/include/firebase/firestore/timestamp.h"
#include "Firestore/core/src/local/leveldb_key.h"
#include "Firestore/core/src/local/leveldb_persistence.h"
#include "Firestore/core/src/local/persistence.h"
#include "Firestore/core/src/local/target_data.h"
Expand Down Expand Up @@ -58,6 +59,10 @@ class LevelDbTargetCacheTest : public TargetCacheTestBase {
LevelDbTargetCache* leveldb_cache() {
return static_cast<LevelDbTargetCache*>(persistence_->target_cache());
}

LevelDbPersistence* leveldb_persistence() {
return static_cast<LevelDbPersistence*>(persistence_.get());
}
};

TEST_F(LevelDbTargetCacheTest, MetadataPersistedAcrossRestarts) {
Expand Down Expand Up @@ -130,6 +135,22 @@ TEST_F(LevelDbTargetCacheTest, RemoveMatchingKeysForTargetID) {
});
}

// We see user issues where target data is missing for some reason, and the root
// cause is unknown. This test makes sure the SDK proceeds even when this
// happens. See: https://github.com/firebase/firebase-ios-sdk/issues/6644
TEST_F(LevelDbTargetCacheTest, SurvivesMissingTargetData) {
persistence_->Run("test_remove_matching_keys_for_target_id", [&]() {
TargetData target_data = MakeTargetData(query_rooms_);
cache_->AddTarget(target_data);
TargetId target_id = target_data.target_id();
std::string key = LevelDbTargetKey::Key(target_id);
leveldb_persistence()->current_transaction()->Delete(key);

auto result = cache_->GetTarget(query_rooms_.ToTarget());
ASSERT_EQ(result, absl::nullopt);
});
}

} // namespace local
} // namespace firestore
} // namespace firebase