// Copyright 2012 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. #ifndef SYNC_ENGINE_SYNC_SCHEDULER_IMPL_H_ #define SYNC_ENGINE_SYNC_SCHEDULER_IMPL_H_ #include #include #include "base/callback.h" #include "base/cancelable_callback.h" #include "base/compiler_specific.h" #include "base/gtest_prod_util.h" #include "base/memory/linked_ptr.h" #include "base/memory/scoped_ptr.h" #include "base/memory/weak_ptr.h" #include "base/threading/non_thread_safe.h" #include "base/time/time.h" #include "base/timer/timer.h" #include "sync/base/sync_export.h" #include "sync/engine/net/server_connection_manager.h" #include "sync/engine/nudge_source.h" #include "sync/engine/sync_scheduler.h" #include "sync/engine/syncer.h" #include "sync/internal_api/public/engine/polling_constants.h" #include "sync/internal_api/public/util/weak_handle.h" #include "sync/sessions/nudge_tracker.h" #include "sync/sessions/sync_session.h" #include "sync/sessions/sync_session_context.h" namespace syncer { class BackoffDelayProvider; namespace sessions { struct ModelNeutralState; } class SYNC_EXPORT_PRIVATE SyncSchedulerImpl : public SyncScheduler, public base::NonThreadSafe { public: // |name| is a display string to identify the syncer thread. Takes // |ownership of |syncer| and |delay_provider|. SyncSchedulerImpl(const std::string& name, BackoffDelayProvider* delay_provider, sessions::SyncSessionContext* context, Syncer* syncer); // Calls Stop(). virtual ~SyncSchedulerImpl(); virtual void Start(Mode mode) OVERRIDE; virtual void ScheduleConfiguration( const ConfigurationParams& params) OVERRIDE; virtual void Stop() OVERRIDE; virtual void ScheduleLocalNudge( const base::TimeDelta& desired_delay, ModelTypeSet types, const tracked_objects::Location& nudge_location) OVERRIDE; virtual void ScheduleLocalRefreshRequest( const base::TimeDelta& desired_delay, ModelTypeSet types, const tracked_objects::Location& nudge_location) OVERRIDE; virtual void ScheduleInvalidationNudge( const base::TimeDelta& desired_delay, const ObjectIdInvalidationMap& invalidation_map, const tracked_objects::Location& nudge_location) OVERRIDE; virtual void SetNotificationsEnabled(bool notifications_enabled) OVERRIDE; virtual base::TimeDelta GetSessionsCommitDelay() const OVERRIDE; virtual void OnCredentialsUpdated() OVERRIDE; virtual void OnConnectionStatusChange() OVERRIDE; // SyncSession::Delegate implementation. virtual void OnThrottled(const base::TimeDelta& throttle_duration) OVERRIDE; virtual void OnTypesThrottled( ModelTypeSet types, const base::TimeDelta& throttle_duration) OVERRIDE; virtual bool IsCurrentlyThrottled() OVERRIDE; virtual void OnReceivedShortPollIntervalUpdate( const base::TimeDelta& new_interval) OVERRIDE; virtual void OnReceivedLongPollIntervalUpdate( const base::TimeDelta& new_interval) OVERRIDE; virtual void OnReceivedSessionsCommitDelay( const base::TimeDelta& new_delay) OVERRIDE; virtual void OnReceivedClientInvalidationHintBufferSize(int size) OVERRIDE; virtual void OnSyncProtocolError( const SyncProtocolError& sync_protocol_error) OVERRIDE; virtual void OnReceivedGuRetryDelay(const base::TimeDelta& delay) OVERRIDE; virtual void OnReceivedMigrationRequest(syncer::ModelTypeSet types) OVERRIDE; // Returns true if the client is currently in exponential backoff. bool IsBackingOff() const; private: enum JobPriority { // Non-canary jobs respect exponential backoff. NORMAL_PRIORITY, // Canary jobs bypass exponential backoff, so use with extreme caution. CANARY_PRIORITY }; enum PollAdjustType { // Restart the poll interval. FORCE_RESET, // Restart the poll interval only if its length has changed. UPDATE_INTERVAL, }; friend class SyncSchedulerTest; friend class SyncSchedulerWhiteboxTest; friend class SyncerTest; FRIEND_TEST_ALL_PREFIXES(SyncSchedulerTest, TransientPollFailure); FRIEND_TEST_ALL_PREFIXES(SyncSchedulerTest, ServerConnectionChangeDuringBackoff); FRIEND_TEST_ALL_PREFIXES(SyncSchedulerTest, ConnectionChangeCanaryPreemptedByNudge); FRIEND_TEST_ALL_PREFIXES(BackoffTriggersSyncSchedulerTest, FailGetEncryptionKey); FRIEND_TEST_ALL_PREFIXES(SyncSchedulerTest, SuccessfulRetry); FRIEND_TEST_ALL_PREFIXES(SyncSchedulerTest, FailedRetry); FRIEND_TEST_ALL_PREFIXES(SyncSchedulerTest, ReceiveNewRetryDelay); struct SYNC_EXPORT_PRIVATE WaitInterval { enum Mode { // Uninitialized state, should not be set in practice. UNKNOWN = -1, // We enter a series of increasingly longer WaitIntervals if we experience // repeated transient failures. We retry at the end of each interval. EXPONENTIAL_BACKOFF, // A server-initiated throttled interval. We do not allow any syncing // during such an interval. THROTTLED, }; WaitInterval(); ~WaitInterval(); WaitInterval(Mode mode, base::TimeDelta length); static const char* GetModeString(Mode mode); Mode mode; base::TimeDelta length; }; static const char* GetModeString(Mode mode); // Invoke the syncer to perform a nudge job. void DoNudgeSyncSessionJob(JobPriority priority); // Invoke the syncer to perform a configuration job. void DoConfigurationSyncSessionJob(JobPriority priority); // Helper function for Do{Nudge,Configuration}SyncSessionJob. void HandleFailure( const sessions::ModelNeutralState& model_neutral_state); // Invoke the Syncer to perform a poll job. void DoPollSyncSessionJob(); // Invoke the Syncer to perform a retry job. void DoRetrySyncSessionJob(); // Helper function to calculate poll interval. base::TimeDelta GetPollInterval(); // Adjusts the poll timer to account for new poll interval, and possibly // resets the poll interval, depedning on the flag's value. void AdjustPolling(PollAdjustType type); // Helper to restart waiting with |wait_interval_|'s timer. void RestartWaiting(); // Determines if we're allowed to contact the server right now. bool CanRunJobNow(JobPriority priority); // Determines if we're allowed to contact the server right now. bool CanRunNudgeJobNow(JobPriority priority); // If the scheduler's current state supports it, this will create a job based // on the passed in parameters and coalesce it with any other pending jobs, // then post a delayed task to run it. It may also choose to drop the job or // save it for later, depending on the scheduler's current state. void ScheduleNudgeImpl( const base::TimeDelta& delay, const tracked_objects::Location& nudge_location); // Helper to signal listeners about changed retry time. void NotifyRetryTime(base::Time retry_time); // Helper to signal listeners about changed throttled types. void NotifyThrottledTypesChanged(ModelTypeSet types); // Looks for pending work and, if it finds any, run this work at "canary" // priority. void TryCanaryJob(); // At the moment TrySyncSessionJob just posts call to TrySyncSessionJobImpl on // current thread. In the future it will request access token here. void TrySyncSessionJob(); void TrySyncSessionJobImpl(); // Transitions out of the THROTTLED WaitInterval then calls TryCanaryJob(). void Unthrottle(); // Called when a per-type throttling interval expires. void TypeUnthrottle(base::TimeTicks unthrottle_time); // Runs a normal nudge job when the scheduled timer expires. void PerformDelayedNudge(); // Attempts to exit EXPONENTIAL_BACKOFF by calling TryCanaryJob(). void ExponentialBackoffRetry(); // Called when the root cause of the current connection error is fixed. void OnServerConnectionErrorFixed(); // Creates a session for a poll and performs the sync. void PollTimerCallback(); // Creates a session for a retry and performs the sync. void RetryTimerCallback(); // Returns the set of types that are enabled and not currently throttled. ModelTypeSet GetEnabledAndUnthrottledTypes(); // Called as we are started to broadcast an initial session snapshot // containing data like initial_sync_ended. Important when the client starts // up and does not need to perform an initial sync. void SendInitialSnapshot(); // This is used for histogramming and analysis of ScheduleNudge* APIs. // SyncScheduler is the ultimate choke-point for all such invocations (with // and without InvalidationState variants, all NudgeSources, etc) and as such // is the most flexible place to do this bookkeeping. void UpdateNudgeTimeRecords(ModelTypeSet types); // For certain methods that need to worry about X-thread posting. WeakHandle weak_handle_this_; // Used for logging. const std::string name_; // Set in Start(), unset in Stop(). bool started_; // Modifiable versions of kDefaultLongPollIntervalSeconds which can be // updated by the server. base::TimeDelta syncer_short_poll_interval_seconds_; base::TimeDelta syncer_long_poll_interval_seconds_; // Server-tweakable sessions commit delay. base::TimeDelta sessions_commit_delay_; // Periodic timer for polling. See AdjustPolling. base::RepeatingTimer poll_timer_; // The mode of operation. Mode mode_; // Current wait state. Null if we're not in backoff and not throttled. scoped_ptr wait_interval_; scoped_ptr delay_provider_; // The event that will wake us up. base::OneShotTimer pending_wakeup_timer_; // An event that fires when data type throttling expires. base::OneShotTimer type_unthrottle_timer_; // Storage for variables related to an in-progress configure request. Note // that (mode_ != CONFIGURATION_MODE) \implies !pending_configure_params_. scoped_ptr pending_configure_params_; // If we have a nudge pending to run soon, it will be listed here. base::TimeTicks scheduled_nudge_time_; // Keeps track of work that the syncer needs to handle. sessions::NudgeTracker nudge_tracker_; // Invoked to run through the sync cycle. scoped_ptr syncer_; sessions::SyncSessionContext* session_context_; // A map tracking LOCAL NudgeSource invocations of ScheduleNudge* APIs, // organized by datatype. Each datatype that was part of the types requested // in the call will have its TimeTicks value updated. typedef std::map ModelTypeTimeMap; ModelTypeTimeMap last_local_nudges_by_model_type_; // Used as an "anti-reentrancy defensive assertion". // While true, it is illegal for any new scheduling activity to take place. // Ensures that higher layers don't break this law in response to events that // take place during a sync cycle. We call this out because such violations // could result in tight sync loops hitting sync servers. bool no_scheduling_allowed_; // crbug/251307. This is a workaround for M29. crbug/259913 tracks proper fix // for M30. // The issue is that poll job runs after few hours of inactivity and therefore // will always fail with auth error because of expired access token. Once // fresh access token is requested poll job is not retried. // The change is to remember that poll timer just fired and retry poll job // after credentials are updated. bool do_poll_after_credentials_updated_; // TryJob might get called for multiple reasons. It should only call // DoPollSyncSessionJob after some time since the last attempt. // last_poll_reset_ keeps track of when was last attempt. base::TimeTicks last_poll_reset_; // next_sync_session_job_priority_ defines which priority will be used next // time TrySyncSessionJobImpl is called. CANARY_PRIORITY allows syncer to run // even if scheduler is in exponential backoff. This is needed for events that // have chance of resolving previous error (e.g. network connection change // after NETWORK_UNAVAILABLE error). // It is reset back to NORMAL_PRIORITY on every call to TrySyncSessionJobImpl. JobPriority next_sync_session_job_priority_; // One-shot timer for scheduling GU retry according to delay set by server. base::OneShotTimer retry_timer_; base::WeakPtrFactory weak_ptr_factory_; // A second factory specially for weak_handle_this_, to allow the handle // to be const and alleviate threading concerns. base::WeakPtrFactory weak_ptr_factory_for_weak_handle_; DISALLOW_COPY_AND_ASSIGN(SyncSchedulerImpl); }; } // namespace syncer #endif // SYNC_ENGINE_SYNC_SCHEDULER_IMPL_H_