| // 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: | |
| if (error.GetHttpErrorOrDie() >= 400 && error.GetHttpErrorOrDie() < 500) { | |
| return UpdateHandler::NudgedUpdateResult:: | |
| kDownloadRequestClientHttpError; | |
| } | |
| // In practice, this error should be for the 5xx HTTP errors but record it | |
| // for any non-4xx HTTP error. | |
| return UpdateHandler::NudgedUpdateResult::kDownloadRequestServerHttpError; | |
| case SyncerError::Type::kProtocolError: | |
| case SyncerError::Type::kProtocolViolationError: | |
| // Return server error for all non-network errors. | |
| return UpdateHandler::NudgedUpdateResult::kDownloadRequestProtocolError; | |
| 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_email(), 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 |