| // 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 "chrome/browser/sync/test/integration/quiesce_status_change_checker.h" |
| |
| #include "base/format_macros.h" |
| #include "base/scoped_observer.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringprintf.h" |
| #include "chrome/browser/sync/profile_sync_service.h" |
| #include "sync/internal_api/public/sessions/sync_session_snapshot.h" |
| |
| namespace { |
| |
| // Returns true if this service is disabled. |
| bool IsSyncDisabled(ProfileSyncService* service) { |
| return !service->setup_in_progress() && !service->HasSyncSetupCompleted(); |
| } |
| |
| // Returns true if these services have matching progress markers. |
| bool ProgressMarkersMatch(const ProfileSyncService* service1, |
| const ProfileSyncService* service2) { |
| const syncer::ModelTypeSet common_types = |
| Intersection(service1->GetActiveDataTypes(), |
| service2->GetActiveDataTypes()); |
| |
| const syncer::sessions::SyncSessionSnapshot& snap1 = |
| service1->GetLastSessionSnapshot(); |
| const syncer::sessions::SyncSessionSnapshot& snap2 = |
| service2->GetLastSessionSnapshot(); |
| |
| for (syncer::ModelTypeSet::Iterator type_it = common_types.First(); |
| type_it.Good(); type_it.Inc()) { |
| // Look up the progress markers. Fail if either one is missing. |
| syncer::ProgressMarkerMap::const_iterator pm_it1 = |
| snap1.download_progress_markers().find(type_it.Get()); |
| if (pm_it1 == snap1.download_progress_markers().end()) { |
| return false; |
| } |
| |
| syncer::ProgressMarkerMap::const_iterator pm_it2 = |
| snap2.download_progress_markers().find(type_it.Get()); |
| if (pm_it2 == snap2.download_progress_markers().end()) { |
| return false; |
| } |
| |
| // Fail if any of them don't match. |
| if (pm_it1->second != pm_it2->second) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| } // namespace |
| |
| // A helper class to keep an eye on a particular ProfileSyncService's |
| // "HasLatestProgressMarkers()" state. |
| // |
| // This is a work-around for the HasLatestProgressMarkers check's inherent |
| // flakiness. It's not safe to check that condition whenever we want. The |
| // safest time to check it is when the ProfileSyncService emits an |
| // OnStateChanged() event. This class waits for those events and updates its |
| // cached HasLatestProgressMarkers state every time that event occurs. |
| // |
| // See the comments in UpdatedProgressMarkerChecker for more details. |
| // |
| // The long-term plan is to deprecate this hack by replacing all its usees with |
| // more reliable status checkers. |
| class ProgressMarkerWatcher : public ProfileSyncServiceObserver { |
| public: |
| ProgressMarkerWatcher( |
| ProfileSyncService* service, |
| QuiesceStatusChangeChecker* quiesce_checker); |
| virtual ~ProgressMarkerWatcher(); |
| virtual void OnStateChanged() OVERRIDE; |
| |
| bool HasLatestProgressMarkers(); |
| bool IsSyncDisabled(); |
| |
| private: |
| void UpdateHasLatestProgressMarkers(); |
| |
| ProfileSyncService* service_; |
| QuiesceStatusChangeChecker* quiesce_checker_; |
| ScopedObserver<ProfileSyncService, ProgressMarkerWatcher> scoped_observer_; |
| bool probably_has_latest_progress_markers_; |
| }; |
| |
| ProgressMarkerWatcher::ProgressMarkerWatcher( |
| ProfileSyncService* service, |
| QuiesceStatusChangeChecker* quiesce_checker) |
| : service_(service), |
| quiesce_checker_(quiesce_checker), |
| scoped_observer_(this), |
| probably_has_latest_progress_markers_(false) { |
| scoped_observer_.Add(service); |
| UpdateHasLatestProgressMarkers(); |
| } |
| |
| ProgressMarkerWatcher::~ProgressMarkerWatcher() { } |
| |
| void ProgressMarkerWatcher::OnStateChanged() { |
| UpdateHasLatestProgressMarkers(); |
| quiesce_checker_->OnServiceStateChanged(service_); |
| } |
| |
| void ProgressMarkerWatcher::UpdateHasLatestProgressMarkers() { |
| if (IsSyncDisabled()) { |
| probably_has_latest_progress_markers_ = false; |
| return; |
| } |
| |
| // This is the same progress marker check as used by the |
| // UpdatedProgressMarkerChecker. It has the samed drawbacks and potential for |
| // flakiness. See the comment in |
| // UpdatedProgressMarkerChecker::IsExitConditionSatisfied() for more |
| // information. |
| // |
| // The QuiesceStatusChangeChecker attempts to work around the limitations of |
| // this progress marker checking method. It tries to update the progress |
| // marker status only in the OnStateChanged() callback, where the snapshot is |
| // freshest. |
| // |
| // It also checks the progress marker status when it is first initialized, and |
| // that's where it's most likely that we could return a false positive. We |
| // need to check these service at startup, since not every service is |
| // guaranteed to generate OnStateChanged() events while we're waiting for |
| // quiescence. |
| const syncer::sessions::SyncSessionSnapshot& snap = |
| service_->GetLastSessionSnapshot(); |
| probably_has_latest_progress_markers_ = |
| snap.model_neutral_state().num_successful_commits == 0 && |
| !service_->HasUnsyncedItems(); |
| } |
| |
| bool ProgressMarkerWatcher::HasLatestProgressMarkers() { |
| return probably_has_latest_progress_markers_; |
| } |
| |
| bool ProgressMarkerWatcher::IsSyncDisabled() { |
| return ::IsSyncDisabled(service_); |
| } |
| |
| QuiesceStatusChangeChecker::QuiesceStatusChangeChecker( |
| std::vector<ProfileSyncService*> services) |
| : services_(services) { |
| DCHECK_LE(1U, services_.size()); |
| for (size_t i = 0; i < services_.size(); ++i) { |
| observers_.push_back(new ProgressMarkerWatcher(services[i], this)); |
| } |
| } |
| |
| QuiesceStatusChangeChecker::~QuiesceStatusChangeChecker() {} |
| |
| void QuiesceStatusChangeChecker::Wait() { |
| DVLOG(1) << "Await: " << GetDebugMessage(); |
| |
| if (IsExitConditionSatisfied()) { |
| DVLOG(1) << "Await -> Exit before waiting: " << GetDebugMessage(); |
| return; |
| } |
| |
| StartBlockingWait(); |
| } |
| |
| bool QuiesceStatusChangeChecker::IsExitConditionSatisfied() { |
| // Check that all progress markers are up to date. |
| for (ScopedVector<ProgressMarkerWatcher>::const_iterator it = |
| observers_.begin(); it != observers_.end(); ++it) { |
| if ((*it)->IsSyncDisabled()) { |
| continue; // Skip disabled services. |
| } |
| |
| if (!(*it)->HasLatestProgressMarkers()) { |
| VLOG(1) << "Not quiesced: Progress markers are old."; |
| return false; |
| } |
| } |
| |
| std::vector<ProfileSyncService*> enabled_services; |
| for (std::vector<ProfileSyncService*>::const_iterator it = services_.begin(); |
| it != services_.end(); ++it) { |
| if (!IsSyncDisabled(*it)) { |
| enabled_services.push_back(*it); |
| } |
| } |
| |
| // Return true if we have nothing to compare against. |
| if (enabled_services.size() <= 1) { |
| return true; |
| } |
| |
| std::vector<ProfileSyncService*>::const_iterator it1 = |
| enabled_services.begin(); |
| std::vector<ProfileSyncService*>::const_iterator it2 = |
| enabled_services.begin(); |
| it2++; |
| |
| while (it2 != enabled_services.end()) { |
| // Return false if there is a progress marker mismatch. |
| if (!ProgressMarkersMatch(*it1, *it2)) { |
| VLOG(1) << "Not quiesced: Progress marker mismatch."; |
| return false; |
| } |
| it1++; |
| it2++; |
| } |
| |
| return true; |
| } |
| |
| std::string QuiesceStatusChangeChecker::GetDebugMessage() const { |
| return base::StringPrintf("Waiting for quiescence of %" PRIuS " clients", |
| services_.size()); |
| } |
| |
| void QuiesceStatusChangeChecker::OnServiceStateChanged( |
| ProfileSyncService* service) { |
| CheckExitCondition(); |
| } |