blob: 662e7ebdb117cc0fef9bf7486128c4085b4d0a2d [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/sync/model_impl/processor_entity_tracker.h"
#include "base/base64.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/sha1.h"
#include "base/strings/stringprintf.h"
#include "base/trace_event/memory_usage_estimator.h"
#include "components/sync/base/sync_base_switches.h"
#include "components/sync/base/time.h"
#include "components/sync/engine/non_blocking_sync_common.h"
#include "components/sync/protocol/proto_memory_estimations.h"
namespace syncer {
namespace {
// Max number of sever version for which E2E latency is calculated.
// Used for E2E latency measurements with UMA.
const size_t kMaxTrackedCommittedServerVersions = 20;
void HashSpecifics(const sync_pb::EntitySpecifics& specifics,
std::string* hash) {
DCHECK_GT(specifics.ByteSize(), 0);
base::Base64Encode(base::SHA1HashString(specifics.SerializeAsString()), hash);
}
} // namespace
std::unique_ptr<ProcessorEntityTracker> ProcessorEntityTracker::CreateNew(
const std::string& storage_key,
const std::string& client_tag_hash,
const std::string& id,
base::Time creation_time) {
// Initialize metadata
sync_pb::EntityMetadata metadata;
metadata.set_client_tag_hash(client_tag_hash);
if (!id.empty())
metadata.set_server_id(id);
metadata.set_sequence_number(0);
metadata.set_acked_sequence_number(0);
metadata.set_server_version(kUncommittedVersion);
metadata.set_creation_time(TimeToProtoTime(creation_time));
return std::unique_ptr<ProcessorEntityTracker>(
new ProcessorEntityTracker(storage_key, &metadata));
}
std::unique_ptr<ProcessorEntityTracker>
ProcessorEntityTracker::CreateFromMetadata(const std::string& storage_key,
sync_pb::EntityMetadata* metadata) {
DCHECK(!storage_key.empty());
return std::unique_ptr<ProcessorEntityTracker>(
new ProcessorEntityTracker(storage_key, metadata));
}
ProcessorEntityTracker::ProcessorEntityTracker(
const std::string& storage_key,
sync_pb::EntityMetadata* metadata)
: storage_key_(storage_key),
commit_requested_sequence_number_(metadata->acked_sequence_number()) {
DCHECK(metadata->has_client_tag_hash());
DCHECK(metadata->has_creation_time());
metadata_.Swap(metadata);
}
ProcessorEntityTracker::~ProcessorEntityTracker() {}
void ProcessorEntityTracker::SetStorageKey(const std::string& storage_key) {
DCHECK(storage_key_.empty());
DCHECK(!storage_key.empty());
storage_key_ = storage_key;
}
void ProcessorEntityTracker::SetCommitData(EntityData* data) {
DCHECK(data);
// Update data's fields from metadata.
data->client_tag_hash = metadata_.client_tag_hash();
if (!metadata_.server_id().empty())
data->id = metadata_.server_id();
data->creation_time = ProtoTimeToTime(metadata_.creation_time());
data->modification_time = ProtoTimeToTime(metadata_.modification_time());
commit_data_.reset();
CacheCommitData(data->PassToPtr());
}
void ProcessorEntityTracker::CacheCommitData(const EntityDataPtr& data_ptr) {
DCHECK(RequiresCommitData());
commit_data_ = data_ptr;
DCHECK(HasCommitData());
}
bool ProcessorEntityTracker::HasCommitData() const {
return !commit_data_->client_tag_hash.empty();
}
bool ProcessorEntityTracker::MatchesData(const EntityData& data) const {
if (metadata_.is_deleted())
return data.is_deleted();
if (data.is_deleted())
return false;
return MatchesSpecificsHash(data.specifics);
}
bool ProcessorEntityTracker::MatchesBaseData(const EntityData& data) const {
DCHECK(IsUnsynced());
if (data.is_deleted() || metadata_.base_specifics_hash().empty()) {
return false;
}
std::string hash;
HashSpecifics(data.specifics, &hash);
return hash == metadata_.base_specifics_hash();
}
bool ProcessorEntityTracker::IsUnsynced() const {
return metadata_.sequence_number() > metadata_.acked_sequence_number();
}
bool ProcessorEntityTracker::RequiresCommitRequest() const {
return metadata_.sequence_number() > commit_requested_sequence_number_;
}
bool ProcessorEntityTracker::RequiresCommitData() const {
return RequiresCommitRequest() && !HasCommitData() && !metadata_.is_deleted();
}
bool ProcessorEntityTracker::CanClearMetadata() const {
return metadata_.is_deleted() && !IsUnsynced();
}
bool ProcessorEntityTracker::UpdateIsReflection(int64_t update_version) const {
return metadata_.server_version() >= update_version;
}
void ProcessorEntityTracker::RecordEntityUpdateLatency(int64_t update_version,
const ModelType& type) {
auto first_greater =
unsynced_time_per_committed_server_version_.upper_bound(update_version);
if (first_greater == unsynced_time_per_committed_server_version_.begin()) {
return;
}
DCHECK(base::FeatureList::IsEnabled(switches::kSyncE2ELatencyMeasurement));
for (auto it = unsynced_time_per_committed_server_version_.begin();
it != first_greater; ++it) {
const base::TimeDelta latency = base::Time::Now() - it->second;
base::UmaHistogramLongTimes(
std::string("Sync.E2ELatency.") + ModelTypeToHistogramSuffix(type),
latency);
}
unsynced_time_per_committed_server_version_.erase(
unsynced_time_per_committed_server_version_.begin(), first_greater);
}
void ProcessorEntityTracker::RecordIgnoredUpdate(
const UpdateResponseData& update) {
DCHECK(metadata_.server_id().empty() ||
metadata_.server_id() == update.entity->id);
metadata_.set_server_id(update.entity->id);
metadata_.set_server_version(update.response_version);
// Either these already matched, acked was just bumped to squash a pending
// commit and this should follow, or the pending commit needs to be requeued.
commit_requested_sequence_number_ = metadata_.acked_sequence_number();
// If local change was made while server assigned a new id to the entity,
// update id in cached commit data.
if (HasCommitData() && commit_data_->id != metadata_.server_id()) {
DCHECK(commit_data_->id.empty());
commit_data_ = commit_data_->UpdateId(metadata_.server_id());
}
}
void ProcessorEntityTracker::RecordAcceptedUpdate(
const UpdateResponseData& update) {
DCHECK(!IsUnsynced());
RecordIgnoredUpdate(update);
metadata_.set_is_deleted(update.entity->is_deleted());
metadata_.set_modification_time(
TimeToProtoTime(update.entity->modification_time));
UpdateSpecificsHash(update.entity->specifics);
}
void ProcessorEntityTracker::RecordForcedUpdate(
const UpdateResponseData& update) {
DCHECK(IsUnsynced());
// There was a conflict and the server just won it. Explicitly ack all
// pending commits so they are never enqueued again.
metadata_.set_acked_sequence_number(metadata_.sequence_number());
commit_data_.reset();
RecordAcceptedUpdate(update);
}
void ProcessorEntityTracker::MakeLocalChange(std::unique_ptr<EntityData> data) {
DCHECK(!metadata_.client_tag_hash().empty());
// Update metadata fields from updated data.
base::Time modification_time = !data->modification_time.is_null()
? data->modification_time
: base::Time::Now();
// IncrementSequenceNumber should be called before UpdateSpecificHash since
// it remembers specifics hash before the modifications.
IncrementSequenceNumber(modification_time);
UpdateSpecificsHash(data->specifics);
if (!data->creation_time.is_null())
metadata_.set_creation_time(TimeToProtoTime(data->creation_time));
metadata_.set_modification_time(TimeToProtoTime(modification_time));
metadata_.set_is_deleted(false);
// SetCommitData will update data's fields from metadata and wrap it into
// immutable EntityDataPtr.
SetCommitData(data.get());
}
bool ProcessorEntityTracker::Delete() {
IncrementSequenceNumber(base::Time::Now());
metadata_.set_modification_time(TimeToProtoTime(base::Time::Now()));
metadata_.set_is_deleted(true);
metadata_.clear_specifics_hash();
// Clear any cached pending commit data.
commit_data_.reset();
// Return true if server might know about this entity.
// TODO(crbug/740757): This check will prevent sending tombstone in situation
// when it should have been sent under following conditions:
// - Original centity was committed to server, but client crashed before
// receiving response.
// - Entity was deleted while client was offline.
// Correct behavior is to send tombstone anyway, but directory based
// implementation doesn't and it is unclear how server will react to such
// tombstones. Change the behavior to always sending tombstone after
// experimenting with server.
return (metadata_.server_version() != kUncommittedVersion) ||
(commit_requested_sequence_number_ >
metadata_.acked_sequence_number());
}
void ProcessorEntityTracker::InitializeCommitRequestData(
CommitRequestData* request) {
if (!metadata_.is_deleted()) {
DCHECK(HasCommitData());
DCHECK_EQ(commit_data_->client_tag_hash, metadata_.client_tag_hash());
DCHECK_EQ(commit_data_->id, metadata_.server_id());
request->entity = commit_data_;
} else {
// Make an EntityData with empty specifics to indicate deletion. This is
// done lazily here to simplify loading a pending deletion on startup.
EntityData data;
data.client_tag_hash = metadata_.client_tag_hash();
data.id = metadata_.server_id();
data.creation_time = ProtoTimeToTime(metadata_.creation_time());
data.modification_time = ProtoTimeToTime(metadata_.modification_time());
request->entity = data.PassToPtr();
}
request->sequence_number = metadata_.sequence_number();
request->base_version = metadata_.server_version();
request->specifics_hash = metadata_.specifics_hash();
request->unsynced_time = unsynced_time_;
commit_requested_sequence_number_ = metadata_.sequence_number();
}
void ProcessorEntityTracker::ReceiveCommitResponse(
const CommitResponseData& data,
bool commit_only) {
DCHECK_EQ(metadata_.client_tag_hash(), data.client_tag_hash);
DCHECK_GT(data.sequence_number, metadata_.acked_sequence_number());
// Version is not valid for commit only types, as it's stripped before being
// sent to the server, so it cannot behave correctly.
DCHECK(commit_only || data.response_version > metadata_.server_version())
<< data.response_version << " vs " << metadata_.server_version();
if (base::FeatureList::IsEnabled(switches::kSyncE2ELatencyMeasurement) &&
unsynced_time_per_committed_server_version_.size() <
kMaxTrackedCommittedServerVersions) {
unsynced_time_per_committed_server_version_[metadata_.server_version()] =
data.unsynced_time;
}
// The server can assign us a new ID in a commit response.
metadata_.set_server_id(data.id);
metadata_.set_acked_sequence_number(data.sequence_number);
metadata_.set_server_version(data.response_version);
if (!IsUnsynced()) {
// Clear pending commit data if there hasn't been another commit request
// since the one that is currently getting acked.
commit_data_.reset();
metadata_.clear_base_specifics_hash();
} else {
metadata_.set_base_specifics_hash(data.specifics_hash);
// If local change was made while server assigned a new id to the entity,
// update id in cached commit data.
if (HasCommitData() && commit_data_->id != metadata_.server_id()) {
DCHECK(commit_data_->id.empty());
commit_data_ = commit_data_->UpdateId(metadata_.server_id());
}
}
}
void ProcessorEntityTracker::ClearTransientSyncState() {
// If we have any unacknowledged commit requests outstanding, they've been
// dropped and we should forget about them.
commit_requested_sequence_number_ = metadata_.acked_sequence_number();
}
void ProcessorEntityTracker::IncrementSequenceNumber(
base::Time modification_time) {
DCHECK(metadata_.has_sequence_number());
if (!IsUnsynced()) {
// Update the base specifics hash if this entity wasn't already out of sync.
metadata_.set_base_specifics_hash(metadata_.specifics_hash());
unsynced_time_ = modification_time;
}
metadata_.set_sequence_number(metadata_.sequence_number() + 1);
DCHECK(IsUnsynced());
}
size_t ProcessorEntityTracker::EstimateMemoryUsage() const {
using base::trace_event::EstimateMemoryUsage;
size_t memory_usage = 0;
memory_usage += EstimateMemoryUsage(storage_key_);
memory_usage += EstimateMemoryUsage(metadata_);
memory_usage += EstimateMemoryUsage(commit_data_);
memory_usage +=
EstimateMemoryUsage(unsynced_time_per_committed_server_version_);
return memory_usage;
}
bool ProcessorEntityTracker::MatchesSpecificsHash(
const sync_pb::EntitySpecifics& specifics) const {
DCHECK(!metadata_.is_deleted());
DCHECK_GT(specifics.ByteSize(), 0);
std::string hash;
HashSpecifics(specifics, &hash);
return hash == metadata_.specifics_hash();
}
void ProcessorEntityTracker::UpdateSpecificsHash(
const sync_pb::EntitySpecifics& specifics) {
if (specifics.ByteSize() > 0) {
HashSpecifics(specifics, metadata_.mutable_specifics_hash());
} else {
metadata_.clear_specifics_hash();
}
}
} // namespace syncer