// Copyright (c) 2011 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.
//
// A class to run the syncer on a thread.
#ifndef CHROME_BROWSER_SYNC_ENGINE_SYNCER_THREAD_H_
#define CHROME_BROWSER_SYNC_ENGINE_SYNCER_THREAD_H_
#pragma once
#include "base/callback.h"
#include "base/memory/linked_ptr.h"
#include "base/memory/scoped_ptr.h"
#include "base/observer_list.h"
#include "base/task.h"
#include "base/threading/thread.h"
#include "base/time.h"
#include "base/timer.h"
#include "chrome/browser/sync/engine/nudge_source.h"
#include "chrome/browser/sync/engine/polling_constants.h"
#include "chrome/browser/sync/engine/syncer.h"
#include "chrome/browser/sync/syncable/model_type_payload_map.h"
#include "chrome/browser/sync/engine/net/server_connection_manager.h"
#include "chrome/browser/sync/sessions/sync_session.h"
#include "chrome/browser/sync/sessions/sync_session_context.h"
namespace browser_sync {
struct ServerConnectionEvent;
class SyncerThread : public sessions::SyncSession::Delegate,
public ServerConnectionEventListener {
public:
enum Mode {
// In this mode, the thread only performs configuration tasks. This is
// designed to make the case where we want to download updates for a
// specific type only, and not continue syncing until we are moved into
// normal mode.
CONFIGURATION_MODE,
// Resumes polling and allows nudges, drops configuration tasks. Runs
// through entire sync cycle.
NORMAL_MODE,
};
// Takes ownership of both |context| and |syncer|.
SyncerThread(sessions::SyncSessionContext* context, Syncer* syncer);
virtual ~SyncerThread();
typedef Callback0::Type ModeChangeCallback;
// Change the mode of operation.
// We don't use a lock when changing modes, so we won't cause currently
// scheduled jobs to adhere to the new mode. We could protect it, but it
// doesn't buy very much as a) a session could already be in progress and it
// will continue no matter what, b) the scheduled sessions already contain
// all their required state and won't be affected by potential change at
// higher levels (i.e. the registrar), and c) we service tasks FIFO, so once
// the mode changes all future jobs will be run against the updated mode.
// If supplied, |callback| will be invoked when the mode has been
// changed to |mode| *from the SyncerThread*, and not from the caller
// thread.
void Start(Mode mode, ModeChangeCallback* callback);
// Joins on the thread as soon as possible (currently running session
// completes).
void Stop();
// The meat and potatoes.
void ScheduleNudge(const base::TimeDelta& delay, NudgeSource source,
const syncable::ModelTypeBitSet& types,
const tracked_objects::Location& nudge_location);
void ScheduleNudgeWithPayloads(
const base::TimeDelta& delay, NudgeSource source,
const syncable::ModelTypePayloadMap& types_with_payloads,
const tracked_objects::Location& nudge_location);
void ScheduleConfig(const syncable::ModelTypeBitSet& types);
void ScheduleClearUserData();
// Change status of notifications in the SyncSessionContext.
void set_notifications_enabled(bool notifications_enabled);
// DDOS avoidance function. Calculates how long we should wait before trying
// again after a failed sync attempt, where the last delay was |base_delay|.
// TODO(tim): Look at URLRequestThrottlerEntryInterface.
static base::TimeDelta GetRecommendedDelay(const base::TimeDelta& base_delay);
// SyncSession::Delegate implementation.
virtual void OnSilencedUntil(const base::TimeTicks& silenced_until);
virtual bool IsSyncingCurrentlySilenced();
virtual void OnReceivedShortPollIntervalUpdate(
const base::TimeDelta& new_interval);
virtual void OnReceivedLongPollIntervalUpdate(
const base::TimeDelta& new_interval);
virtual void OnShouldStopSyncingPermanently();
// ServerConnectionEventListener implementation.
// TODO(tim): schedule a nudge when valid connection detected? in 1 minute?
virtual void OnServerConnectionEvent(const ServerConnectionEvent2& event);
private:
enum JobProcessDecision {
// Indicates we should continue with the current job.
CONTINUE,
// Indicates that we should save it to be processed later.
SAVE,
// Indicates we should drop this job.
DROP,
};
struct SyncSessionJob {
// An enum used to describe jobs for scheduling purposes.
enum SyncSessionJobPurpose {
// Our poll timer schedules POLL jobs periodically based on a server
// assigned poll interval.
POLL,
// A nudge task can come from a variety of components needing to force
// a sync. The source is inferable from |session.source()|.
NUDGE,
// The user invoked a function in the UI to clear their entire account
// and stop syncing (globally).
CLEAR_USER_DATA,
// Typically used for fetching updates for a subset of the enabled types
// during initial sync or reconfiguration. We don't run all steps of
// the sync cycle for these (e.g. CleanupDisabledTypes is skipped).
CONFIGURATION,
};
SyncSessionJob();
SyncSessionJob(SyncSessionJobPurpose purpose, base::TimeTicks start,
linked_ptr<sessions::SyncSession> session, bool is_canary_job,
const tracked_objects::Location& nudge_location);
~SyncSessionJob();
SyncSessionJobPurpose purpose;
base::TimeTicks scheduled_start;
linked_ptr<sessions::SyncSession> session;
bool is_canary_job;
// This is the location the nudge came from. used for debugging purpose.
// In case of multiple nudges getting coalesced this stores the first nudge
// that came in.
tracked_objects::Location nudge_location;
};
friend class SyncerThread2Test;
friend class SyncerThread2WhiteboxTest;
FRIEND_TEST_ALL_PREFIXES(SyncerThread2WhiteboxTest,
DropNudgeWhileExponentialBackOff);
FRIEND_TEST_ALL_PREFIXES(SyncerThread2WhiteboxTest, SaveNudge);
FRIEND_TEST_ALL_PREFIXES(SyncerThread2WhiteboxTest, ContinueNudge);
FRIEND_TEST_ALL_PREFIXES(SyncerThread2WhiteboxTest, DropPoll);
FRIEND_TEST_ALL_PREFIXES(SyncerThread2WhiteboxTest, ContinuePoll);
FRIEND_TEST_ALL_PREFIXES(SyncerThread2WhiteboxTest, ContinueConfiguration);
FRIEND_TEST_ALL_PREFIXES(SyncerThread2WhiteboxTest,
SaveConfigurationWhileThrottled);
FRIEND_TEST_ALL_PREFIXES(SyncerThread2WhiteboxTest,
SaveNudgeWhileThrottled);
FRIEND_TEST_ALL_PREFIXES(SyncerThread2WhiteboxTest,
ContinueClearUserDataUnderAllCircumstances);
FRIEND_TEST_ALL_PREFIXES(SyncerThread2WhiteboxTest,
ContinueCanaryJobConfig);
FRIEND_TEST_ALL_PREFIXES(SyncerThread2WhiteboxTest,
ContinueNudgeWhileExponentialBackOff);
// A component used to get time delays associated with exponential backoff.
// Encapsulated into a class to facilitate testing.
class DelayProvider {
public:
DelayProvider();
virtual base::TimeDelta GetDelay(const base::TimeDelta& last_delay);
virtual ~DelayProvider();
private:
DISALLOW_COPY_AND_ASSIGN(DelayProvider);
};
struct WaitInterval {
enum Mode {
// A wait interval whose duration has been affected by exponential
// backoff.
// EXPONENTIAL_BACKOFF intervals are nudge-rate limited to 1 per interval.
EXPONENTIAL_BACKOFF,
// A server-initiated throttled interval. We do not allow any syncing
// during such an interval.
THROTTLED,
};
WaitInterval();
~WaitInterval();
Mode mode;
// This bool is set to true if we have observed a nudge during this
// interval and mode == EXPONENTIAL_BACKOFF.
bool had_nudge;
base::TimeDelta length;
base::OneShotTimer<SyncerThread> timer;
// Configure jobs are saved only when backing off or throttling. So we
// expose the pointer here.
scoped_ptr<SyncSessionJob> pending_configure_job;
WaitInterval(Mode mode, base::TimeDelta length);
};
// Helper to assemble a job and post a delayed task to sync.
void ScheduleSyncSessionJob(
const base::TimeDelta& delay,
SyncSessionJob::SyncSessionJobPurpose purpose,
sessions::SyncSession* session,
const tracked_objects::Location& nudge_location);
// Invoke the Syncer to perform a sync.
void DoSyncSessionJob(const SyncSessionJob& job);
// Called after the Syncer has performed the sync represented by |job|, to
// reset our state.
void FinishSyncSessionJob(const SyncSessionJob& job);
// Record important state that might be needed in future syncs, such as which
// data types may require cleanup.
void UpdateCarryoverSessionState(const SyncSessionJob& old_job);
// Helper to FinishSyncSessionJob to schedule the next sync operation.
void ScheduleNextSync(const SyncSessionJob& old_job);
// Helper to configure polling intervals. Used by Start and ScheduleNextSync.
void AdjustPolling(const SyncSessionJob* old_job);
// Helper to ScheduleNextSync in case of consecutive sync errors.
void HandleConsecutiveContinuationError(const SyncSessionJob& old_job);
// Determines if it is legal to run |job| by checking current
// operational mode, backoff or throttling, freshness
// (so we don't make redundant syncs), and connection.
bool ShouldRunJob(const SyncSessionJob& job);
// Decide whether we should CONTINUE, SAVE or DROP the job.
JobProcessDecision DecideOnJob(const SyncSessionJob& job);
// Decide on whether to CONTINUE, SAVE or DROP the job when we are in
// backoff mode.
JobProcessDecision DecideWhileInWaitInterval(const SyncSessionJob& job);
// Saves the job for future execution. Note: It drops all the poll jobs.
void SaveJob(const SyncSessionJob& job);
// Coalesces the current job with the pending nudge.
void InitOrCoalescePendingJob(const SyncSessionJob& job);
// 'Impl' here refers to real implementation of public functions, running on
// |thread_|.
void StartImpl(Mode mode, ModeChangeCallback* callback);
void ScheduleNudgeImpl(
const base::TimeDelta& delay,
sync_pb::GetUpdatesCallerInfo::GetUpdatesSource source,
const syncable::ModelTypePayloadMap& types_with_payloads,
bool is_canary_job, const tracked_objects::Location& nudge_location);
void ScheduleConfigImpl(const ModelSafeRoutingInfo& routing_info,
const std::vector<ModelSafeWorker*>& workers,
const sync_pb::GetUpdatesCallerInfo::GetUpdatesSource source);
void ScheduleClearUserDataImpl();
// Returns true if the client is currently in exponential backoff.
bool IsBackingOff() const;
// Helper to signal all listeners registered with |session_context_|.
void Notify(SyncEngineEvent::EventCause cause);
// Callback to change backoff state.
void DoCanaryJob();
void Unthrottle();
// Executes the pending job. Called whenever an event occurs that may
// change conditions permitting a job to run. Like when network connection is
// re-established, mode changes etc.
void DoPendingJobIfPossible(bool is_canary_job);
// The pointer is owned by the caller.
browser_sync::sessions::SyncSession* CreateSyncSession(
const browser_sync::sessions::SyncSourceInfo& info);
// Creates a session for a poll and performs the sync.
void PollTimerCallback();
// Assign |start| and |end| to appropriate SyncerStep values for the
// specified |purpose|.
void SetSyncerStepsForPurpose(SyncSessionJob::SyncSessionJobPurpose purpose,
SyncerStep* start,
SyncerStep* end);
// Initializes the hookup between the ServerConnectionManager and us.
void WatchConnectionManager();
// Used to update |server_connection_ok_|, see below.
void CheckServerConnectionManagerStatus(
HttpResponse::ServerConnectionCode code);
// Called once the first time thread_ is 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();
base::Thread thread_;
// 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_;
// Periodic timer for polling. See AdjustPolling.
base::RepeatingTimer<SyncerThread> poll_timer_;
// The mode of operation. We don't use a lock, see Start(...) comment.
Mode mode_;
// TODO(tim): Bug 26339. This needs to track more than just time I think,
// since the nudges could be for different types. Current impl doesn't care.
base::TimeTicks last_sync_session_end_time_;
// Have we observed a valid server connection?
bool server_connection_ok_;
// Tracks in-flight nudges so we can coalesce.
scoped_ptr<SyncSessionJob> pending_nudge_;
// Current wait state. Null if we're not in backoff and not throttled.
scoped_ptr<WaitInterval> wait_interval_;
scoped_ptr<DelayProvider> delay_provider_;
// Invoked to run through the sync cycle.
scoped_ptr<Syncer> syncer_;
scoped_ptr<sessions::SyncSessionContext> session_context_;
DISALLOW_COPY_AND_ASSIGN(SyncerThread);
};
} // namespace browser_sync
// The SyncerThread manages its own internal thread and thus outlives it. We
// don't need refcounting for posting tasks to this internal thread.
DISABLE_RUNNABLE_METHOD_REFCOUNT(browser_sync::SyncerThread);
#endif // CHROME_BROWSER_SYNC_ENGINE_SYNCER_THREAD_H_