| // Copyright 2012 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/engine/syncer.h" |
| |
| #include <memory> |
| #include <string> |
| |
| #include "base/auto_reset.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/notreached.h" |
| #include "base/strings/strcat.h" |
| #include "base/time/time.h" |
| #include "base/trace_event/trace_event.h" |
| #include "components/sync/base/data_type.h" |
| #include "components/sync/engine/active_devices_invalidation_info.h" |
| #include "components/sync/engine/cancelation_signal.h" |
| #include "components/sync/engine/commit.h" |
| #include "components/sync/engine/commit_processor.h" |
| #include "components/sync/engine/cycle/nudge_tracker.h" |
| #include "components/sync/engine/cycle/sync_cycle.h" |
| #include "components/sync/engine/get_updates_delegate.h" |
| #include "components/sync/engine/get_updates_processor.h" |
| #include "components/sync/engine/sync_protocol_error.h" |
| #include "components/sync/engine/syncer_error.h" |
| #include "components/sync/engine/update_handler.h" |
| #include "components/sync/protocol/sync_enums.pb.h" |
| #include "net/http/http_status_code.h" |
| |
| namespace syncer { |
| |
| namespace { |
| |
| SyncerErrorValueForUma GetSyncerErrorValueForUma( |
| SyncProtocolErrorType protocol_error) { |
| switch (protocol_error) { |
| case SYNC_SUCCESS: |
| NOTREACHED(); |
| case NOT_MY_BIRTHDAY: |
| return SyncerErrorValueForUma::kServerReturnNotMyBirthday; |
| case THROTTLED: |
| return SyncerErrorValueForUma::kServerReturnThrottled; |
| case TRANSIENT_ERROR: |
| return SyncerErrorValueForUma::kServerReturnTransientError; |
| case MIGRATION_DONE: |
| return SyncerErrorValueForUma::kServerReturnMigrationDone; |
| case DISABLED_BY_ADMIN: |
| return SyncerErrorValueForUma::kServerReturnDisabledByAdmin; |
| case PARTIAL_FAILURE: |
| NOTREACHED(); |
| case CLIENT_DATA_OBSOLETE: |
| return SyncerErrorValueForUma::kServerReturnClientDataObsolete; |
| case ENCRYPTION_OBSOLETE: |
| return SyncerErrorValueForUma::kServerReturnEncryptionObsolete; |
| case UNKNOWN_ERROR: |
| return SyncerErrorValueForUma::kServerReturnUnknownError; |
| case CONFLICT: |
| return SyncerErrorValueForUma::kServerReturnConflict; |
| case INVALID_MESSAGE: |
| return SyncerErrorValueForUma::kServerReturnInvalidMessage; |
| } |
| NOTREACHED(); |
| } |
| |
| SyncerErrorValueForUma GetSyncerErrorValueForUma(const SyncerError& error) { |
| switch (error.type()) { |
| case SyncerError::Type::kSuccess: |
| return SyncerErrorValueForUma::kSyncerOk; |
| case SyncerError::Type::kNetworkError: |
| return SyncerErrorValueForUma::kNetworkConnectionUnavailable; |
| case SyncerError::Type::kHttpError: |
| if (error.GetHttpErrorOrDie() == net::HTTP_UNAUTHORIZED) { |
| return SyncerErrorValueForUma::kHttpAuthError; |
| } |
| return SyncerErrorValueForUma::kHttpError; |
| case SyncerError::Type::kProtocolError: |
| return GetSyncerErrorValueForUma(error.GetProtocolErrorOrDie()); |
| case SyncerError::Type::kProtocolViolationError: |
| return SyncerErrorValueForUma::kProtocolViolationError; |
| } |
| NOTREACHED(); |
| } |
| |
| // Returns the NudgedUpdateResult corresponding to the given SyncerError. Not |
| // used for `kSuccess`. |
| UpdateHandler::NudgedUpdateResult SyncerErrorToNudgedUpdateResult( |
| const SyncerError& error) { |
| switch (error.type()) { |
| case SyncerError::Type::kNetworkError: |
| return UpdateHandler::NudgedUpdateResult::kDownloadRequestNetworkError; |
| case SyncerError::Type::kHttpError: |
| case SyncerError::Type::kProtocolError: |
| case SyncerError::Type::kProtocolViolationError: |
| // Return server error for all non-network errors. |
| return UpdateHandler::NudgedUpdateResult::kDownloadRequestServerError; |
| case SyncerError::Type::kSuccess: |
| NOTREACHED(); |
| } |
| } |
| |
| // Returns invalidation info after applying updates. This is used to drop |
| // optimization flags if DeviceInfo has been just updated (and new subscriptions |
| // might be just received). Without that if a new device with enabled |
| // invalidations has been just received, it may be updated only in the next |
| // sync cycle due to delay between threads. |
| ActiveDevicesInvalidationInfo GetInvalidationInfo(const SyncCycle* cycle) { |
| if (cycle->status_controller().get_updated_types().Has(DEVICE_INFO)) { |
| return ActiveDevicesInvalidationInfo::CreateUninitialized(); |
| } |
| return cycle->context()->active_devices_invalidation_info(); |
| } |
| |
| void HandleCycleBegin(SyncCycle* cycle) { |
| cycle->mutable_status_controller()->UpdateStartTime(); |
| cycle->mutable_status_controller()->clear_updated_types(); |
| cycle->SendEventNotification(SyncCycleEvent::SYNC_CYCLE_BEGIN); |
| } |
| |
| void LogCommitResult(const SyncerError& error, |
| const DataTypeSet& request_types, |
| base::TimeTicks start_time) { |
| constexpr char kCommitLatencyPrefix[] = "Sync.CommitLatency"; |
| constexpr char kCommitResponsePrefix[] = "Sync.CommitResponse"; |
| base::TimeDelta latency = base::TimeTicks::Now() - start_time; |
| |
| base::UmaHistogramEnumeration(kCommitResponsePrefix, |
| GetSyncerErrorValueForUma(error)); |
| |
| const bool success = error.type() == SyncerError::Type::kSuccess; |
| if (success) { |
| base::UmaHistogramMediumTimes(kCommitLatencyPrefix, latency); |
| } |
| |
| for (DataType type : request_types) { |
| base::UmaHistogramEnumeration( |
| base::StrCat( |
| {kCommitResponsePrefix, ".", DataTypeToHistogramSuffix(type)}), |
| GetSyncerErrorValueForUma(error)); |
| if (success) { |
| base::UmaHistogramMediumTimes( |
| base::StrCat( |
| {kCommitLatencyPrefix, ".", DataTypeToHistogramSuffix(type)}), |
| latency); |
| } |
| } |
| } |
| |
| } // namespace |
| |
| Syncer::Syncer(CancelationSignal* cancelation_signal) |
| : cancelation_signal_(cancelation_signal) {} |
| |
| Syncer::~Syncer() = default; |
| |
| bool Syncer::IsSyncing() const { |
| return is_syncing_; |
| } |
| |
| bool Syncer::NormalSyncShare(DataTypeSet request_types, |
| NudgeTracker* nudge_tracker, |
| SyncCycle* cycle) { |
| base::AutoReset<bool> is_syncing(&is_syncing_, true); |
| HandleCycleBegin(cycle); |
| if (nudge_tracker->IsGetUpdatesRequired(request_types)) { |
| VLOG(1) << "Downloading types " << DataTypeSetToDebugString(request_types); |
| if (!DownloadAndApplyUpdates(&request_types, cycle, |
| NormalGetUpdatesDelegate(*nudge_tracker))) { |
| return HandleCycleEnd(cycle, nudge_tracker->GetOrigin()); |
| } |
| } |
| |
| SyncerError commit_result = |
| BuildAndPostCommits(request_types, nudge_tracker, cycle); |
| cycle->mutable_status_controller()->set_commit_result(commit_result); |
| |
| return HandleCycleEnd(cycle, nudge_tracker->GetOrigin()); |
| } |
| |
| bool Syncer::ConfigureSyncShare(const DataTypeSet& request_types, |
| sync_pb::SyncEnums::GetUpdatesOrigin origin, |
| SyncCycle* cycle) { |
| base::AutoReset<bool> is_syncing(&is_syncing_, true); |
| |
| // It is possible during configuration that datatypes get unregistered from |
| // DataTypeRegistry before scheduled configure sync cycle gets executed. |
| // This happens either because DataTypeController::LoadModels fail and type |
| // need to be stopped or during shutdown when all datatypes are stopped. When |
| // it happens we should adjust set of types to download to only include |
| // registered types. |
| DataTypeSet still_enabled_types = |
| Intersection(request_types, cycle->context()->GetConnectedTypes()); |
| VLOG(1) << "Configuring types " |
| << DataTypeSetToDebugString(still_enabled_types); |
| HandleCycleBegin(cycle); |
| DownloadAndApplyUpdates(&still_enabled_types, cycle, |
| ConfigureGetUpdatesDelegate(origin)); |
| return HandleCycleEnd(cycle, origin); |
| } |
| |
| bool Syncer::PollSyncShare(DataTypeSet request_types, SyncCycle* cycle) { |
| base::AutoReset<bool> is_syncing(&is_syncing_, true); |
| VLOG(1) << "Polling types " << DataTypeSetToDebugString(request_types); |
| HandleCycleBegin(cycle); |
| DownloadAndApplyUpdates(&request_types, cycle, PollGetUpdatesDelegate()); |
| return HandleCycleEnd(cycle, sync_pb::SyncEnums::PERIODIC); |
| } |
| |
| bool Syncer::DownloadAndApplyUpdates(DataTypeSet* request_types, |
| SyncCycle* cycle, |
| const GetUpdatesDelegate& delegate) { |
| // CommitOnlyTypes() should not be included in the GetUpdates, but should be |
| // included in the Commit. We are given a set of types for our SyncShare, |
| // and we must do this filtering. Note that `request_types` is also an out |
| // param, see below where we update it. |
| DataTypeSet requested_commit_only_types = |
| Intersection(*request_types, CommitOnlyTypes()); |
| DataTypeSet download_types = |
| Difference(*request_types, requested_commit_only_types); |
| GetUpdatesProcessor get_updates_processor( |
| cycle->context()->data_type_registry()->update_handler_map(), delegate); |
| SyncerError download_result = SyncerError::Success(); |
| do { |
| download_result = |
| get_updates_processor.DownloadUpdates(&download_types, cycle); |
| |
| // Exit without applying if we're shutting down. |
| if (ExitRequested()) { |
| return false; |
| } |
| } while (get_updates_processor.HasMoreUpdatesToDownload()); |
| |
| DataTypeSet data_types_with_failure = Difference( |
| Difference(*request_types, download_types), requested_commit_only_types); |
| |
| // It is our responsibility to propagate the removal of types that occurred in |
| // GetUpdatesProcessor::DownloadUpdates(). |
| *request_types = Union(download_types, requested_commit_only_types); |
| |
| base::UmaHistogramEnumeration("Sync.DownloadUpdatesResult", |
| GetSyncerErrorValueForUma(download_result)); |
| |
| // Exit without applying if an error was detected. |
| if (download_result.type() != SyncerError::Type::kSuccess) { |
| get_updates_processor.RecordDownloadFailure( |
| Union(download_types, data_types_with_failure), |
| SyncerErrorToNudgedUpdateResult(download_result)); |
| return false; |
| } |
| |
| { |
| TRACE_EVENT0("sync", "ApplyUpdates"); |
| |
| // Apply updates to the other types. May or may not involve cross-thread |
| // traffic, depending on the underlying update handlers and the GU type's |
| // delegate. |
| get_updates_processor.ApplyUpdates(download_types, data_types_with_failure, |
| cycle->mutable_status_controller()); |
| |
| cycle->SendEventNotification(SyncCycleEvent::STATUS_CHANGED); |
| } |
| |
| return !ExitRequested(); |
| } |
| |
| SyncerError Syncer::BuildAndPostCommits(const DataTypeSet& request_types, |
| NudgeTracker* nudge_tracker, |
| SyncCycle* cycle) { |
| VLOG(1) << "Committing from types " |
| << DataTypeSetToDebugString(request_types); |
| |
| CommitProcessor commit_processor( |
| request_types, |
| cycle->context()->data_type_registry()->commit_contributor_map()); |
| |
| // The ExitRequested() check is unnecessary, since we should start getting |
| // errors from the ServerConnectionManager if an exist has been requested. |
| // However, it doesn't hurt to check it anyway. |
| while (!ExitRequested()) { |
| std::unique_ptr<Commit> commit = Commit::Init( |
| cycle->context()->GetConnectedTypes(), |
| cycle->context()->max_commit_batch_size(), |
| cycle->context()->account_name(), cycle->context()->cache_guid(), |
| cycle->context()->cookie_jar_mismatch(), GetInvalidationInfo(cycle), |
| &commit_processor, cycle->context()->extensions_activity()); |
| if (!commit) { |
| break; |
| } |
| |
| base::TimeTicks commit_start_time = base::TimeTicks::Now(); |
| SyncerError error = commit->PostAndProcessResponse( |
| nudge_tracker, cycle, cycle->mutable_status_controller(), |
| cycle->context()->extensions_activity()); |
| LogCommitResult(error, commit->GetContributingDataTypes(), |
| commit_start_time); |
| if (error.type() != SyncerError::Type::kSuccess) { |
| return error; |
| } |
| nudge_tracker->RecordSuccessfulCommitMessage( |
| commit->GetContributingDataTypes()); |
| } |
| |
| return SyncerError::Success(); |
| } |
| |
| bool Syncer::ExitRequested() { |
| return cancelation_signal_->IsSignalled(); |
| } |
| |
| bool Syncer::HandleCycleEnd(SyncCycle* cycle, |
| sync_pb::SyncEnums::GetUpdatesOrigin origin) { |
| if (ExitRequested()) { |
| return false; |
| } |
| |
| bool success = |
| !HasSyncerError(cycle->status_controller().model_neutral_state()); |
| if (success && origin == sync_pb::SyncEnums::PERIODIC) { |
| cycle->mutable_status_controller()->UpdatePollTime(); |
| } |
| cycle->SendSyncCycleEndEventNotification(origin); |
| |
| return success; |
| } |
| |
| } // namespace syncer |