blob: 3c063cd1b44332ea5ba9dd9fb75de03280be3c46 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/sync/model/processor_entity_tracker.h"
#include <algorithm>
#include <utility>
#include "base/metrics/histogram_functions.h"
#include "base/strings/strcat.h"
#include "base/trace_event/memory_usage_estimator.h"
#include "components/sync/base/data_type.h"
#include "components/sync/model/processor_entity.h"
#include "components/sync/protocol/data_type_state_helper.h"
#include "components/sync/protocol/entity_metadata.pb.h"
#include "components/sync/protocol/proto_memory_estimations.h"
#include "components/sync/protocol/unique_position.pb.h"
namespace syncer {
ProcessorEntityTracker::ProcessorEntityTracker(
DataType type,
const sync_pb::DataTypeState& data_type_state,
std::map<std::string, std::unique_ptr<sync_pb::EntityMetadata>>
metadata_map)
: data_type_state_(data_type_state) {
DCHECK(
IsInitialSyncAtLeastPartiallyDone(data_type_state.initial_sync_state()));
size_t invalid_entities = 0;
for (auto& [storage_key, metadata] : metadata_map) {
std::unique_ptr<ProcessorEntity> entity =
ProcessorEntity::CreateFromMetadata(storage_key, std::move(*metadata));
if (!entity) {
// The persisted metadata was invalid. This should be very rare.
++invalid_entities;
continue;
}
const ClientTagHash client_tag_hash =
ClientTagHash::FromHashed(entity->metadata().client_tag_hash());
DCHECK(storage_key_to_tag_hash_.find(entity->storage_key()) ==
storage_key_to_tag_hash_.end());
DCHECK(entities_.find(client_tag_hash) == entities_.end());
storage_key_to_tag_hash_[entity->storage_key()] = client_tag_hash;
entities_[client_tag_hash] = std::move(entity);
}
std::string suffix = DataTypeToHistogramSuffix(type);
base::UmaHistogramCounts1000(
base::StrCat({"Sync.EntityTracker.InvalidEntitiesOnLoad.", suffix}),
invalid_entities);
base::UmaHistogramCounts1000(
base::StrCat({"Sync.EntityTracker.TotalEntitiesOnLoad.", suffix}),
metadata_map.size());
}
ProcessorEntityTracker::~ProcessorEntityTracker() = default;
bool ProcessorEntityTracker::AllStorageKeysPopulated() const {
for (const auto& [client_tag_hash, entity] : entities_) {
if (entity->storage_key().empty()) {
return false;
}
}
if (entities_.size() != storage_key_to_tag_hash_.size()) {
return false;
}
return true;
}
void ProcessorEntityTracker::ClearTransientSyncState() {
for (const auto& [client_tag_hash, entity] : entities_) {
entity->ClearTransientSyncState();
}
}
size_t ProcessorEntityTracker::CountNonTombstoneEntries() const {
size_t count = 0;
for (const auto& [client_tag_hash, entity] : entities_) {
if (!entity->metadata().is_deleted()) {
++count;
}
}
return count;
}
ProcessorEntity* ProcessorEntityTracker::AddUnsyncedLocal(
const std::string& storage_key,
std::unique_ptr<EntityData> data,
sync_pb::EntitySpecifics trimmed_specifics,
std::optional<sync_pb::UniquePosition> unique_position) {
DCHECK(data);
DCHECK(!data->client_tag_hash.value().empty());
DCHECK(!GetEntityForTagHash(data->client_tag_hash));
DCHECK(!data->is_deleted());
DCHECK(!storage_key.empty());
ProcessorEntity* entity = AddInternal(storage_key, *data);
entity->RecordLocalUpdate(std::move(data), std::move(trimmed_specifics),
std::move(unique_position));
return entity;
}
ProcessorEntity* ProcessorEntityTracker::AddRemote(
const std::string& storage_key,
const UpdateResponseData& update_data,
sync_pb::EntitySpecifics trimmed_specifics,
std::optional<sync_pb::UniquePosition> unique_position) {
const EntityData& data = update_data.entity;
DCHECK(!data.client_tag_hash.value().empty());
DCHECK(!GetEntityForTagHash(data.client_tag_hash));
DCHECK(!data.is_deleted());
DCHECK(storage_key_to_tag_hash_.find(storage_key) ==
storage_key_to_tag_hash_.end());
DCHECK(update_data.response_version != kUncommittedVersion);
ProcessorEntity* entity = AddInternal(storage_key, data);
entity->RecordAcceptedRemoteUpdate(update_data, std::move(trimmed_specifics),
std::move(unique_position));
return entity;
}
void ProcessorEntityTracker::RemoveEntityForClientTagHash(
const ClientTagHash& client_tag_hash) {
DCHECK(
IsInitialSyncAtLeastPartiallyDone(data_type_state_.initial_sync_state()));
DCHECK(!client_tag_hash.value().empty());
const ProcessorEntity* entity = GetEntityForTagHash(client_tag_hash);
if (entity == nullptr || entity->storage_key().empty()) {
entities_.erase(client_tag_hash);
} else {
DCHECK(storage_key_to_tag_hash_.find(entity->storage_key()) !=
storage_key_to_tag_hash_.end());
RemoveEntityForStorageKey(entity->storage_key());
}
}
void ProcessorEntityTracker::RemoveEntityForStorageKey(
const std::string& storage_key) {
DCHECK(
IsInitialSyncAtLeastPartiallyDone(data_type_state_.initial_sync_state()));
// Look-up the client tag hash.
auto iter = storage_key_to_tag_hash_.find(storage_key);
if (iter == storage_key_to_tag_hash_.end()) {
// Missing is as good as untracked as far as the model is concerned.
return;
}
DCHECK_EQ(entities_[iter->second]->storage_key(), storage_key);
entities_.erase(iter->second);
storage_key_to_tag_hash_.erase(iter);
}
std::vector<std::string> ProcessorEntityTracker::RemoveInactiveCollaborations(
const base::flat_set<std::string>& active_collaborations) {
CHECK(
IsInitialSyncAtLeastPartiallyDone(data_type_state_.initial_sync_state()));
std::vector<std::string> removed_storage_keys;
absl::erase_if(entities_, [&removed_storage_keys,
&active_collaborations](const auto& item) {
const std::unique_ptr<ProcessorEntity>& entity = item.second;
if (!active_collaborations.contains(
entity->metadata().collaboration().collaboration_id())) {
// The storage key should never be empty because there shouldn't be
// updates for inactive collaborations (DataTypeWorker would filter them
// out).
CHECK(!entity->storage_key().empty());
removed_storage_keys.push_back(entity->storage_key());
return true;
}
return false;
});
for (const std::string& storage_key : removed_storage_keys) {
storage_key_to_tag_hash_.erase(storage_key);
}
return removed_storage_keys;
}
void ProcessorEntityTracker::ClearStorageKey(const std::string& storage_key) {
DCHECK(!storage_key.empty());
ProcessorEntity* entity = GetEntityForStorageKey(storage_key);
DCHECK(entity);
DCHECK_EQ(entity->storage_key(), storage_key);
storage_key_to_tag_hash_.erase(storage_key);
entity->ClearStorageKey();
}
size_t ProcessorEntityTracker::EstimateMemoryUsage() const {
size_t memory_usage = 0;
memory_usage += sync_pb::EstimateMemoryUsage(data_type_state_);
memory_usage += base::trace_event::EstimateMemoryUsage(entities_);
memory_usage +=
base::trace_event::EstimateMemoryUsage(storage_key_to_tag_hash_);
return memory_usage;
}
ProcessorEntity* ProcessorEntityTracker::GetEntityForTagHash(
const ClientTagHash& tag_hash) {
return const_cast<ProcessorEntity*>(
static_cast<const ProcessorEntityTracker*>(this)->GetEntityForTagHash(
tag_hash));
}
const ProcessorEntity* ProcessorEntityTracker::GetEntityForTagHash(
const ClientTagHash& tag_hash) const {
auto it = entities_.find(tag_hash);
return it != entities_.end() ? it->second.get() : nullptr;
}
ProcessorEntity* ProcessorEntityTracker::GetEntityForStorageKey(
const std::string& storage_key) {
return const_cast<ProcessorEntity*>(
static_cast<const ProcessorEntityTracker*>(this)->GetEntityForStorageKey(
storage_key));
}
const ProcessorEntity* ProcessorEntityTracker::GetEntityForStorageKey(
const std::string& storage_key) const {
auto iter = storage_key_to_tag_hash_.find(storage_key);
if (iter == storage_key_to_tag_hash_.end()) {
return nullptr;
}
return GetEntityForTagHash(iter->second);
}
std::vector<const ProcessorEntity*>
ProcessorEntityTracker::GetAllEntitiesIncludingTombstones() const {
std::vector<const ProcessorEntity*> entities;
entities.reserve(entities_.size());
for (const auto& [client_tag_hash, entity] : entities_) {
entities.push_back(entity.get());
}
return entities;
}
std::vector<std::string> ProcessorEntityTracker::GetAllStorageKeys() const {
std::vector<std::string> storage_keys;
storage_keys.reserve(storage_key_to_tag_hash_.size());
for (const auto& [storage_key, client_tag_hash] : storage_key_to_tag_hash_) {
storage_keys.push_back(storage_key);
}
return storage_keys;
}
std::vector<ProcessorEntity*>
ProcessorEntityTracker::GetEntitiesWithLocalChanges(size_t max_entries) {
std::vector<ProcessorEntity*> entities;
for (const auto& [client_tag_hash, entity] : entities_) {
if (entity->RequiresCommitRequest() && !entity->RequiresCommitData()) {
entities.push_back(entity.get());
if (entities.size() >= max_entries) {
break;
}
}
}
return entities;
}
bool ProcessorEntityTracker::HasLocalChanges() const {
for (const auto& [client_tag_hash, entity] : entities_) {
if (entity->RequiresCommitRequest()) {
return true;
}
}
return false;
}
size_t ProcessorEntityTracker::GetUnsyncedDataCount() const {
return std::ranges::count_if(
entities_, [](const auto& pair) { return pair.second->IsUnsynced(); });
}
size_t ProcessorEntityTracker::size() const {
return entities_.size();
}
std::vector<const ProcessorEntity*>
ProcessorEntityTracker::IncrementSequenceNumberForAllExcept(
const std::unordered_set<std::string>& already_updated_storage_keys) {
std::vector<const ProcessorEntity*> affected_entities;
for (const auto& [client_tag_hash, entity] : entities_) {
if (entity->storage_key().empty() ||
(already_updated_storage_keys.find(entity->storage_key()) !=
already_updated_storage_keys.end())) {
// Entities with empty storage key were already processed. ProcessUpdate()
// incremented their sequence numbers and cached commit data. Their
// metadata will be persisted in UpdateStorageKey().
continue;
}
entity->IncrementSequenceNumber(base::Time::Now());
affected_entities.push_back(entity.get());
}
return affected_entities;
}
void ProcessorEntityTracker::UpdateOrOverrideStorageKey(
const ClientTagHash& client_tag_hash,
const std::string& storage_key) {
ProcessorEntity* entity = GetEntityForTagHash(client_tag_hash);
DCHECK(entity);
// If the entity already had a storage key, clear it.
const std::string previous_storage_key = entity->storage_key();
DCHECK_NE(previous_storage_key, storage_key);
if (!previous_storage_key.empty()) {
ClearStorageKey(previous_storage_key);
}
DCHECK(storage_key_to_tag_hash_.find(previous_storage_key) ==
storage_key_to_tag_hash_.end());
// Populate the new storage key in the existing entity.
entity->SetStorageKey(storage_key);
DCHECK(storage_key_to_tag_hash_.find(storage_key) ==
storage_key_to_tag_hash_.end());
storage_key_to_tag_hash_[storage_key] = client_tag_hash;
}
ProcessorEntity* ProcessorEntityTracker::AddInternal(
const std::string& storage_key,
const EntityData& data) {
DCHECK(!data.client_tag_hash.value().empty());
DCHECK(!GetEntityForTagHash(data.client_tag_hash));
DCHECK(storage_key.empty() || storage_key_to_tag_hash_.find(storage_key) ==
storage_key_to_tag_hash_.end());
std::unique_ptr<ProcessorEntity> entity = ProcessorEntity::CreateNew(
storage_key, data.client_tag_hash, data.id, data.creation_time);
ProcessorEntity* entity_ptr = entity.get();
entities_[data.client_tag_hash] = std::move(entity);
if (!storage_key.empty()) {
storage_key_to_tag_hash_[storage_key] = data.client_tag_hash;
}
return entity_ptr;
}
} // namespace syncer