| Index: sync/engine/sync_scheduler_impl.cc
|
| diff --git a/sync/engine/sync_scheduler_impl.cc b/sync/engine/sync_scheduler_impl.cc
|
| index 6f76be0918cf930c80ff57493fb3070e352c7321..a1394fbe4d87a62af2c1f4895fd34e6d5a070f10 100644
|
| --- a/sync/engine/sync_scheduler_impl.cc
|
| +++ b/sync/engine/sync_scheduler_impl.cc
|
| @@ -7,7 +7,6 @@
|
| #include <algorithm>
|
| #include <cstring>
|
|
|
| -#include "base/auto_reset.h"
|
| #include "base/bind.h"
|
| #include "base/bind_helpers.h"
|
| #include "base/compiler_specific.h"
|
| @@ -156,12 +155,10 @@ SyncSchedulerImpl::SyncSchedulerImpl(const std::string& name,
|
| TimeDelta::FromSeconds(kDefaultShortPollIntervalSeconds)),
|
| syncer_long_poll_interval_seconds_(
|
| TimeDelta::FromSeconds(kDefaultLongPollIntervalSeconds)),
|
| - mode_(NORMAL_MODE),
|
| + mode_(CONFIGURATION_MODE),
|
| delay_provider_(delay_provider),
|
| syncer_(syncer),
|
| session_context_(context),
|
| - no_scheduling_allowed_(false),
|
| - do_poll_after_credentials_updated_(false),
|
| next_sync_session_job_priority_(NORMAL_PRIORITY),
|
| weak_ptr_factory_(this),
|
| weak_ptr_factory_for_weak_handle_(this) {
|
| @@ -207,7 +204,7 @@ void SyncSchedulerImpl::OnServerConnectionErrorFixed() {
|
| TryCanaryJob();
|
| }
|
|
|
| -void SyncSchedulerImpl::Start(Mode mode) {
|
| +void SyncSchedulerImpl::Start(Mode mode, base::Time last_poll_time) {
|
| DCHECK(CalledOnValidThread());
|
| std::string thread_name = base::MessageLoop::current()->thread_name();
|
| if (thread_name.empty())
|
| @@ -223,12 +220,24 @@ void SyncSchedulerImpl::Start(Mode mode) {
|
| DCHECK(syncer_.get());
|
| Mode old_mode = mode_;
|
| mode_ = mode;
|
| - AdjustPolling(UPDATE_INTERVAL); // Will kick start poll timer if needed.
|
| + // Only adjust the poll reset time if it was valid and in the past.
|
| + if (!last_poll_time.is_null() && last_poll_time < base::Time::Now()) {
|
| + // Convert from base::Time to base::TimeTicks. The reason we use Time
|
| + // for persisting is that TimeTicks can stop making forward progress when
|
| + // the machine is suspended. This implies that on resume the client might
|
| + // actually have miss the real poll, unless the client is restarted. Fixing
|
| + // that would require using an AlarmTimer though, which is only supported
|
| + // on certain platforms.
|
| + last_poll_reset_ =
|
| + base::TimeTicks::Now() - (base::Time::Now() - last_poll_time);
|
| + }
|
|
|
| if (old_mode != mode_ && mode_ == NORMAL_MODE) {
|
| // We just got back to normal mode. Let's try to run the work that was
|
| // queued up while we were configuring.
|
|
|
| + AdjustPolling(UPDATE_INTERVAL); // Will kick start poll timer if needed.
|
| +
|
| // Update our current time before checking IsRetryRequired().
|
| nudge_tracker_.SetSyncCycleStartTime(base::TimeTicks::Now());
|
| if (nudge_tracker_.IsSyncRequired() && CanRunNudgeJobNow(NORMAL_PRIORITY)) {
|
| @@ -306,14 +315,12 @@ void SyncSchedulerImpl::ScheduleConfiguration(
|
|
|
| bool SyncSchedulerImpl::CanRunJobNow(JobPriority priority) {
|
| DCHECK(CalledOnValidThread());
|
| - if (wait_interval_ && wait_interval_->mode == WaitInterval::THROTTLED) {
|
| + if (IsCurrentlyThrottled()) {
|
| SDVLOG(1) << "Unable to run a job because we're throttled.";
|
| return false;
|
| }
|
|
|
| - if (wait_interval_
|
| - && wait_interval_->mode == WaitInterval::EXPONENTIAL_BACKOFF
|
| - && priority != CANARY_PRIORITY) {
|
| + if (IsBackingOff() && priority != CANARY_PRIORITY) {
|
| SDVLOG(1) << "Unable to run a job because we're backing off.";
|
| return false;
|
| }
|
| @@ -404,11 +411,7 @@ void SyncSchedulerImpl::ScheduleNudgeImpl(
|
| const TimeDelta& delay,
|
| const tracked_objects::Location& nudge_location) {
|
| DCHECK(CalledOnValidThread());
|
| -
|
| - if (no_scheduling_allowed_) {
|
| - NOTREACHED() << "Illegal to schedule job while session in progress.";
|
| - return;
|
| - }
|
| + CHECK(!syncer_->IsSyncing());
|
|
|
| if (!started_) {
|
| SDVLOG_LOC(nudge_location, 2)
|
| @@ -464,25 +467,23 @@ void SyncSchedulerImpl::DoNudgeSyncSessionJob(JobPriority priority) {
|
| DVLOG(2) << "Will run normal mode sync cycle with types "
|
| << ModelTypeSetToString(session_context_->GetEnabledTypes());
|
| scoped_ptr<SyncSession> session(SyncSession::Build(session_context_, this));
|
| - bool premature_exit = !syncer_->NormalSyncShare(
|
| + bool success = syncer_->NormalSyncShare(
|
| GetEnabledAndUnthrottledTypes(), &nudge_tracker_, session.get());
|
| - AdjustPolling(FORCE_RESET);
|
| - // Don't run poll job till the next time poll timer fires.
|
| - do_poll_after_credentials_updated_ = false;
|
| -
|
| - bool success = !premature_exit
|
| - && !sessions::HasSyncerError(
|
| - session->status_controller().model_neutral_state());
|
|
|
| if (success) {
|
| // That cycle took care of any outstanding work we had.
|
| SDVLOG(2) << "Nudge succeeded.";
|
| nudge_tracker_.RecordSuccessfulSyncCycle();
|
| scheduled_nudge_time_ = base::TimeTicks();
|
| -
|
| - // If we're here, then we successfully reached the server. End all backoff.
|
| - wait_interval_.reset();
|
| - NotifyRetryTime(base::Time());
|
| + HandleSuccess();
|
| +
|
| + // If this was a canary, we may need to restart the poll timer (the poll
|
| + // timer may have fired while the scheduler was in an error state, ignoring
|
| + // the poll).
|
| + if (!poll_timer_.IsRunning()) {
|
| + SDVLOG(1) << "Canary succeeded, restarting polling.";
|
| + AdjustPolling(UPDATE_INTERVAL);
|
| + }
|
| } else {
|
| HandleFailure(session->status_controller().model_neutral_state());
|
| }
|
| @@ -505,26 +506,16 @@ void SyncSchedulerImpl::DoConfigurationSyncSessionJob(JobPriority priority) {
|
| SDVLOG(2) << "Will run configure SyncShare with types "
|
| << ModelTypeSetToString(session_context_->GetEnabledTypes());
|
| scoped_ptr<SyncSession> session(SyncSession::Build(session_context_, this));
|
| - bool premature_exit = !syncer_->ConfigureSyncShare(
|
| + bool success = syncer_->ConfigureSyncShare(
|
| pending_configure_params_->types_to_download,
|
| pending_configure_params_->source,
|
| session.get());
|
| - AdjustPolling(FORCE_RESET);
|
| - // Don't run poll job till the next time poll timer fires.
|
| - do_poll_after_credentials_updated_ = false;
|
| -
|
| - bool success = !premature_exit
|
| - && !sessions::HasSyncerError(
|
| - session->status_controller().model_neutral_state());
|
|
|
| if (success) {
|
| SDVLOG(2) << "Configure succeeded.";
|
| pending_configure_params_->ready_task.Run();
|
| pending_configure_params_.reset();
|
| -
|
| - // If we're here, then we successfully reached the server. End all backoff.
|
| - wait_interval_.reset();
|
| - NotifyRetryTime(base::Time());
|
| + HandleSuccess();
|
| } else {
|
| HandleFailure(session->status_controller().model_neutral_state());
|
| // Sync cycle might receive response from server that causes scheduler to
|
| @@ -536,11 +527,16 @@ void SyncSchedulerImpl::DoConfigurationSyncSessionJob(JobPriority priority) {
|
| }
|
| }
|
|
|
| +void SyncSchedulerImpl::HandleSuccess() {
|
| + // If we're here, then we successfully reached the server. End all backoff.
|
| + wait_interval_.reset();
|
| + NotifyRetryTime(base::Time());
|
| +}
|
| +
|
| void SyncSchedulerImpl::HandleFailure(
|
| const sessions::ModelNeutralState& model_neutral_state) {
|
| if (IsCurrentlyThrottled()) {
|
| SDVLOG(2) << "Was throttled during previous sync cycle.";
|
| - RestartWaiting();
|
| } else if (!IsBackingOff()) {
|
| // Setup our backoff if this is our first such failure.
|
| TimeDelta length = delay_provider_->GetDelay(
|
| @@ -549,27 +545,32 @@ void SyncSchedulerImpl::HandleFailure(
|
| new WaitInterval(WaitInterval::EXPONENTIAL_BACKOFF, length));
|
| SDVLOG(2) << "Sync cycle failed. Will back off for "
|
| << wait_interval_->length.InMilliseconds() << "ms.";
|
| - RestartWaiting();
|
| + } else {
|
| + // Increase our backoff interval and schedule another retry.
|
| + TimeDelta length = delay_provider_->GetDelay(wait_interval_->length);
|
| + wait_interval_.reset(
|
| + new WaitInterval(WaitInterval::EXPONENTIAL_BACKOFF, length));
|
| + SDVLOG(2) << "Sync cycle failed. Will back off for "
|
| + << wait_interval_->length.InMilliseconds() << "ms.";
|
| }
|
| + RestartWaiting();
|
| }
|
|
|
| void SyncSchedulerImpl::DoPollSyncSessionJob() {
|
| - base::AutoReset<bool> protector(&no_scheduling_allowed_, true);
|
| -
|
| SDVLOG(2) << "Polling with types "
|
| << ModelTypeSetToString(GetEnabledAndUnthrottledTypes());
|
| scoped_ptr<SyncSession> session(SyncSession::Build(session_context_, this));
|
| - syncer_->PollSyncShare(
|
| + bool success = syncer_->PollSyncShare(
|
| GetEnabledAndUnthrottledTypes(),
|
| session.get());
|
|
|
| - AdjustPolling(FORCE_RESET);
|
| -
|
| - if (IsCurrentlyThrottled()) {
|
| - SDVLOG(2) << "Poll request got us throttled.";
|
| - // The OnSilencedUntil() call set up the WaitInterval for us. All we need
|
| - // to do is start the timer.
|
| - RestartWaiting();
|
| + // Only restart the timer if the poll succeeded. Otherwise rely on normal
|
| + // failure handling to retry with backoff.
|
| + if (success) {
|
| + AdjustPolling(FORCE_RESET);
|
| + HandleSuccess();
|
| + } else {
|
| + HandleFailure(session->status_controller().model_neutral_state());
|
| }
|
| }
|
|
|
| @@ -599,23 +600,44 @@ TimeDelta SyncSchedulerImpl::GetPollInterval() {
|
|
|
| void SyncSchedulerImpl::AdjustPolling(PollAdjustType type) {
|
| DCHECK(CalledOnValidThread());
|
| + if (!started_)
|
| + return;
|
|
|
| - TimeDelta poll = GetPollInterval();
|
| - bool rate_changed = !poll_timer_.IsRunning() ||
|
| - poll != poll_timer_.GetCurrentDelay();
|
| -
|
| - if (type == FORCE_RESET) {
|
| - last_poll_reset_ = base::TimeTicks::Now();
|
| - if (!rate_changed)
|
| - poll_timer_.Reset();
|
| + TimeDelta poll_interval = GetPollInterval();
|
| + TimeDelta poll_delay = poll_interval;
|
| + const TimeTicks now = TimeTicks::Now();
|
| +
|
| + if (type == UPDATE_INTERVAL) {
|
| + if (!last_poll_reset_.is_null()) {
|
| + // Override the delay based on the last successful poll time (if it was
|
| + // set).
|
| + TimeTicks new_poll_time = poll_interval + last_poll_reset_;
|
| + poll_delay = new_poll_time - TimeTicks::Now();
|
| +
|
| + if (poll_delay < TimeDelta()) {
|
| + // The desired poll time was in the past, so trigger a poll now (the
|
| + // timer will post the task asynchronously, so re-entrancy isn't an
|
| + // issue).
|
| + poll_delay = TimeDelta();
|
| + }
|
| + } else {
|
| + // There was no previous poll. Keep the delay set to the normal interval,
|
| + // as if we had just completed a poll.
|
| + DCHECK_EQ(GetPollInterval(), poll_delay);
|
| + last_poll_reset_ = now;
|
| + }
|
| + } else {
|
| + // Otherwise just restart the timer.
|
| + DCHECK_EQ(FORCE_RESET, type);
|
| + DCHECK_EQ(GetPollInterval(), poll_delay);
|
| + last_poll_reset_ = now;
|
| }
|
|
|
| - if (!rate_changed)
|
| - return;
|
| + SDVLOG(1) << "Updating polling delay to " << poll_delay.InMinutes()
|
| + << " minutes.";
|
|
|
| - // Adjust poll rate.
|
| - poll_timer_.Stop();
|
| - poll_timer_.Start(FROM_HERE, poll, this,
|
| + // Adjust poll rate. Start will reset the timer if it was already running.
|
| + poll_timer_.Start(FROM_HERE, poll_delay, this,
|
| &SyncSchedulerImpl::PollTimerCallback);
|
| }
|
|
|
| @@ -659,6 +681,7 @@ void SyncSchedulerImpl::Stop() {
|
| // privileges. Everyone else should use NORMAL_PRIORITY.
|
| void SyncSchedulerImpl::TryCanaryJob() {
|
| next_sync_session_job_priority_ = CANARY_PRIORITY;
|
| + SDVLOG(2) << "Attempting canary job";
|
| TrySyncSessionJob();
|
| }
|
|
|
| @@ -686,24 +709,16 @@ void SyncSchedulerImpl::TrySyncSessionJobImpl() {
|
| if (nudge_tracker_.IsSyncRequired()) {
|
| SDVLOG(2) << "Found pending nudge job";
|
| DoNudgeSyncSessionJob(priority);
|
| - } else if (do_poll_after_credentials_updated_ ||
|
| - ((base::TimeTicks::Now() - last_poll_reset_) >= GetPollInterval())) {
|
| + } else if (((base::TimeTicks::Now() - last_poll_reset_) >=
|
| + GetPollInterval())) {
|
| + SDVLOG(2) << "Found pending poll";
|
| DoPollSyncSessionJob();
|
| - // Poll timer fires infrequently. Usually by this time access token is
|
| - // already expired and poll job will fail with auth error. Set flag to
|
| - // retry poll once ProfileSyncService gets new access token, TryCanaryJob
|
| - // will be called after access token is retrieved.
|
| - if (HttpResponse::SYNC_AUTH_ERROR ==
|
| - session_context_->connection_manager()->server_status()) {
|
| - do_poll_after_credentials_updated_ = true;
|
| - }
|
| }
|
| - }
|
| -
|
| - if (priority == CANARY_PRIORITY) {
|
| - // If this is canary job then whatever result was don't run poll job till
|
| - // the next time poll timer fires.
|
| - do_poll_after_credentials_updated_ = false;
|
| + } else {
|
| + // We must be in an error state. Transitioning out of each of these
|
| + // error states should trigger a canary job.
|
| + DCHECK(IsCurrentlyThrottled() || IsBackingOff() ||
|
| + session_context_->connection_manager()->HasInvalidAuthToken());
|
| }
|
|
|
| if (IsBackingOff() && !pending_wakeup_timer_.IsRunning()) {
|
| @@ -712,7 +727,7 @@ void SyncSchedulerImpl::TrySyncSessionJobImpl() {
|
| // another retry.
|
| TimeDelta length = delay_provider_->GetDelay(wait_interval_->length);
|
| wait_interval_.reset(
|
| - new WaitInterval(WaitInterval::EXPONENTIAL_BACKOFF, length));
|
| + new WaitInterval(WaitInterval::EXPONENTIAL_BACKOFF, length));
|
| SDVLOG(2) << "Sync cycle failed. Will back off for "
|
| << wait_interval_->length.InMilliseconds() << "ms.";
|
| RestartWaiting();
|
| @@ -721,16 +736,7 @@ void SyncSchedulerImpl::TrySyncSessionJobImpl() {
|
|
|
| void SyncSchedulerImpl::PollTimerCallback() {
|
| DCHECK(CalledOnValidThread());
|
| - if (no_scheduling_allowed_) {
|
| - // The no_scheduling_allowed_ flag is set by a function-scoped AutoReset in
|
| - // functions that are called only on the sync thread. This function is also
|
| - // called only on the sync thread, and only when it is posted by an expiring
|
| - // timer. If we find that no_scheduling_allowed_ is set here, then
|
| - // something is very wrong. Maybe someone mistakenly called us directly, or
|
| - // mishandled the book-keeping for no_scheduling_allowed_.
|
| - NOTREACHED() << "Illegal to schedule job while session in progress.";
|
| - return;
|
| - }
|
| + CHECK(!syncer_->IsSyncing());
|
|
|
| TrySyncSessionJob();
|
| }
|
| @@ -826,6 +832,9 @@ void SyncSchedulerImpl::OnTypesThrottled(
|
| const base::TimeDelta& throttle_duration) {
|
| base::TimeTicks now = base::TimeTicks::Now();
|
|
|
| + SDVLOG(1) << "Throttling " << ModelTypeSetToString(types) << " for "
|
| + << throttle_duration.InMinutes() << " minutes.";
|
| +
|
| nudge_tracker_.SetTypesThrottledUntil(types, throttle_duration, now);
|
| base::TimeDelta time_until_next_unthrottle =
|
| nudge_tracker_.GetTimeUntilNextUnthrottle(now);
|
| @@ -847,13 +856,23 @@ bool SyncSchedulerImpl::IsCurrentlyThrottled() {
|
| void SyncSchedulerImpl::OnReceivedShortPollIntervalUpdate(
|
| const base::TimeDelta& new_interval) {
|
| DCHECK(CalledOnValidThread());
|
| + if (new_interval == syncer_short_poll_interval_seconds_)
|
| + return;
|
| + SDVLOG(1) << "Updating short poll interval to " << new_interval.InMinutes()
|
| + << " minutes.";
|
| syncer_short_poll_interval_seconds_ = new_interval;
|
| + AdjustPolling(UPDATE_INTERVAL);
|
| }
|
|
|
| void SyncSchedulerImpl::OnReceivedLongPollIntervalUpdate(
|
| const base::TimeDelta& new_interval) {
|
| DCHECK(CalledOnValidThread());
|
| + if (new_interval == syncer_long_poll_interval_seconds_)
|
| + return;
|
| + SDVLOG(1) << "Updating long poll interval to " << new_interval.InMinutes()
|
| + << " minutes.";
|
| syncer_long_poll_interval_seconds_ = new_interval;
|
| + AdjustPolling(UPDATE_INTERVAL);
|
| }
|
|
|
| void SyncSchedulerImpl::OnReceivedCustomNudgeDelays(
|
|
|