diff options
author | akalin@chromium.org <akalin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-08-16 01:26:17 +0000 |
---|---|---|
committer | akalin@chromium.org <akalin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-08-16 01:26:17 +0000 |
commit | 6517c45b4e76fe4ef381da7b3559db8afdf65fc2 (patch) | |
tree | 2c650653d6ee941ca671d3215b4abdea0f301415 | |
parent | 257745de825487cc6bb520e0f420ffa51003e0a1 (diff) | |
download | chromium_src-6517c45b4e76fe4ef381da7b3559db8afdf65fc2.zip chromium_src-6517c45b4e76fe4ef381da7b3559db8afdf65fc2.tar.gz chromium_src-6517c45b4e76fe4ef381da7b3559db8afdf65fc2.tar.bz2 |
[Sync] (Merge to 1229) Avoid unregistering object IDs on shutdown
Add RegisterHandler() and UnregisterHandler(), which should be called before and after calls to UpdateRegisteredIds(). Use UnregisterHandler() on
shutdown instead of UpdateRegisteredIds(_, ObjectIdSet()).
Make SyncNotifierHelper non-thread-safe. Fix test breakages that this revealed. Also add GetAllRegisteredIds() instead of making it the return value of UpdateRegisteredIds().
Propagate UpdateRegisteredIds()/RegisterHandler()/UnregisterHandler() all
the way up to ProfileSyncService.
Make FakeSyncManager be created on the sync thread.
Clean up SyncBackendHost startup/shutdown behavior a bit.
BUG=140325
TBR=tim@chromium.org
Committed: http://src.chromium.org/viewvc/chrome?view=rev&revision=150990
Review URL: https://chromiumcodereview.appspot.com/10824161
Review URL: https://chromiumcodereview.appspot.com/10825379
git-svn-id: svn://svn.chromium.org/chrome/branches/1229/src@151820 0039d316-1c4b-4281-b951-d872f2087c98
37 files changed, 1072 insertions, 513 deletions
diff --git a/chrome/browser/sync/glue/bridged_sync_notifier.cc b/chrome/browser/sync/glue/bridged_sync_notifier.cc index 5e3f669..f783418 100644 --- a/chrome/browser/sync/glue/bridged_sync_notifier.cc +++ b/chrome/browser/sync/glue/bridged_sync_notifier.cc @@ -18,14 +18,28 @@ BridgedSyncNotifier::BridgedSyncNotifier( BridgedSyncNotifier::~BridgedSyncNotifier() { } +void BridgedSyncNotifier::RegisterHandler( + syncer::SyncNotifierObserver* handler) { + if (delegate_.get()) + delegate_->RegisterHandler(handler); + bridge_->RegisterHandler(handler); +} + void BridgedSyncNotifier::UpdateRegisteredIds( syncer::SyncNotifierObserver* handler, const syncer::ObjectIdSet& ids) { if (delegate_.get()) - delegate_->UpdateRegisteredIds(handler, ids); + delegate_->UpdateRegisteredIds(handler, ids); bridge_->UpdateRegisteredIds(handler, ids); } +void BridgedSyncNotifier::UnregisterHandler( + syncer::SyncNotifierObserver* handler) { + if (delegate_.get()) + delegate_->UnregisterHandler(handler); + bridge_->UnregisterHandler(handler); +} + void BridgedSyncNotifier::SetUniqueId(const std::string& unique_id) { if (delegate_.get()) delegate_->SetUniqueId(unique_id); diff --git a/chrome/browser/sync/glue/bridged_sync_notifier.h b/chrome/browser/sync/glue/bridged_sync_notifier.h index f48d0f9..616e16d 100644 --- a/chrome/browser/sync/glue/bridged_sync_notifier.h +++ b/chrome/browser/sync/glue/bridged_sync_notifier.h @@ -28,9 +28,13 @@ class BridgedSyncNotifier : public syncer::SyncNotifier { virtual ~BridgedSyncNotifier(); // SyncNotifier implementation. Passes through all calls to the delegate. - // UpdateRegisteredIds calls will also be forwarded to the bridge. + // RegisterHandler, UnregisterHandler, and UpdateRegisteredIds calls will + // also be forwarded to the bridge. + virtual void RegisterHandler(syncer::SyncNotifierObserver* handler) OVERRIDE; virtual void UpdateRegisteredIds(syncer::SyncNotifierObserver* handler, const syncer::ObjectIdSet& ids) OVERRIDE; + virtual void UnregisterHandler( + syncer::SyncNotifierObserver* handler) OVERRIDE; virtual void SetUniqueId(const std::string& unique_id) OVERRIDE; virtual void SetStateDeprecated(const std::string& state) OVERRIDE; virtual void UpdateCredentials( diff --git a/chrome/browser/sync/glue/bridged_sync_notifier_unittest.cc b/chrome/browser/sync/glue/bridged_sync_notifier_unittest.cc index 7347eac..8863be4 100644 --- a/chrome/browser/sync/glue/bridged_sync_notifier_unittest.cc +++ b/chrome/browser/sync/glue/bridged_sync_notifier_unittest.cc @@ -35,8 +35,11 @@ class MockChromeSyncNotificationBridge : public ChromeSyncNotificationBridge { : ChromeSyncNotificationBridge(profile, sync_task_runner) {} virtual ~MockChromeSyncNotificationBridge() {} - MOCK_METHOD2(UpdateRegisteredIds, void(syncer::SyncNotifierObserver*, - const syncer::ObjectIdSet&)); + MOCK_METHOD1(RegisterHandler, void(syncer::SyncNotifierObserver*)); + MOCK_METHOD2(UpdateRegisteredIds, + void(syncer::SyncNotifierObserver*, + const syncer::ObjectIdSet&)); + MOCK_METHOD1(UnregisterHandler, void(syncer::SyncNotifierObserver*)); }; class MockSyncNotifier : public syncer::SyncNotifier { @@ -44,8 +47,11 @@ class MockSyncNotifier : public syncer::SyncNotifier { MockSyncNotifier() {} virtual ~MockSyncNotifier() {} + MOCK_METHOD1(RegisterHandler, void(syncer::SyncNotifierObserver*)); MOCK_METHOD2(UpdateRegisteredIds, - void(syncer::SyncNotifierObserver*, const syncer::ObjectIdSet&)); + void(syncer::SyncNotifierObserver*, + const syncer::ObjectIdSet&)); + MOCK_METHOD1(UnregisterHandler, void(syncer::SyncNotifierObserver*)); MOCK_METHOD1(SetUniqueId, void(const std::string&)); MOCK_METHOD1(SetStateDeprecated, void(const std::string&)); MOCK_METHOD2(UpdateCredentials, void(const std::string&, const std::string&)); @@ -53,8 +59,9 @@ class MockSyncNotifier : public syncer::SyncNotifier { }; // All tests just verify that each call is passed through to the delegate, with -// the exception of UpdateRegisteredIds, which also verifies the call is -// forwarded to the bridge. +// the exception of RegisterHandler, UnregisterHandler, and +// UpdateRegisteredIds, which also verifies the call is forwarded to the +// bridge. class BridgedSyncNotifierTest : public testing::Test { public: BridgedSyncNotifierTest() @@ -73,13 +80,26 @@ class BridgedSyncNotifierTest : public testing::Test { BridgedSyncNotifier bridged_notifier_; }; -TEST_F(BridgedSyncNotifierTest, UpdateRegisteredIds) { +TEST_F(BridgedSyncNotifierTest, RegisterHandler) { syncer::MockSyncNotifierObserver observer; + EXPECT_CALL(mock_bridge_, RegisterHandler(&observer)); + EXPECT_CALL(*mock_delegate_, RegisterHandler(&observer)); + bridged_notifier_.RegisterHandler(&observer); +} + +TEST_F(BridgedSyncNotifierTest, UpdateRegisteredIds) { EXPECT_CALL(mock_bridge_, UpdateRegisteredIds( - &observer, syncer::ObjectIdSet())); + NULL, syncer::ObjectIdSet())); EXPECT_CALL(*mock_delegate_, UpdateRegisteredIds( - &observer, syncer::ObjectIdSet())); - bridged_notifier_.UpdateRegisteredIds(&observer, syncer::ObjectIdSet()); + NULL, syncer::ObjectIdSet())); + bridged_notifier_.UpdateRegisteredIds(NULL, syncer::ObjectIdSet()); +} + +TEST_F(BridgedSyncNotifierTest, UnregisterHandler) { + syncer::MockSyncNotifierObserver observer; + EXPECT_CALL(mock_bridge_, UnregisterHandler(&observer)); + EXPECT_CALL(*mock_delegate_, UnregisterHandler(&observer)); + bridged_notifier_.UnregisterHandler(&observer); } TEST_F(BridgedSyncNotifierTest, SetUniqueId) { diff --git a/chrome/browser/sync/glue/chrome_sync_notification_bridge.cc b/chrome/browser/sync/glue/chrome_sync_notification_bridge.cc index 31db8e2..038d4f2 100644 --- a/chrome/browser/sync/glue/chrome_sync_notification_bridge.cc +++ b/chrome/browser/sync/glue/chrome_sync_notification_bridge.cc @@ -6,11 +6,13 @@ #include "base/bind.h" #include "base/location.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" #include "chrome/common/chrome_notification_types.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/notification_service.h" -#include "sync/notifier/sync_notifier_helper.h" #include "sync/notifier/sync_notifier_observer.h" +#include "sync/notifier/sync_notifier_registrar.h" using content::BrowserThread; @@ -25,9 +27,14 @@ class ChromeSyncNotificationBridge::Core // All member functions below must be called on the sync task runner. + void InitializeOnSyncThread(); + void CleanupOnSyncThread(); + void UpdateEnabledTypes(syncer::ModelTypeSet enabled_types); + void RegisterHandler(syncer::SyncNotifierObserver* handler); void UpdateRegisteredIds(syncer::SyncNotifierObserver* handler, const syncer::ObjectIdSet& ids); + void UnregisterHandler(syncer::SyncNotifierObserver* handler); void EmitNotification( const syncer::ModelTypePayloadMap& payload_map, @@ -43,7 +50,7 @@ class ChromeSyncNotificationBridge::Core // Used only on |sync_task_runner_|. syncer::ModelTypeSet enabled_types_; - syncer::SyncNotifierHelper helper_; + scoped_ptr<syncer::SyncNotifierRegistrar> notifier_registrar_; }; ChromeSyncNotificationBridge::Core::Core( @@ -56,6 +63,15 @@ ChromeSyncNotificationBridge::Core::Core( ChromeSyncNotificationBridge::Core::~Core() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) || sync_task_runner_->RunsTasksOnCurrentThread()); + DCHECK(!notifier_registrar_.get()); +} + +void ChromeSyncNotificationBridge::Core::InitializeOnSyncThread() { + notifier_registrar_.reset(new syncer::SyncNotifierRegistrar()); +} + +void ChromeSyncNotificationBridge::Core::CleanupOnSyncThread() { + notifier_registrar_.reset(); } void ChromeSyncNotificationBridge::Core::UpdateEnabledTypes( @@ -64,11 +80,23 @@ void ChromeSyncNotificationBridge::Core::UpdateEnabledTypes( enabled_types_ = types; } +void ChromeSyncNotificationBridge::Core::RegisterHandler( + syncer::SyncNotifierObserver* handler) { + DCHECK(sync_task_runner_->RunsTasksOnCurrentThread()); + notifier_registrar_->RegisterHandler(handler); +} + void ChromeSyncNotificationBridge::Core::UpdateRegisteredIds( syncer::SyncNotifierObserver* handler, const syncer::ObjectIdSet& ids) { DCHECK(sync_task_runner_->RunsTasksOnCurrentThread()); - helper_.UpdateRegisteredIds(handler, ids); + notifier_registrar_->UpdateRegisteredIds(handler, ids); +} + +void ChromeSyncNotificationBridge::Core::UnregisterHandler( + syncer::SyncNotifierObserver* handler) { + DCHECK(sync_task_runner_->RunsTasksOnCurrentThread()); + notifier_registrar_->UnregisterHandler(handler); } void ChromeSyncNotificationBridge::Core::EmitNotification( @@ -80,7 +108,7 @@ void ChromeSyncNotificationBridge::Core::EmitNotification( syncer::ModelTypePayloadMapFromEnumSet(enabled_types_, std::string()) : payload_map; - helper_.DispatchInvalidationsToHandlers( + notifier_registrar_->DispatchInvalidationsToHandlers( ModelTypePayloadMapToObjectIdPayloadMap(effective_payload_map), notification_source); } @@ -96,16 +124,34 @@ ChromeSyncNotificationBridge::ChromeSyncNotificationBridge( content::Source<Profile>(profile)); registrar_.Add(this, chrome::NOTIFICATION_SYNC_REFRESH_REMOTE, content::Source<Profile>(profile)); + + if (!sync_task_runner_->PostTask( + FROM_HERE, base::Bind(&Core::InitializeOnSyncThread, core_))) { + NOTREACHED(); + } } ChromeSyncNotificationBridge::~ChromeSyncNotificationBridge() {} +void ChromeSyncNotificationBridge::StopForShutdown() { + if (!sync_task_runner_->PostTask( + FROM_HERE, base::Bind(&Core::CleanupOnSyncThread, core_))) { + NOTREACHED(); + } +} + void ChromeSyncNotificationBridge::UpdateEnabledTypes( syncer::ModelTypeSet types) { DCHECK(sync_task_runner_->RunsTasksOnCurrentThread()); core_->UpdateEnabledTypes(types); } +void ChromeSyncNotificationBridge::RegisterHandler( + syncer::SyncNotifierObserver* handler) { + DCHECK(sync_task_runner_->RunsTasksOnCurrentThread()); + core_->RegisterHandler(handler); +} + void ChromeSyncNotificationBridge::UpdateRegisteredIds( syncer::SyncNotifierObserver* handler, const syncer::ObjectIdSet& ids) { @@ -113,6 +159,12 @@ void ChromeSyncNotificationBridge::UpdateRegisteredIds( core_->UpdateRegisteredIds(handler, ids); } +void ChromeSyncNotificationBridge::UnregisterHandler( + syncer::SyncNotifierObserver* handler) { + DCHECK(sync_task_runner_->RunsTasksOnCurrentThread()); + core_->UnregisterHandler(handler); +} + void ChromeSyncNotificationBridge::Observe( int type, const content::NotificationSource& source, @@ -132,10 +184,12 @@ void ChromeSyncNotificationBridge::Observe( content::Details<const syncer::ModelTypePayloadMap> payload_details(details); const syncer::ModelTypePayloadMap& payload_map = *(payload_details.ptr()); - sync_task_runner_->PostTask( - FROM_HERE, - base::Bind(&Core::EmitNotification, - core_, payload_map, notification_source)); + if (!sync_task_runner_->PostTask( + FROM_HERE, + base::Bind(&Core::EmitNotification, + core_, payload_map, notification_source))) { + NOTREACHED(); + } } } // namespace browser_sync diff --git a/chrome/browser/sync/glue/chrome_sync_notification_bridge.h b/chrome/browser/sync/glue/chrome_sync_notification_bridge.h index a878ff7..0216df9 100644 --- a/chrome/browser/sync/glue/chrome_sync_notification_bridge.h +++ b/chrome/browser/sync/glue/chrome_sync_notification_bridge.h @@ -36,11 +36,18 @@ class ChromeSyncNotificationBridge : public content::NotificationObserver { const scoped_refptr<base::SequencedTaskRunner>& sync_task_runner); virtual ~ChromeSyncNotificationBridge(); + // Must be called on the UI thread while the sync task runner is still + // around. No other member functions on the sync thread may be called after + // this is called. + void StopForShutdown(); + // Must be called on the sync task runner. void UpdateEnabledTypes(syncer::ModelTypeSet enabled_types); // Marked virtual for tests. + virtual void RegisterHandler(syncer::SyncNotifierObserver* handler); virtual void UpdateRegisteredIds(syncer::SyncNotifierObserver* handler, const syncer::ObjectIdSet& ids); + virtual void UnregisterHandler(syncer::SyncNotifierObserver* handler); // NotificationObserver implementation. Called on UI thread. virtual void Observe(int type, diff --git a/chrome/browser/sync/glue/chrome_sync_notification_bridge_unittest.cc b/chrome/browser/sync/glue/chrome_sync_notification_bridge_unittest.cc index 991b774..ed50b9e 100644 --- a/chrome/browser/sync/glue/chrome_sync_notification_bridge_unittest.cc +++ b/chrome/browser/sync/glue/chrome_sync_notification_bridge_unittest.cc @@ -4,6 +4,8 @@ #include "chrome/browser/sync/glue/chrome_sync_notification_bridge.h" +#include <cstddef> + #include "base/compiler_specific.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" @@ -53,6 +55,7 @@ class FakeSyncNotifierObserver : public syncer::SyncNotifierObserver { expected_payloads_(expected_payloads), expected_source_(expected_source) { DCHECK(sync_task_runner_->RunsTasksOnCurrentThread()); + bridge_->RegisterHandler(this); const syncer::ObjectIdSet& ids = syncer::ObjectIdPayloadMapToSet(expected_payloads); bridge_->UpdateRegisteredIds(this, ids); @@ -60,7 +63,7 @@ class FakeSyncNotifierObserver : public syncer::SyncNotifierObserver { virtual ~FakeSyncNotifierObserver() { DCHECK(sync_task_runner_->RunsTasksOnCurrentThread()); - bridge_->UpdateRegisteredIds(this, syncer::ObjectIdSet()); + bridge_->UnregisterHandler(this); } // SyncNotifierObserver implementation. @@ -120,6 +123,7 @@ class ChromeSyncNotificationBridgeTest : public testing::Test { } virtual void TearDown() OVERRIDE { + bridge_->StopForShutdown(); sync_thread_.Stop(); // Must be reset only after the sync thread is stopped. bridge_.reset(); diff --git a/chrome/browser/sync/glue/sync_backend_host.cc b/chrome/browser/sync/glue/sync_backend_host.cc index 9b7d19d..e3dc7cf 100644 --- a/chrome/browser/sync/glue/sync_backend_host.cc +++ b/chrome/browser/sync/glue/sync_backend_host.cc @@ -170,6 +170,7 @@ class SyncBackendHost::Core // (in step 2). void DoStopSyncManagerForShutdown(const base::Closure& closure); void DoShutdown(bool stopping_sync); + void DoDestroySyncManager(); // Configuration methods that must execute on sync loop. void DoConfigureSyncer( @@ -245,6 +246,13 @@ class SyncBackendHost::Core // The top-level syncapi entry point. Lives on the sync thread. scoped_ptr<syncer::SyncManager> sync_manager_; + // Whether or not we registered with |sync_manager_| as an invalidation + // handler. Necessary since we may end up trying to unregister before we + // register in tests (in synchronous initialization mode). + // + // TODO(akalin): Fix this behavior (see http://crbug.com/140354). + bool registered_as_invalidation_handler_; + DISALLOW_COPY_AND_ASSIGN(Core); }; @@ -541,11 +549,15 @@ void SyncBackendHost::StopSyncManagerForShutdown( void SyncBackendHost::StopSyncingForShutdown() { DCHECK_EQ(MessageLoop::current(), frontend_loop_); + + // Immediately stop sending messages to the frontend. + frontend_ = NULL; + // Thread shutdown should occur in the following order: // - Sync Thread // - UI Thread (stops some time after we return from this call). // - // In order to acheive this, we first shutdown components from the UI thread + // In order to achieve this, we first shutdown components from the UI thread // and send signals to abort components that may be busy on the sync thread. // The callback (OnSyncerShutdownComplete) will happen on the sync thread, // after which we'll shutdown components on the sync thread, and then be @@ -570,17 +582,23 @@ void SyncBackendHost::StopSyncingForShutdown() { // If the sync thread isn't running, then the syncer is effectively // stopped. Moreover, it implies that we never attempted initialization, // so the registrar won't need stopping either. - DCHECK_EQ(NOT_ATTEMPTED, initialization_state_); + DCHECK_EQ(initialization_state_, NOT_ATTEMPTED); DCHECK(!registrar_.get()); } } void SyncBackendHost::Shutdown(bool sync_disabled) { + // StopSyncingForShutdown() (which nulls out |frontend_|) should be + // called first. + DCHECK(!frontend_); // TODO(tim): DCHECK(registrar_->StoppedOnUIThread()) would be nice. if (sync_thread_.IsRunning()) { sync_thread_.message_loop()->PostTask(FROM_HERE, base::Bind(&SyncBackendHost::Core::DoShutdown, core_.get(), sync_disabled)); + + if (chrome_sync_notification_bridge_.get()) + chrome_sync_notification_bridge_->StopForShutdown(); } // Stop will return once the thread exits, which will be after DoShutdown @@ -605,7 +623,6 @@ void SyncBackendHost::Shutdown(bool sync_disabled) { stop_sync_thread_time); registrar_.reset(); - frontend_ = NULL; chrome_sync_notification_bridge_.reset(); core_ = NULL; // Releases reference to core_. } @@ -846,7 +863,8 @@ SyncBackendHost::Core::Core(const std::string& name, host_(backend), sync_loop_(NULL), registrar_(NULL), - chrome_sync_notification_bridge_(NULL) { + chrome_sync_notification_bridge_(NULL), + registered_as_invalidation_handler_(false) { DCHECK(backend.get()); } @@ -892,11 +910,7 @@ void SyncBackendHost::Core::OnInitializationComplete( DCHECK_EQ(MessageLoop::current(), sync_loop_); if (!success) { - sync_manager_->RemoveObserver(this); - sync_manager_->UpdateRegisteredInvalidationIds( - this, syncer::ObjectIdSet()); - sync_manager_->ShutdownOnSyncThread(); - sync_manager_.reset(); + DoDestroySyncManager(); } host_.Call( @@ -1085,15 +1099,24 @@ void SyncBackendHost::Core::DoInitialize(const DoInitializeOptions& options) { options.report_unrecoverable_error_function); LOG_IF(ERROR, !success) << "Sync manager initialization failed!"; - // Now check the command line to see if we need to simulate an - // unrecoverable error for testing purpose. Note the error is thrown - // only if the initialization succeeded. Also it makes sense to use this - // flag only when restarting the browser with an account already setup. If - // you use this before setting up the setup would not succeed as an error - // would be encountered. - if (CommandLine::ForCurrentProcess()->HasSwitch( - switches::kSyncThrowUnrecoverableError)) { - sync_manager_->ThrowUnrecoverableError(); + // |sync_manager_| may end up being NULL here in tests (in + // synchronous initialization mode). + // + // TODO(akalin): Fix this behavior (see http://crbug.com/140354). + if (sync_manager_.get()) { + sync_manager_->RegisterInvalidationHandler(this); + registered_as_invalidation_handler_ = true; + + // Now check the command line to see if we need to simulate an + // unrecoverable error for testing purpose. Note the error is thrown + // only if the initialization succeeded. Also it makes sense to use this + // flag only when restarting the browser with an account already setup. If + // you use this before setting up the setup would not succeed as an error + // would be encountered. + if (CommandLine::ForCurrentProcess()->HasSwitch( + switches::kSyncThrowUnrecoverableError)) { + sync_manager_->ThrowUnrecoverableError(); + } } } @@ -1110,7 +1133,7 @@ void SyncBackendHost::Core::DoUpdateRegisteredInvalidationIds( // synchronous initialization mode) since this is called during // shutdown. // - // TODO(akalin): Fix this behavior. + // TODO(akalin): Fix this behavior (see http://crbug.com/140354). if (sync_manager_.get()) { sync_manager_->UpdateRegisteredInvalidationIds(this, ids); } @@ -1159,14 +1182,7 @@ void SyncBackendHost::Core::DoStopSyncManagerForShutdown( void SyncBackendHost::Core::DoShutdown(bool sync_disabled) { DCHECK_EQ(MessageLoop::current(), sync_loop_); - if (sync_manager_.get()) { - save_changes_timer_.reset(); - sync_manager_->UpdateRegisteredInvalidationIds( - this, syncer::ObjectIdSet()); - sync_manager_->ShutdownOnSyncThread(); - sync_manager_->RemoveObserver(this); - sync_manager_.reset(); - } + DoDestroySyncManager(); chrome_sync_notification_bridge_ = NULL; registrar_ = NULL; @@ -1179,6 +1195,20 @@ void SyncBackendHost::Core::DoShutdown(bool sync_disabled) { host_.Reset(); } +void SyncBackendHost::Core::DoDestroySyncManager() { + DCHECK_EQ(MessageLoop::current(), sync_loop_); + if (sync_manager_.get()) { + save_changes_timer_.reset(); + if (registered_as_invalidation_handler_) { + sync_manager_->UnregisterInvalidationHandler(this); + registered_as_invalidation_handler_ = false; + } + sync_manager_->RemoveObserver(this); + sync_manager_->ShutdownOnSyncThread(); + sync_manager_.reset(); + } +} + void SyncBackendHost::Core::DoConfigureSyncer( syncer::ConfigureReason reason, syncer::ModelTypeSet types_to_config, @@ -1269,7 +1299,7 @@ void SyncBackendHost::OnNigoriDownloadRetry() { void SyncBackendHost::HandleInitializationCompletedOnFrontendLoop( const syncer::WeakHandle<syncer::JsBackend>& js_backend, bool success) { - DCHECK_NE(NOT_ATTEMPTED, initialization_state_); + DCHECK_NE(initialization_state_, NOT_ATTEMPTED); if (!frontend_) return; diff --git a/chrome/browser/sync/glue/sync_backend_host.h b/chrome/browser/sync/glue/sync_backend_host.h index bafe140..dd703b4 100644 --- a/chrome/browser/sync/glue/sync_backend_host.h +++ b/chrome/browser/sync/glue/sync_backend_host.h @@ -179,8 +179,8 @@ class SyncBackendHost : public BackendDataTypeConfigurer { // Called on |frontend_loop| to update SyncCredentials. void UpdateCredentials(const syncer::SyncCredentials& credentials); - // Registers the underlying frontend for the given IDs to the - // underlying notifier. + // Registers the underlying frontend for the given IDs to the underlying + // notifier. This lasts until StopSyncingForShutdown() is called. void UpdateRegisteredInvalidationIds(const syncer::ObjectIdSet& ids); // This starts the SyncerThread running a Syncer object to communicate with @@ -211,9 +211,10 @@ class SyncBackendHost : public BackendDataTypeConfigurer { bool SetDecryptionPassphrase(const std::string& passphrase) WARN_UNUSED_RESULT; - // Called on |frontend_loop_| to kick off shutdown procedure. After this, - // no further sync activity will occur with the sync server and no further + // Called on |frontend_loop_| to kick off shutdown procedure. After this, no + // further sync activity will occur with the sync server and no further // change applications will occur from changes already downloaded. + // Furthermore, no notifications will be sent to any invalidation handler. virtual void StopSyncingForShutdown(); // Called on |frontend_loop_| to kick off shutdown. diff --git a/chrome/browser/sync/glue/sync_backend_host_unittest.cc b/chrome/browser/sync/glue/sync_backend_host_unittest.cc index 2145da1..16770e7d 100644 --- a/chrome/browser/sync/glue/sync_backend_host_unittest.cc +++ b/chrome/browser/sync/glue/sync_backend_host_unittest.cc @@ -89,24 +89,44 @@ class MockSyncFrontend : public SyncFrontend { class FakeSyncManagerFactory : public syncer::SyncManagerFactory { public: - FakeSyncManagerFactory() : manager_(NULL) {} + FakeSyncManagerFactory() : fake_manager_(NULL) {} virtual ~FakeSyncManagerFactory() {} - // Takes ownership of |manager|. - void SetSyncManager(FakeSyncManager* manager) { - DCHECK(!manager_.get()); - manager_.reset(manager); + // SyncManagerFactory implementation. Called on the sync thread. + virtual scoped_ptr<SyncManager> CreateSyncManager( + std::string name) OVERRIDE { + DCHECK(!fake_manager_); + fake_manager_ = new FakeSyncManager(initial_sync_ended_types_, + progress_marker_types_, + configure_fail_types_); + return scoped_ptr<SyncManager>(fake_manager_); } - // Passes ownership of |manager_|. - // SyncManagerFactory implementation. - virtual scoped_ptr<SyncManager> CreateSyncManager(std::string name) OVERRIDE { - DCHECK(manager_.get()); - return manager_.Pass(); + // Returns NULL until CreateSyncManager() is called on the sync + // thread. Called on the main thread, but only after + // OnBackendInitialized() is called (which is strictly after + // CreateSyncManager is called on the sync thread). + FakeSyncManager* fake_manager() { + return fake_manager_; + } + + void set_initial_sync_ended_types(syncer::ModelTypeSet types) { + initial_sync_ended_types_ = types; + } + + void set_progress_marker_types(syncer::ModelTypeSet types) { + progress_marker_types_ = types; + } + + void set_configure_fail_types(syncer::ModelTypeSet types) { + configure_fail_types_ = types; } private: - scoped_ptr<SyncManager> manager_; + syncer::ModelTypeSet initial_sync_ended_types_; + syncer::ModelTypeSet progress_marker_types_; + syncer::ModelTypeSet configure_fail_types_; + FakeSyncManager* fake_manager_; }; class SyncBackendHostTest : public testing::Test { @@ -131,8 +151,6 @@ class SyncBackendHostTest : public testing::Test { invalidator_storage_->AsWeakPtr())); credentials_.email = "user@example.com"; credentials_.sync_token = "sync_token"; - fake_manager_ = new FakeSyncManager(); - fake_sync_manager_factory_.SetSyncManager(fake_manager_); // NOTE: We can't include Passwords or Typed URLs due to the Sync Backend // Registrar removing them if it can't find their model workers. @@ -145,8 +163,10 @@ class SyncBackendHostTest : public testing::Test { } virtual void TearDown() OVERRIDE { - backend_->StopSyncingForShutdown(); - backend_->Shutdown(false); + if (backend_.get()) { + backend_->StopSyncingForShutdown(); + backend_->Shutdown(false); + } backend_.reset(); sync_prefs_.reset(); invalidator_storage_.reset(); @@ -168,12 +188,17 @@ class SyncBackendHostTest : public testing::Test { GURL(""), credentials_, true, - &fake_sync_manager_factory_, + &fake_manager_factory_, &handler_, NULL); ui_loop_.PostDelayedTask(FROM_HERE, ui_loop_.QuitClosure(), TestTimeouts::action_timeout()); ui_loop_.Run(); + // |fake_manager_factory_|'s fake_manager() is set on the sync + // thread, but we can rely on the message loop barriers to + // guarantee that we see the updated value. + fake_manager_ = fake_manager_factory_.fake_manager(); + DCHECK(fake_manager_); } // Synchronously configures the backend's datatypes. @@ -206,15 +231,15 @@ class SyncBackendHostTest : public testing::Test { MessageLoop ui_loop_; content::TestBrowserThread ui_thread_; content::TestBrowserThread io_thread_; - MockSyncFrontend mock_frontend_; + StrictMock<MockSyncFrontend> mock_frontend_; syncer::SyncCredentials credentials_; syncer::TestUnrecoverableErrorHandler handler_; scoped_ptr<TestingProfile> profile_; scoped_ptr<SyncPrefs> sync_prefs_; scoped_ptr<InvalidatorStorage> invalidator_storage_; scoped_ptr<SyncBackendHost> backend_; - FakeSyncManagerFactory fake_sync_manager_factory_; FakeSyncManager* fake_manager_; + FakeSyncManagerFactory fake_manager_factory_; syncer::ModelTypeSet enabled_types_; }; @@ -257,8 +282,8 @@ TEST_F(SyncBackendHostTest, FirstTimeSync) { TEST_F(SyncBackendHostTest, Restart) { sync_prefs_->SetSyncSetupCompleted(); syncer::ModelTypeSet all_but_nigori = enabled_types_; - fake_manager_->set_progress_marker_types(enabled_types_); - fake_manager_->set_initial_sync_ended_types(enabled_types_); + fake_manager_factory_.set_progress_marker_types(enabled_types_); + fake_manager_factory_.set_initial_sync_ended_types(enabled_types_); InitializeBackend(); EXPECT_TRUE(fake_manager_->GetAndResetDownloadedTypes().Empty()); EXPECT_TRUE(Intersection(fake_manager_->GetAndResetCleanedTypes(), @@ -289,8 +314,8 @@ TEST_F(SyncBackendHostTest, PartialTypes) { syncer::ModelTypeSet partial_types(syncer::NIGORI, syncer::BOOKMARKS); syncer::ModelTypeSet full_types = Difference(enabled_types_, partial_types); - fake_manager_->set_progress_marker_types(enabled_types_); - fake_manager_->set_initial_sync_ended_types(full_types); + fake_manager_factory_.set_progress_marker_types(enabled_types_); + fake_manager_factory_.set_initial_sync_ended_types(full_types); // Bringing up the backend should purge all partial types, then proceed to // download the Nigori. @@ -476,8 +501,8 @@ TEST_F(SyncBackendHostTest, NewlySupportedTypes) { // Set sync manager behavior before passing it down. All types have progress // markers and initial sync ended except the new types. syncer::ModelTypeSet old_types = enabled_types_; - fake_manager_->set_progress_marker_types(old_types); - fake_manager_->set_initial_sync_ended_types(old_types); + fake_manager_factory_.set_progress_marker_types(old_types); + fake_manager_factory_.set_initial_sync_ended_types(old_types); syncer::ModelTypeSet new_types(syncer::APP_SETTINGS, syncer::EXTENSION_SETTINGS); enabled_types_.PutAll(new_types); @@ -517,8 +542,8 @@ TEST_F(SyncBackendHostTest, NewlySupportedTypesWithPartialTypes) { syncer::ModelTypeSet partial_types(syncer::NIGORI, syncer::BOOKMARKS); syncer::ModelTypeSet full_types = Difference(enabled_types_, partial_types); - fake_manager_->set_progress_marker_types(old_types); - fake_manager_->set_initial_sync_ended_types(full_types); + fake_manager_factory_.set_progress_marker_types(old_types); + fake_manager_factory_.set_initial_sync_ended_types(full_types); syncer::ModelTypeSet new_types(syncer::APP_SETTINGS, syncer::EXTENSION_SETTINGS); enabled_types_.PutAll(new_types); @@ -609,6 +634,35 @@ TEST_F(SyncBackendHostTest, DisableNotifications) { ui_loop_.Run(); } +// Call StopSyncingForShutdown() on the backend and fire some notifications +// before calling Shutdown(). Then start up and shut down the backend again. +// Those notifications shouldn't propagate to the frontend. +TEST_F(SyncBackendHostTest, NotificationsAfterStopSyncingForShutdown) { + InitializeBackend(); + + syncer::ObjectIdSet ids; + ids.insert(invalidation::ObjectId(5, "id5")); + backend_->UpdateRegisteredInvalidationIds(ids); + + backend_->StopSyncingForShutdown(); + + // Should not trigger anything. + fake_manager_->DisableNotifications(syncer::TRANSIENT_NOTIFICATION_ERROR); + fake_manager_->EnableNotifications(); + const syncer::ObjectIdPayloadMap& id_payloads = + syncer::ObjectIdSetToPayloadMap(ids, "payload"); + fake_manager_->Invalidate(id_payloads, syncer::REMOTE_NOTIFICATION); + + // Make sure the above calls take effect before we continue. + fake_manager_->WaitForSyncThread(); + + backend_->Shutdown(false); + backend_.reset(); + + TearDown(); + SetUp(); +} + } // namespace } // namespace browser_sync diff --git a/chrome/browser/sync/profile_sync_service.cc b/chrome/browser/sync/profile_sync_service.cc index bc72dcc..5e83b20 100644 --- a/chrome/browser/sync/profile_sync_service.cc +++ b/chrome/browser/sync/profile_sync_service.cc @@ -428,7 +428,8 @@ void ProfileSyncService::StartUp() { // TODO(akalin): Fix this horribly non-intuitive behavior (see // http://crbug.com/140354). if (backend_.get()) { - backend_->UpdateRegisteredInvalidationIds(all_registered_ids_); + backend_->UpdateRegisteredInvalidationIds( + notifier_registrar_.GetAllRegisteredIds()); } if (!sync_global_error_.get()) { @@ -441,17 +442,29 @@ void ProfileSyncService::StartUp() { } } +void ProfileSyncService::RegisterInvalidationHandler( + syncer::SyncNotifierObserver* handler) { + notifier_registrar_.RegisterHandler(handler); +} + void ProfileSyncService::UpdateRegisteredInvalidationIds( syncer::SyncNotifierObserver* handler, const syncer::ObjectIdSet& ids) { - all_registered_ids_ = notifier_helper_.UpdateRegisteredIds(handler, ids); + notifier_registrar_.UpdateRegisteredIds(handler, ids); + // If |backend_| is NULL, its registered IDs will be updated when // it's created and initialized. if (backend_.get()) { - backend_->UpdateRegisteredInvalidationIds(all_registered_ids_); + backend_->UpdateRegisteredInvalidationIds( + notifier_registrar_.GetAllRegisteredIds()); } } +void ProfileSyncService::UnregisterInvalidationHandler( + syncer::SyncNotifierObserver* handler) { + notifier_registrar_.UnregisterHandler(handler); +} + void ProfileSyncService::Shutdown() { ShutdownImpl(false); } @@ -464,7 +477,6 @@ void ProfileSyncService::ShutdownImpl(bool sync_disabled) { base::Time shutdown_start_time = base::Time::Now(); if (backend_.get()) { backend_->StopSyncingForShutdown(); - backend_->UpdateRegisteredInvalidationIds(syncer::ObjectIdSet()); } // Stop all data type controllers, if needed. Note that until Stop @@ -659,18 +671,18 @@ void ProfileSyncService::DisableBrokenDatatype( } void ProfileSyncService::OnNotificationsEnabled() { - notifier_helper_.EmitOnNotificationsEnabled(); + notifier_registrar_.EmitOnNotificationsEnabled(); } void ProfileSyncService::OnNotificationsDisabled( syncer::NotificationsDisabledReason reason) { - notifier_helper_.EmitOnNotificationsDisabled(reason); + notifier_registrar_.EmitOnNotificationsDisabled(reason); } void ProfileSyncService::OnIncomingNotification( const syncer::ObjectIdPayloadMap& id_payloads, syncer::IncomingNotificationSource source) { - notifier_helper_.DispatchInvalidationsToHandlers(id_payloads, source); + notifier_registrar_.DispatchInvalidationsToHandlers(id_payloads, source); } void ProfileSyncService::OnBackendInitialized( diff --git a/chrome/browser/sync/profile_sync_service.h b/chrome/browser/sync/profile_sync_service.h index 3e3e14c..6e8ea6e 100644 --- a/chrome/browser/sync/profile_sync_service.h +++ b/chrome/browser/sync/profile_sync_service.h @@ -38,7 +38,7 @@ #include "sync/internal_api/public/util/experiments.h" #include "sync/internal_api/public/util/unrecoverable_error_handler.h" #include "sync/js/sync_js_controller.h" -#include "sync/notifier/sync_notifier_helper.h" +#include "sync/notifier/sync_notifier_registrar.h" class Profile; class ProfileSyncComponentsFactory; @@ -552,15 +552,56 @@ class ProfileSyncService : public browser_sync::SyncFrontend, // been cleared yet. Virtual for testing purposes. virtual bool waiting_for_auth() const; - // Updates the set of ObjectIds associated with a given |handler|. - // Passing an empty ObjectIdSet will unregister |handler|. - // There should be at most one handler registered per object id. + // Invalidation clients should follow the pattern below: // - // The handler -> registered ids map is persisted across restarts of - // sync. + // When starting the client: + // + // pss->RegisterInvalidationHandler(client_handler); + // + // When the set of IDs to register changes for the client during its lifetime + // (i.e., between calls to RegisterInvalidationHandler(client_handler) and + // UnregisterInvalidationHandler(client_handler): + // + // pss->UpdateRegisteredInvalidationIds(client_handler, client_ids); + // + // When shutting down the client for browser shutdown: + // + // pss->UnregisterInvalidationHandler(client_handler); + // + // Note that there's no call to UpdateRegisteredIds() -- this is because the + // invalidation API persists registrations across browser restarts. + // + // When permanently shutting down the client, e.g. when disabling the related + // feature: + // + // pss->UpdateRegisteredInvalidationIds(client_handler, ObjectIdSet()); + // pss->UnregisterInvalidationHandler(client_handler); + + // NOTE(akalin): Invalidations that come in during browser shutdown may get + // dropped. This won't matter once we have an Acknowledge API, though: see + // http://crbug.com/78462 and http://crbug.com/124149. + + // Starts sending notifications to |handler|. |handler| must not be NULL, + // and it must already be registered. + // + // Handler registrations are persisted across restarts of sync. + void RegisterInvalidationHandler(syncer::SyncNotifierObserver* handler); + + // Updates the set of ObjectIds associated with |handler|. |handler| must + // not be NULL, and must already be registered. An ID must be registered for + // at most one handler. + // + // Registered IDs are persisted across restarts of sync. void UpdateRegisteredInvalidationIds(syncer::SyncNotifierObserver* handler, const syncer::ObjectIdSet& ids); + // Stops sending notifications to |handler|. |handler| must not be NULL, and + // it must already be registered. Note that this doesn't unregister the IDs + // associated with |handler|. + // + // Handler registrations are persisted across restarts of sync. + void UnregisterInvalidationHandler(syncer::SyncNotifierObserver* handler); + // ProfileKeyedService implementation. virtual void Shutdown() OVERRIDE; @@ -835,11 +876,8 @@ class ProfileSyncService : public browser_sync::SyncFrontend, // Factory the backend will use to build the SyncManager. syncer::SyncManagerFactory sync_manager_factory_; - // The set of all registered IDs. - syncer::ObjectIdSet all_registered_ids_; - // Dispatches invalidations to handlers. - syncer::SyncNotifierHelper notifier_helper_; + syncer::SyncNotifierRegistrar notifier_registrar_; DISALLOW_COPY_AND_ASSIGN(ProfileSyncService); }; diff --git a/chrome/browser/sync/profile_sync_service_unittest.cc b/chrome/browser/sync/profile_sync_service_unittest.cc index 78ac1f4..1a2fe90 100644 --- a/chrome/browser/sync/profile_sync_service_unittest.cc +++ b/chrome/browser/sync/profile_sync_service_unittest.cc @@ -389,6 +389,7 @@ TEST_F(ProfileSyncServiceTest, UpdateRegisteredInvalidationIds) { EXPECT_CALL(observer, OnNotificationsDisabled( syncer::TRANSIENT_NOTIFICATION_ERROR)); + service_->RegisterInvalidationHandler(&observer); service_->UpdateRegisteredInvalidationIds(&observer, ids); SyncBackendHostForProfileSyncTest* const backend = @@ -400,7 +401,7 @@ TEST_F(ProfileSyncServiceTest, UpdateRegisteredInvalidationIds) { Mock::VerifyAndClearExpectations(&observer); - service_->UpdateRegisteredInvalidationIds(&observer, syncer::ObjectIdSet()); + service_->UnregisterInvalidationHandler(&observer); backend->EmitOnNotificationsEnabled(); backend->EmitOnIncomingNotification(payloads, syncer::REMOTE_NOTIFICATION); @@ -422,9 +423,12 @@ TEST_F(ProfileSyncServiceTest, UpdateRegisteredInvalidationIdsPersistence) { EXPECT_CALL(observer, OnNotificationsEnabled()); EXPECT_CALL(observer, OnIncomingNotification( payloads, syncer::REMOTE_NOTIFICATION)); + // This may get called more than once, as a real notifier is + // created. EXPECT_CALL(observer, OnNotificationsDisabled( - syncer::TRANSIENT_NOTIFICATION_ERROR)); + syncer::TRANSIENT_NOTIFICATION_ERROR)).Times(AtLeast(1)); + service_->RegisterInvalidationHandler(&observer); service_->UpdateRegisteredInvalidationIds(&observer, ids); service_->StopAndSuppress(); diff --git a/sync/internal_api/public/sync_manager.h b/sync/internal_api/public/sync_manager.h index 1fccac4..b08810c 100644 --- a/sync/internal_api/public/sync_manager.h +++ b/sync/internal_api/public/sync_manager.h @@ -407,12 +407,19 @@ class SyncManager { virtual void UpdateEnabledTypes( const ModelTypeSet& enabled_types) = 0; - // Forwards to the underlying notifier (see - // SyncNotifier::UpdateRegisteredIds()). + // Forwards to the underlying notifier (see comments in sync_notifier.h). + virtual void RegisterInvalidationHandler( + SyncNotifierObserver* handler) = 0; + + // Forwards to the underlying notifier (see comments in sync_notifier.h). virtual void UpdateRegisteredInvalidationIds( SyncNotifierObserver* handler, const ObjectIdSet& ids) = 0; + // Forwards to the underlying notifier (see comments in sync_notifier.h). + virtual void UnregisterInvalidationHandler( + SyncNotifierObserver* handler) = 0; + // Put the syncer in normal mode ready to perform nudges and polls. virtual void StartSyncingNormally( const ModelSafeRoutingInfo& routing_info) = 0; diff --git a/sync/internal_api/public/test/fake_sync_manager.h b/sync/internal_api/public/test/fake_sync_manager.h index cbc8b56..8607712 100644 --- a/sync/internal_api/public/test/fake_sync_manager.h +++ b/sync/internal_api/public/test/fake_sync_manager.h @@ -10,7 +10,7 @@ #include "base/memory/ref_counted.h" #include "base/observer_list.h" #include "sync/internal_api/public/sync_manager.h" -#include "sync/notifier/sync_notifier_helper.h" +#include "sync/notifier/sync_notifier_registrar.h" namespace base { class SequencedTaskRunner; @@ -20,25 +20,23 @@ namespace syncer { class FakeSyncManager : public SyncManager { public: - explicit FakeSyncManager(); + // |initial_sync_ended_types|: The set of types that have initial_sync_ended + // set to true. This value will be used by InitialSyncEndedTypes() until the + // next configuration is performed. + // + // |progress_marker_types|: The set of types that have valid progress + // markers. This will be used by GetTypesWithEmptyProgressMarkerToken() until + // the next configuration is performed. + // + // |configure_fail_types|: The set of types that will fail + // configuration. Once ConfigureSyncer is called, the + // |initial_sync_ended_types_| and |progress_marker_types_| will be updated + // to include those types that didn't fail. + FakeSyncManager(ModelTypeSet initial_sync_ended_types, + ModelTypeSet progress_marker_types, + ModelTypeSet configure_fail_types); virtual ~FakeSyncManager(); - // The set of types that have initial_sync_ended set to true. This value will - // be used by InitialSyncEndedTypes() until the next configuration is - // performed. - void set_initial_sync_ended_types(ModelTypeSet types); - - // The set of types that have valid progress markers. This will be used by - // GetTypesWithEmptyProgressMarkerToken() until the next configuration is - // performed. - void set_progress_marker_types(ModelTypeSet types); - - // The set of types that will fail configuration. Once ConfigureSyncer is - // called, the |initial_sync_ended_types_| and - // |progress_marker_types_| will be updated to include those types - // that didn't fail. - void set_configure_fail_types(ModelTypeSet types); - // Returns those types that have been cleaned (purged from the directory) // since the last call to GetAndResetCleanedTypes(), or since startup if never // called. @@ -63,6 +61,9 @@ class FakeSyncManager : public SyncManager { // Posts a method to disable notifications on the sync thread. void DisableNotifications(NotificationsDisabledReason reason); + // Block until the sync thread has finished processing any pending messages. + void WaitForSyncThread(); + // SyncManager implementation. // Note: we treat whatever message loop this is called from as the sync // loop for purposes of callbacks. @@ -94,9 +95,13 @@ class FakeSyncManager : public SyncManager { virtual bool PurgePartiallySyncedTypes() OVERRIDE; virtual void UpdateCredentials(const SyncCredentials& credentials) OVERRIDE; virtual void UpdateEnabledTypes(const ModelTypeSet& types) OVERRIDE; + virtual void RegisterInvalidationHandler( + SyncNotifierObserver* handler) OVERRIDE; virtual void UpdateRegisteredInvalidationIds( SyncNotifierObserver* handler, const ObjectIdSet& ids) OVERRIDE; + virtual void UnregisterInvalidationHandler( + SyncNotifierObserver* handler) OVERRIDE; virtual void StartSyncingNormally( const ModelSafeRoutingInfo& routing_info) OVERRIDE; virtual void SetEncryptionPassphrase(const std::string& passphrase, @@ -150,7 +155,7 @@ class FakeSyncManager : public SyncManager { ModelTypeSet enabled_types_; // Faked notifier state. - SyncNotifierHelper notifier_helper_; + SyncNotifierRegistrar registrar_; DISALLOW_COPY_AND_ASSIGN(FakeSyncManager); }; diff --git a/sync/internal_api/sync_manager_impl.cc b/sync/internal_api/sync_manager_impl.cc index 72f936f..7e8c17f 100644 --- a/sync/internal_api/sync_manager_impl.cc +++ b/sync/internal_api/sync_manager_impl.cc @@ -396,6 +396,7 @@ bool SyncManagerImpl::Init( change_delegate_ = change_delegate; sync_notifier_ = sync_notifier.Pass(); + sync_notifier_->RegisterHandler(this); AddObserver(&js_sync_manager_observer_); SetJsEventHandler(event_handler); @@ -733,18 +734,34 @@ void SyncManagerImpl::UpdateCredentials( void SyncManagerImpl::UpdateEnabledTypes( const ModelTypeSet& enabled_types) { DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(initialized_); sync_notifier_->UpdateRegisteredIds( this, ModelTypeSetToObjectIdSet(enabled_types)); } +void SyncManagerImpl::RegisterInvalidationHandler( + SyncNotifierObserver* handler) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(initialized_); + sync_notifier_->RegisterHandler(handler); +} + void SyncManagerImpl::UpdateRegisteredInvalidationIds( SyncNotifierObserver* handler, const ObjectIdSet& ids) { DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(initialized_); sync_notifier_->UpdateRegisteredIds(handler, ids); } +void SyncManagerImpl::UnregisterInvalidationHandler( + SyncNotifierObserver* handler) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(initialized_); + sync_notifier_->UnregisterHandler(handler); +} + void SyncManagerImpl::SetEncryptionPassphrase( const std::string& passphrase, bool is_explicit) { @@ -1216,14 +1233,17 @@ void SyncManagerImpl::ShutdownOnSyncThread() { RemoveObserver(&debug_info_event_listener_); - if (sync_notifier_.get()) { - sync_notifier_->UpdateRegisteredIds(this, ObjectIdSet()); - } + // |sync_notifier_| and |connection_manager_| may end up being NULL here in + // tests (in synchronous initialization mode). + // + // TODO(akalin): Fix this behavior. + + if (sync_notifier_.get()) + sync_notifier_->UnregisterHandler(this); sync_notifier_.reset(); - if (connection_manager_.get()) { + if (connection_manager_.get()) connection_manager_->RemoveListener(this); - } connection_manager_.reset(); net::NetworkChangeNotifier::RemoveIPAddressObserver(this); diff --git a/sync/internal_api/sync_manager_impl.h b/sync/internal_api/sync_manager_impl.h index c09a498..501c1cd 100644 --- a/sync/internal_api/sync_manager_impl.h +++ b/sync/internal_api/sync_manager_impl.h @@ -87,9 +87,13 @@ class SyncManagerImpl : public SyncManager, virtual void UpdateCredentials(const SyncCredentials& credentials) OVERRIDE; virtual void UpdateEnabledTypes( const ModelTypeSet& enabled_types) OVERRIDE; + virtual void RegisterInvalidationHandler( + SyncNotifierObserver* handler) OVERRIDE; virtual void UpdateRegisteredInvalidationIds( SyncNotifierObserver* handler, const ObjectIdSet& ids) OVERRIDE; + virtual void UnregisterInvalidationHandler( + SyncNotifierObserver* handler) OVERRIDE; virtual void StartSyncingNormally( const ModelSafeRoutingInfo& routing_info) OVERRIDE; virtual void SetEncryptionPassphrase(const std::string& passphrase, diff --git a/sync/internal_api/sync_manager_impl_unittest.cc b/sync/internal_api/sync_manager_impl_unittest.cc index bd2ffef..841ffd0 100644 --- a/sync/internal_api/sync_manager_impl_unittest.cc +++ b/sync/internal_api/sync_manager_impl_unittest.cc @@ -696,8 +696,10 @@ class SyncManagerObserverMock : public SyncManager::Observer { class SyncNotifierMock : public SyncNotifier { public: + MOCK_METHOD1(RegisterHandler, void(SyncNotifierObserver*)); MOCK_METHOD2(UpdateRegisteredIds, void(SyncNotifierObserver*, const ObjectIdSet&)); + MOCK_METHOD1(UnregisterHandler, void(SyncNotifierObserver*)); MOCK_METHOD1(SetUniqueId, void(const std::string&)); MOCK_METHOD1(SetStateDeprecated, void(const std::string&)); MOCK_METHOD2(UpdateCredentials, @@ -742,6 +744,10 @@ class SyncManagerTest : public testing::Test, EXPECT_CALL(*sync_notifier_mock_, SetStateDeprecated("")); EXPECT_CALL(*sync_notifier_mock_, UpdateCredentials(credentials.email, credentials.sync_token)); + EXPECT_CALL(*sync_notifier_mock_, RegisterHandler(_)); + + // Called by ShutdownOnSyncThread(). + EXPECT_CALL(*sync_notifier_mock_, UnregisterHandler(_)); sync_manager_.AddObserver(&observer_); EXPECT_CALL(observer_, OnInitializationComplete(_, _, _)). @@ -782,7 +788,8 @@ class SyncManagerTest : public testing::Test, void TearDown() { sync_manager_.RemoveObserver(&observer_); - EXPECT_CALL(*sync_notifier_mock_, UpdateRegisteredIds(_, ObjectIdSet())); + // |sync_notifier_mock_| is strict, which ensures we don't do anything but + // unregister |sync_manager_| as a handler on shutdown. sync_manager_.ShutdownOnSyncThread(); sync_notifier_mock_ = NULL; PumpLoop(); @@ -956,18 +963,28 @@ TEST_F(SyncManagerTest, UpdateEnabledTypes) { ModelSafeRoutingInfo routes; GetModelSafeRoutingInfo(&routes); const ModelTypeSet enabled_types = GetRoutingInfoTypes(routes); - EXPECT_CALL(*sync_notifier_mock_, UpdateRegisteredIds( _, ModelTypeSetToObjectIdSet(enabled_types))); + sync_manager_.UpdateEnabledTypes(enabled_types); } +TEST_F(SyncManagerTest, RegisterInvalidationHandler) { + EXPECT_CALL(*sync_notifier_mock_, RegisterHandler(NULL)); + sync_manager_.RegisterInvalidationHandler(NULL); +} + TEST_F(SyncManagerTest, UpdateRegisteredInvalidationIds) { EXPECT_CALL(*sync_notifier_mock_, UpdateRegisteredIds(NULL, ObjectIdSet())); sync_manager_.UpdateRegisteredInvalidationIds(NULL, ObjectIdSet()); } +TEST_F(SyncManagerTest, UnregisterInvalidationHandler) { + EXPECT_CALL(*sync_notifier_mock_, UnregisterHandler(NULL)); + sync_manager_.UnregisterInvalidationHandler(NULL); +} + TEST_F(SyncManagerTest, ProcessJsMessage) { const JsArgList kNoArgs; diff --git a/sync/internal_api/test/fake_sync_manager.cc b/sync/internal_api/test/fake_sync_manager.cc index c9a742d..4028040 100644 --- a/sync/internal_api/test/fake_sync_manager.cc +++ b/sync/internal_api/test/fake_sync_manager.cc @@ -9,6 +9,7 @@ #include "base/bind.h" #include "base/location.h" #include "base/logging.h" +#include "base/run_loop.h" #include "base/sequenced_task_runner.h" #include "base/single_thread_task_runner.h" #include "base/thread_task_runner_handle.h" @@ -21,22 +22,15 @@ namespace syncer { -FakeSyncManager::FakeSyncManager() {} +FakeSyncManager::FakeSyncManager(ModelTypeSet initial_sync_ended_types, + ModelTypeSet progress_marker_types, + ModelTypeSet configure_fail_types) : + initial_sync_ended_types_(initial_sync_ended_types), + progress_marker_types_(progress_marker_types), + configure_fail_types_(configure_fail_types) {} FakeSyncManager::~FakeSyncManager() {} -void FakeSyncManager::set_initial_sync_ended_types(ModelTypeSet types) { - initial_sync_ended_types_ = types; -} - -void FakeSyncManager::set_progress_marker_types(ModelTypeSet types) { - progress_marker_types_ = types; -} - -void FakeSyncManager::set_configure_fail_types(ModelTypeSet types) { - configure_fail_types_ = types; -} - ModelTypeSet FakeSyncManager::GetAndResetCleanedTypes() { ModelTypeSet cleaned_types = cleaned_types_; cleaned_types_.Clear(); @@ -84,6 +78,24 @@ void FakeSyncManager::DisableNotifications( } } +namespace { + +void DoNothing() {} + +} // namespace + +void FakeSyncManager::WaitForSyncThread() { + // Post a task to |sync_task_runner_| and block until it runs. + base::RunLoop run_loop; + if (!sync_task_runner_->PostTaskAndReply( + FROM_HERE, + base::Bind(&DoNothing), + run_loop.QuitClosure())) { + NOTREACHED(); + } + run_loop.Run(); +} + bool FakeSyncManager::Init( const FilePath& database_location, const WeakHandle<JsEventHandler>& event_handler, @@ -149,10 +161,20 @@ void FakeSyncManager::UpdateEnabledTypes(const ModelTypeSet& types) { enabled_types_ = types; } +void FakeSyncManager::RegisterInvalidationHandler( + SyncNotifierObserver* handler) { + registrar_.RegisterHandler(handler); +} + void FakeSyncManager::UpdateRegisteredInvalidationIds( SyncNotifierObserver* handler, const ObjectIdSet& ids) { - notifier_helper_.UpdateRegisteredIds(handler, ids); + registrar_.UpdateRegisteredIds(handler, ids); +} + +void FakeSyncManager::UnregisterInvalidationHandler( + SyncNotifierObserver* handler) { + registrar_.UnregisterHandler(handler); } void FakeSyncManager::StartSyncingNormally( @@ -265,18 +287,18 @@ void FakeSyncManager::InvalidateOnSyncThread( const ObjectIdPayloadMap& id_payloads, IncomingNotificationSource source) { DCHECK(sync_task_runner_->RunsTasksOnCurrentThread()); - notifier_helper_.DispatchInvalidationsToHandlers(id_payloads, source); + registrar_.DispatchInvalidationsToHandlers(id_payloads, source); } void FakeSyncManager::EnableNotificationsOnSyncThread() { DCHECK(sync_task_runner_->RunsTasksOnCurrentThread()); - notifier_helper_.EmitOnNotificationsEnabled(); + registrar_.EmitOnNotificationsEnabled(); } void FakeSyncManager::DisableNotificationsOnSyncThread( NotificationsDisabledReason reason) { DCHECK(sync_task_runner_->RunsTasksOnCurrentThread()); - notifier_helper_.EmitOnNotificationsDisabled(reason); + registrar_.EmitOnNotificationsDisabled(reason); } } // namespace syncer diff --git a/sync/notifier/invalidation_notifier.cc b/sync/notifier/invalidation_notifier.cc index 3a72f32..91a59ad 100644 --- a/sync/notifier/invalidation_notifier.cc +++ b/sync/notifier/invalidation_notifier.cc @@ -34,10 +34,21 @@ InvalidationNotifier::~InvalidationNotifier() { DCHECK(CalledOnValidThread()); } +void InvalidationNotifier::RegisterHandler(SyncNotifierObserver* handler) { + DCHECK(CalledOnValidThread()); + registrar_.RegisterHandler(handler); +} + void InvalidationNotifier::UpdateRegisteredIds(SyncNotifierObserver* handler, const ObjectIdSet& ids) { DCHECK(CalledOnValidThread()); - invalidation_client_.RegisterIds(helper_.UpdateRegisteredIds(handler, ids)); + registrar_.UpdateRegisteredIds(handler, ids); + invalidation_client_.RegisterIds(registrar_.GetAllRegisteredIds()); +} + +void InvalidationNotifier::UnregisterHandler(SyncNotifierObserver* handler) { + DCHECK(CalledOnValidThread()); + registrar_.UnregisterHandler(handler); } void InvalidationNotifier::SetUniqueId(const std::string& unique_id) { @@ -88,18 +99,18 @@ void InvalidationNotifier::SendNotification(ModelTypeSet changed_types) { void InvalidationNotifier::OnInvalidate(const ObjectIdPayloadMap& id_payloads) { DCHECK(CalledOnValidThread()); - helper_.DispatchInvalidationsToHandlers(id_payloads, REMOTE_NOTIFICATION); + registrar_.DispatchInvalidationsToHandlers(id_payloads, REMOTE_NOTIFICATION); } void InvalidationNotifier::OnNotificationsEnabled() { DCHECK(CalledOnValidThread()); - helper_.EmitOnNotificationsEnabled(); + registrar_.EmitOnNotificationsEnabled(); } void InvalidationNotifier::OnNotificationsDisabled( NotificationsDisabledReason reason) { DCHECK(CalledOnValidThread()); - helper_.EmitOnNotificationsDisabled(reason); + registrar_.EmitOnNotificationsDisabled(reason); } } // namespace syncer diff --git a/sync/notifier/invalidation_notifier.h b/sync/notifier/invalidation_notifier.h index 8d74552..6cbe44d 100644 --- a/sync/notifier/invalidation_notifier.h +++ b/sync/notifier/invalidation_notifier.h @@ -23,7 +23,7 @@ #include "sync/notifier/chrome_invalidation_client.h" #include "sync/notifier/invalidation_state_tracker.h" #include "sync/notifier/sync_notifier.h" -#include "sync/notifier/sync_notifier_helper.h" +#include "sync/notifier/sync_notifier_registrar.h" namespace notifier { class PushClient; @@ -49,8 +49,10 @@ class InvalidationNotifier virtual ~InvalidationNotifier(); // SyncNotifier implementation. + virtual void RegisterHandler(SyncNotifierObserver* handler) OVERRIDE; virtual void UpdateRegisteredIds(SyncNotifierObserver* handler, const ObjectIdSet& ids) OVERRIDE; + virtual void UnregisterHandler(SyncNotifierObserver* handler) OVERRIDE; virtual void SetUniqueId(const std::string& unique_id) OVERRIDE; virtual void SetStateDeprecated(const std::string& state) OVERRIDE; virtual void UpdateCredentials( @@ -75,7 +77,7 @@ class InvalidationNotifier }; State state_; - SyncNotifierHelper helper_; + SyncNotifierRegistrar registrar_; // Passed to |invalidation_client_|. const InvalidationVersionMap initial_max_invalidation_versions_; diff --git a/sync/notifier/invalidation_notifier_unittest.cc b/sync/notifier/invalidation_notifier_unittest.cc index 4a07678..5f41428 100644 --- a/sync/notifier/invalidation_notifier_unittest.cc +++ b/sync/notifier/invalidation_notifier_unittest.cc @@ -33,11 +33,10 @@ class InvalidationNotifierTest : public testing::Test { ResetNotifier(); } - // Constructs an InvalidationNotifier, places it in - // |invalidation_notifier_|, and adds |mock_observer_| as an observer. This - // remains in place until either TearDown (automatic) or ResetNotifier - // (manual) is called. - void CreateAndObserveNotifier( + // Constructs an InvalidationNotifier, places it in |invalidation_notifier_|, + // and registers |mock_observer_| as a handler. This remains in place until + // either TearDown (automatic) or ResetNotifier (manual) is called. + void CreateNotifier( const std::string& initial_invalidation_state) { notifier::NotifierOptions notifier_options; // Note: URLRequestContextGetters are ref-counted. @@ -50,10 +49,11 @@ class InvalidationNotifierTest : public testing::Test { initial_invalidation_state, MakeWeakHandle(mock_tracker_.AsWeakPtr()), "fake_client_info")); + invalidation_notifier_->RegisterHandler(&mock_observer_); } void ResetNotifier() { - invalidation_notifier_->UpdateRegisteredIds(&mock_observer_, ObjectIdSet()); + invalidation_notifier_->UnregisterHandler(&mock_observer_); // Stopping the invalidation notifier stops its scheduler, which deletes any // pending tasks without running them. Some tasks "run and delete" another // task, so they must be run in order to avoid leaking the inner task. @@ -71,13 +71,11 @@ class InvalidationNotifierTest : public testing::Test { }; TEST_F(InvalidationNotifierTest, Basic) { - CreateAndObserveNotifier("fake_state"); InSequence dummy; - ModelTypeSet models(PREFERENCES, BOOKMARKS, AUTOFILL); - invalidation_notifier_->UpdateRegisteredIds( - &mock_observer_, ModelTypeSetToObjectIdSet(models)); + CreateNotifier("fake_state"); + const ModelTypeSet models(PREFERENCES, BOOKMARKS, AUTOFILL); const ModelTypePayloadMap& type_payloads = ModelTypePayloadMapFromEnumSet(models, "payload"); EXPECT_CALL(mock_observer_, OnNotificationsEnabled()); @@ -91,6 +89,9 @@ TEST_F(InvalidationNotifierTest, Basic) { // Note no expectation on mock_tracker_, as we initialized with // non-empty initial_invalidation_state above. + invalidation_notifier_->UpdateRegisteredIds( + &mock_observer_, ModelTypeSetToObjectIdSet(models)); + // TODO(tim): This call should be a no-op, Remove once bug 124140 and // associated issues are fixed. invalidation_notifier_->SetStateDeprecated("fake_state"); @@ -109,8 +110,7 @@ TEST_F(InvalidationNotifierTest, Basic) { } TEST_F(InvalidationNotifierTest, MigrateState) { - CreateAndObserveNotifier(std::string()); - InSequence dummy; + CreateNotifier(std::string()); EXPECT_CALL(mock_tracker_, SetInvalidationState("fake_state")); invalidation_notifier_->SetStateDeprecated("fake_state"); @@ -121,7 +121,7 @@ TEST_F(InvalidationNotifierTest, MigrateState) { // Pretend Chrome shut down. ResetNotifier(); - CreateAndObserveNotifier("fake_state"); + CreateNotifier("fake_state"); // Should do nothing. invalidation_notifier_->SetStateDeprecated("more_spurious_fake_state"); } diff --git a/sync/notifier/non_blocking_invalidation_notifier.cc b/sync/notifier/non_blocking_invalidation_notifier.cc index ac0f76a..c561c35 100644 --- a/sync/notifier/non_blocking_invalidation_notifier.cc +++ b/sync/notifier/non_blocking_invalidation_notifier.cc @@ -4,6 +4,8 @@ #include "sync/notifier/non_blocking_invalidation_notifier.h" +#include <cstddef> + #include "base/location.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" @@ -89,12 +91,12 @@ void NonBlockingInvalidationNotifier::Core::Initialize( initial_invalidation_state, invalidation_state_tracker, client_info)); + invalidation_notifier_->RegisterHandler(this); } - void NonBlockingInvalidationNotifier::Core::Teardown() { DCHECK(network_task_runner_->BelongsToCurrentThread()); - invalidation_notifier_->UpdateRegisteredIds(this, ObjectIdSet()); + invalidation_notifier_->UnregisterHandler(this); invalidation_notifier_.reset(); network_task_runner_ = NULL; } @@ -183,21 +185,33 @@ NonBlockingInvalidationNotifier::~NonBlockingInvalidationNotifier() { } } +void NonBlockingInvalidationNotifier::RegisterHandler( + SyncNotifierObserver* handler) { + DCHECK(parent_task_runner_->BelongsToCurrentThread()); + registrar_.RegisterHandler(handler); +} + void NonBlockingInvalidationNotifier::UpdateRegisteredIds( - SyncNotifierObserver* handler, const ObjectIdSet& ids) { + SyncNotifierObserver* handler, + const ObjectIdSet& ids) { DCHECK(parent_task_runner_->BelongsToCurrentThread()); - const ObjectIdSet& all_registered_ids = - helper_.UpdateRegisteredIds(handler, ids); + registrar_.UpdateRegisteredIds(handler, ids); if (!network_task_runner_->PostTask( FROM_HERE, base::Bind( &NonBlockingInvalidationNotifier::Core::UpdateRegisteredIds, core_.get(), - all_registered_ids))) { + registrar_.GetAllRegisteredIds()))) { NOTREACHED(); } } +void NonBlockingInvalidationNotifier::UnregisterHandler( + SyncNotifierObserver* handler) { + DCHECK(parent_task_runner_->BelongsToCurrentThread()); + registrar_.UnregisterHandler(handler); +} + void NonBlockingInvalidationNotifier::SetUniqueId( const std::string& unique_id) { DCHECK(parent_task_runner_->BelongsToCurrentThread()); @@ -241,20 +255,20 @@ void NonBlockingInvalidationNotifier::SendNotification( void NonBlockingInvalidationNotifier::OnNotificationsEnabled() { DCHECK(parent_task_runner_->BelongsToCurrentThread()); - helper_.EmitOnNotificationsEnabled(); + registrar_.EmitOnNotificationsEnabled(); } void NonBlockingInvalidationNotifier::OnNotificationsDisabled( NotificationsDisabledReason reason) { DCHECK(parent_task_runner_->BelongsToCurrentThread()); - helper_.EmitOnNotificationsDisabled(reason); + registrar_.EmitOnNotificationsDisabled(reason); } void NonBlockingInvalidationNotifier::OnIncomingNotification( const ObjectIdPayloadMap& id_payloads, IncomingNotificationSource source) { DCHECK(parent_task_runner_->BelongsToCurrentThread()); - helper_.DispatchInvalidationsToHandlers(id_payloads, source); + registrar_.DispatchInvalidationsToHandlers(id_payloads, source); } } // namespace syncer diff --git a/sync/notifier/non_blocking_invalidation_notifier.h b/sync/notifier/non_blocking_invalidation_notifier.h index f76b37b..9577297 100644 --- a/sync/notifier/non_blocking_invalidation_notifier.h +++ b/sync/notifier/non_blocking_invalidation_notifier.h @@ -18,8 +18,8 @@ #include "sync/internal_api/public/util/weak_handle.h" #include "sync/notifier/invalidation_state_tracker.h" #include "sync/notifier/sync_notifier.h" -#include "sync/notifier/sync_notifier_helper.h" #include "sync/notifier/sync_notifier_observer.h" +#include "sync/notifier/sync_notifier_registrar.h" namespace base { class SingleThreadTaskRunner; @@ -27,6 +27,8 @@ class SingleThreadTaskRunner; namespace syncer { +// TODO(akalin): Generalize to NonBlockingSyncNotifier +// (http://crbug.com/140409). class NonBlockingInvalidationNotifier : public SyncNotifier, // SyncNotifierObserver to "observe" our Core via WeakHandle. @@ -44,8 +46,10 @@ class NonBlockingInvalidationNotifier virtual ~NonBlockingInvalidationNotifier(); // SyncNotifier implementation. + virtual void RegisterHandler(SyncNotifierObserver* handler) OVERRIDE; virtual void UpdateRegisteredIds(SyncNotifierObserver* handler, const ObjectIdSet& ids) OVERRIDE; + virtual void UnregisterHandler(SyncNotifierObserver* handler) OVERRIDE; virtual void SetUniqueId(const std::string& unique_id) OVERRIDE; virtual void SetStateDeprecated(const std::string& state) OVERRIDE; virtual void UpdateCredentials( @@ -65,7 +69,7 @@ class NonBlockingInvalidationNotifier base::WeakPtrFactory<NonBlockingInvalidationNotifier> weak_ptr_factory_; - SyncNotifierHelper helper_; + SyncNotifierRegistrar registrar_; // The real guts of NonBlockingInvalidationNotifier, which allows // this class to live completely on the parent thread. diff --git a/sync/notifier/non_blocking_invalidation_notifier_unittest.cc b/sync/notifier/non_blocking_invalidation_notifier_unittest.cc index 0cadd4a..f237cdb 100644 --- a/sync/notifier/non_blocking_invalidation_notifier_unittest.cc +++ b/sync/notifier/non_blocking_invalidation_notifier_unittest.cc @@ -46,10 +46,11 @@ class NonBlockingInvalidationNotifierTest : public testing::Test { std::string(), // initial_invalidation_state MakeWeakHandle(base::WeakPtr<InvalidationStateTracker>()), "fake_client_info")); + invalidation_notifier_->RegisterHandler(&mock_observer_); } virtual void TearDown() { - invalidation_notifier_->UpdateRegisteredIds(&mock_observer_, ObjectIdSet()); + invalidation_notifier_->UnregisterHandler(&mock_observer_); invalidation_notifier_.reset(); request_context_getter_ = NULL; io_thread_.Stop(); @@ -64,13 +65,12 @@ class NonBlockingInvalidationNotifierTest : public testing::Test { notifier::FakeBaseTask fake_base_task_; }; +// TODO(akalin): Add real unit tests (http://crbug.com/140410). + TEST_F(NonBlockingInvalidationNotifierTest, Basic) { InSequence dummy; - ModelTypeSet models(PREFERENCES, BOOKMARKS, AUTOFILL); - invalidation_notifier_->UpdateRegisteredIds( - &mock_observer_, ModelTypeSetToObjectIdSet(models)); - + const ModelTypeSet models(PREFERENCES, BOOKMARKS, AUTOFILL); const ModelTypePayloadMap& type_payloads = ModelTypePayloadMapFromEnumSet(models, "payload"); EXPECT_CALL(mock_observer_, OnNotificationsEnabled()); @@ -82,6 +82,9 @@ TEST_F(NonBlockingInvalidationNotifierTest, Basic) { EXPECT_CALL(mock_observer_, OnNotificationsDisabled(NOTIFICATION_CREDENTIALS_REJECTED)); + invalidation_notifier_->UpdateRegisteredIds( + &mock_observer_, ModelTypeSetToObjectIdSet(models)); + invalidation_notifier_->SetStateDeprecated("fake_state"); invalidation_notifier_->SetUniqueId("fake_id"); invalidation_notifier_->UpdateCredentials("foo@bar.com", "fake_token"); diff --git a/sync/notifier/p2p_notifier.cc b/sync/notifier/p2p_notifier.cc index 5d5aeff..dab7221 100644 --- a/sync/notifier/p2p_notifier.cc +++ b/sync/notifier/p2p_notifier.cc @@ -157,10 +157,18 @@ P2PNotifier::~P2PNotifier() { push_client_->RemoveObserver(this); } +void P2PNotifier::RegisterHandler(SyncNotifierObserver* handler) { + DCHECK(thread_checker_.CalledOnValidThread()); + registrar_.RegisterHandler(handler); +} + void P2PNotifier::UpdateRegisteredIds(SyncNotifierObserver* handler, const ObjectIdSet& ids) { - const ModelTypeSet enabled_types = ObjectIdSetToModelTypeSet( - helper_.UpdateRegisteredIds(handler, ids)); + // TODO(akalin): Handle arbitrary object IDs (http://crbug.com/140411). + DCHECK(thread_checker_.CalledOnValidThread()); + registrar_.UpdateRegisteredIds(handler, ids); + const ModelTypeSet enabled_types = + ObjectIdSetToModelTypeSet(registrar_.GetAllRegisteredIds()); const ModelTypeSet new_enabled_types = Difference(enabled_types, enabled_types_); const P2PNotificationData notification_data( @@ -169,6 +177,11 @@ void P2PNotifier::UpdateRegisteredIds(SyncNotifierObserver* handler, enabled_types_ = enabled_types; } +void P2PNotifier::UnregisterHandler(SyncNotifierObserver* handler) { + DCHECK(thread_checker_.CalledOnValidThread()); + registrar_.UnregisterHandler(handler); +} + void P2PNotifier::SetUniqueId(const std::string& unique_id) { DCHECK(thread_checker_.CalledOnValidThread()); unique_id_ = unique_id; @@ -207,7 +220,7 @@ void P2PNotifier::OnNotificationsEnabled() { DCHECK(thread_checker_.CalledOnValidThread()); bool just_turned_on = (notifications_enabled_ == false); notifications_enabled_ = true; - helper_.EmitOnNotificationsEnabled(); + registrar_.EmitOnNotificationsEnabled(); if (just_turned_on) { const P2PNotificationData notification_data( unique_id_, NOTIFY_SELF, enabled_types_); @@ -218,7 +231,7 @@ void P2PNotifier::OnNotificationsEnabled() { void P2PNotifier::OnNotificationsDisabled( notifier::NotificationsDisabledReason reason) { DCHECK(thread_checker_.CalledOnValidThread()); - helper_.EmitOnNotificationsDisabled(FromNotifierReason(reason)); + registrar_.EmitOnNotificationsDisabled(FromNotifierReason(reason)); } void P2PNotifier::OnIncomingNotification( @@ -257,7 +270,7 @@ void P2PNotifier::OnIncomingNotification( } const ModelTypePayloadMap& type_payloads = ModelTypePayloadMapFromEnumSet( notification_data.GetChangedTypes(), std::string()); - helper_.DispatchInvalidationsToHandlers( + registrar_.DispatchInvalidationsToHandlers( ModelTypePayloadMapToObjectIdPayloadMap(type_payloads), REMOTE_NOTIFICATION); } diff --git a/sync/notifier/p2p_notifier.h b/sync/notifier/p2p_notifier.h index d2de89d..4457c6f 100644 --- a/sync/notifier/p2p_notifier.h +++ b/sync/notifier/p2p_notifier.h @@ -20,7 +20,7 @@ #include "sync/internal_api/public/base/model_type.h" #include "sync/notifier/notifications_disabled_reason.h" #include "sync/notifier/sync_notifier.h" -#include "sync/notifier/sync_notifier_helper.h" +#include "sync/notifier/sync_notifier_registrar.h" namespace notifier { class PushClient; @@ -96,8 +96,10 @@ class P2PNotifier : public SyncNotifier, virtual ~P2PNotifier(); // SyncNotifier implementation + virtual void RegisterHandler(SyncNotifierObserver* handler) OVERRIDE; virtual void UpdateRegisteredIds(SyncNotifierObserver* handler, const ObjectIdSet& ids) OVERRIDE; + virtual void UnregisterHandler(SyncNotifierObserver* handler) OVERRIDE; virtual void SetUniqueId(const std::string& unique_id) OVERRIDE; virtual void SetStateDeprecated(const std::string& state) OVERRIDE; virtual void UpdateCredentials( @@ -119,7 +121,7 @@ class P2PNotifier : public SyncNotifier, base::ThreadChecker thread_checker_; - SyncNotifierHelper helper_; + SyncNotifierRegistrar registrar_; // The push client. scoped_ptr<notifier::PushClient> push_client_; diff --git a/sync/notifier/p2p_notifier_unittest.cc b/sync/notifier/p2p_notifier_unittest.cc index 28a73c9..97033ea 100644 --- a/sync/notifier/p2p_notifier_unittest.cc +++ b/sync/notifier/p2p_notifier_unittest.cc @@ -28,10 +28,11 @@ class P2PNotifierTest : public testing::Test { scoped_ptr<notifier::PushClient>(fake_push_client_), NOTIFY_OTHERS), next_sent_notification_to_reflect_(0) { + p2p_notifier_.RegisterHandler(&mock_observer_); } virtual ~P2PNotifierTest() { - p2p_notifier_.UpdateRegisteredIds(&mock_observer_, ObjectIdSet()); + p2p_notifier_.UnregisterHandler(&mock_observer_); } ModelTypePayloadMap MakePayloadMap(ModelTypeSet types) { @@ -140,16 +141,16 @@ TEST_F(P2PNotifierTest, P2PNotificationDataNonDefault) { // observer should receive only a notification from the call to // UpdateEnabledTypes(). TEST_F(P2PNotifierTest, NotificationsBasic) { - ModelTypeSet enabled_types(BOOKMARKS, PREFERENCES); - - p2p_notifier_.UpdateRegisteredIds(&mock_observer_, - ModelTypeSetToObjectIdSet(enabled_types)); + const ModelTypeSet enabled_types(BOOKMARKS, PREFERENCES); EXPECT_CALL(mock_observer_, OnNotificationsEnabled()); EXPECT_CALL(mock_observer_, OnIncomingNotification( ModelTypePayloadMapToObjectIdPayloadMap(MakePayloadMap(enabled_types)), REMOTE_NOTIFICATION)); + p2p_notifier_.UpdateRegisteredIds(&mock_observer_, + ModelTypeSetToObjectIdSet(enabled_types)); + p2p_notifier_.SetUniqueId("sender"); const char kEmail[] = "foo@bar.com"; @@ -183,15 +184,9 @@ TEST_F(P2PNotifierTest, NotificationsBasic) { // target settings. The notifications received by the observer should // be consistent with the target settings. TEST_F(P2PNotifierTest, SendNotificationData) { - ModelTypeSet enabled_types(BOOKMARKS, PREFERENCES, THEMES); - ModelTypeSet changed_types(THEMES, APPS); - ModelTypeSet expected_types(THEMES); - - p2p_notifier_.UpdateRegisteredIds(&mock_observer_, - ModelTypeSetToObjectIdSet(enabled_types)); - - const ModelTypePayloadMap& expected_payload_map = - MakePayloadMap(expected_types); + const ModelTypeSet enabled_types(BOOKMARKS, PREFERENCES, THEMES); + const ModelTypeSet changed_types(THEMES, APPS); + const ModelTypeSet expected_types(THEMES); EXPECT_CALL(mock_observer_, OnNotificationsEnabled()); EXPECT_CALL(mock_observer_, @@ -200,6 +195,12 @@ TEST_F(P2PNotifierTest, SendNotificationData) { MakePayloadMap(enabled_types)), REMOTE_NOTIFICATION)); + p2p_notifier_.UpdateRegisteredIds(&mock_observer_, + ModelTypeSetToObjectIdSet(enabled_types)); + + const ModelTypePayloadMap& expected_payload_map = + MakePayloadMap(expected_types); + p2p_notifier_.SetUniqueId("sender"); p2p_notifier_.UpdateCredentials("foo@bar.com", "fake_token"); diff --git a/sync/notifier/sync_notifier.h b/sync/notifier/sync_notifier.h index 80b97b8..9191b90 100644 --- a/sync/notifier/sync_notifier.h +++ b/sync/notifier/sync_notifier.h @@ -22,12 +22,46 @@ class SyncNotifier { SyncNotifier() {} virtual ~SyncNotifier() {} - // Updates the set of ObjectIds associated with a given |handler|. - // Passing an empty ObjectIdSet will unregister |handler|. - // There should be at most one handler registered per object id. + // Clients should follow the pattern below: + // + // When starting the client: + // + // notifier->RegisterHandler(client_handler); + // + // When the set of IDs to register changes for the client during its lifetime + // (i.e., between calls to RegisterHandler(client_handler) and + // UnregisterHandler(client_handler): + // + // notifier->UpdateRegisteredIds(client_handler, client_ids); + // + // When shutting down the client for browser shutdown: + // + // notifier->UnregisterHandler(client_handler); + // + // Note that there's no call to UpdateRegisteredIds() -- this is because the + // invalidation API persists registrations across browser restarts. + // + // When permanently shutting down the client, e.g. when disabling the related + // feature: + // + // notifier->UpdateRegisteredIds(client_handler, ObjectIdSet()); + // notifier->UnregisterHandler(client_handler); + + // Starts sending notifications to |handler|. |handler| must not be NULL, + // and it must already be registered. + virtual void RegisterHandler(SyncNotifierObserver* handler) = 0; + + // Updates the set of ObjectIds associated with |handler|. |handler| must + // not be NULL, and must already be registered. An ID must be registered for + // at most one handler. virtual void UpdateRegisteredIds(SyncNotifierObserver* handler, const ObjectIdSet& ids) = 0; + // Stops sending notifications to |handler|. |handler| must not be NULL, and + // it must already be registered. Note that this doesn't unregister the IDs + // associated with |handler|. + virtual void UnregisterHandler(SyncNotifierObserver* handler) = 0; + // SetUniqueId must be called once, before any call to // UpdateCredentials. |unique_id| should be a non-empty globally // unique string. diff --git a/sync/notifier/sync_notifier_factory_unittest.cc b/sync/notifier/sync_notifier_factory_unittest.cc index 8036c1a..50d923b 100644 --- a/sync/notifier/sync_notifier_factory_unittest.cc +++ b/sync/notifier/sync_notifier_factory_unittest.cc @@ -61,8 +61,9 @@ TEST_F(SyncNotifierFactoryTest, Basic) { #else ASSERT_TRUE(notifier.get()); ObjectIdSet ids = ModelTypeSetToObjectIdSet(ModelTypeSet(syncer::BOOKMARKS)); + notifier->RegisterHandler(&mock_observer_); notifier->UpdateRegisteredIds(&mock_observer_, ids); - notifier->UpdateRegisteredIds(&mock_observer_, ObjectIdSet()); + notifier->UnregisterHandler(&mock_observer_); #endif } @@ -79,8 +80,9 @@ TEST_F(SyncNotifierFactoryTest, Basic_P2P) { #else ASSERT_TRUE(notifier.get()); ObjectIdSet ids = ModelTypeSetToObjectIdSet(ModelTypeSet(syncer::BOOKMARKS)); + notifier->RegisterHandler(&mock_observer_); notifier->UpdateRegisteredIds(&mock_observer_, ids); - notifier->UpdateRegisteredIds(&mock_observer_, ObjectIdSet()); + notifier->UnregisterHandler(&mock_observer_); #endif } diff --git a/sync/notifier/sync_notifier_helper.cc b/sync/notifier/sync_notifier_helper.cc deleted file mode 100644 index af3b2ea..0000000 --- a/sync/notifier/sync_notifier_helper.cc +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) 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. - -#include "sync/notifier/sync_notifier_helper.h" - -#include "base/logging.h" - -namespace syncer { - -SyncNotifierHelper::SyncNotifierHelper() {} -SyncNotifierHelper::~SyncNotifierHelper() {} - -ObjectIdSet SyncNotifierHelper::UpdateRegisteredIds( - SyncNotifierObserver* handler, const ObjectIdSet& ids) { - if (ids.empty()) { - handlers_.RemoveObserver(handler); - } else if (!handlers_.HasObserver(handler)) { - handlers_.AddObserver(handler); - } - - ObjectIdSet registered_ids(ids); - // Remove all existing entries for |handler| and fill |registered_ids| with - // the rest. - for (ObjectIdHandlerMap::iterator it = id_to_handler_map_.begin(); - it != id_to_handler_map_.end(); ) { - if (it->second == handler) { - ObjectIdHandlerMap::iterator erase_it = it; - ++it; - id_to_handler_map_.erase(erase_it); - } else { - registered_ids.insert(it->first); - ++it; - } - } - - // Now add the entries for |handler|. We keep track of the last insertion - // point so we only traverse the map once to insert all the new entries. - ObjectIdHandlerMap::iterator insert_it = id_to_handler_map_.begin(); - for (ObjectIdSet::const_iterator it = ids.begin(); it != ids.end(); ++it) { - insert_it = id_to_handler_map_.insert(insert_it, - std::make_pair(*it, handler)); - CHECK_EQ(handler, insert_it->second) << "Duplicate registration for " - << ObjectIdToString(insert_it->first); - } - if (logging::DEBUG_MODE) { - // The mapping shouldn't contain any handlers that aren't in |handlers_|. - for (ObjectIdHandlerMap::const_iterator it = id_to_handler_map_.begin(); - it != id_to_handler_map_.end(); ++it) { - CHECK(handlers_.HasObserver(it->second)); - } - } - return registered_ids; -} - -void SyncNotifierHelper::DispatchInvalidationsToHandlers( - const ObjectIdPayloadMap& id_payloads, - IncomingNotificationSource source) { - typedef std::map<SyncNotifierObserver*, ObjectIdPayloadMap> DispatchMap; - DispatchMap dispatch_map; - for (ObjectIdPayloadMap::const_iterator it = id_payloads.begin(); - it != id_payloads.end(); ++it) { - ObjectIdHandlerMap::const_iterator find_it = - id_to_handler_map_.find(it->first); - // If we get an invalidation with a source type that we can't map to an - // observer, just drop it--the observer was unregistered while the - // invalidation was in flight. - if (find_it == id_to_handler_map_.end()) - continue; - dispatch_map[find_it->second].insert(*it); - } - - if (handlers_.might_have_observers()) { - ObserverListBase<SyncNotifierObserver>::Iterator it(handlers_); - SyncNotifierObserver* handler = NULL; - while ((handler = it.GetNext()) != NULL) { - DispatchMap::const_iterator dispatch_it = dispatch_map.find(handler); - if (dispatch_it != dispatch_map.end()) { - handler->OnIncomingNotification(dispatch_it->second, source); - } - } - } -} - -void SyncNotifierHelper::EmitOnNotificationsEnabled() { - FOR_EACH_OBSERVER(SyncNotifierObserver, handlers_, OnNotificationsEnabled()); -} - -void SyncNotifierHelper::EmitOnNotificationsDisabled( - NotificationsDisabledReason reason) { - FOR_EACH_OBSERVER(SyncNotifierObserver, handlers_, - OnNotificationsDisabled(reason)); -} - -} // namespace syncer diff --git a/sync/notifier/sync_notifier_helper.h b/sync/notifier/sync_notifier_helper.h deleted file mode 100644 index 28ff187..0000000 --- a/sync/notifier/sync_notifier_helper.h +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) 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_NOTIFIER_SYNC_NOTIFIER_HELPER_H_ -#define SYNC_NOTIFIER_SYNC_NOTIFIER_HELPER_H_ - -#include <map> - -#include "base/basictypes.h" -#include "base/observer_list.h" -#include "sync/notifier/invalidation_util.h" -#include "sync/notifier/object_id_payload_map.h" -#include "sync/notifier/sync_notifier_observer.h" - -namespace syncer { - -// A helper class for classes that want to implement the SyncNotifier interface. -// It helps keep track of registered handlers and which object ID registrations -// are associated with which handlers, so implementors can just reuse the logic -// here to dispatch invalidations and other interesting notifications. -class SyncNotifierHelper { - public: - SyncNotifierHelper(); - ~SyncNotifierHelper(); - - // Updates the set of ObjectIds associated with a given |handler|. Passing an - // empty ObjectIdSet will unregister |handler|. The return value is an - // ObjectIdSet containing values for all existing handlers. - ObjectIdSet UpdateRegisteredIds(SyncNotifierObserver* handler, - const ObjectIdSet& ids); - - // Helper that sorts incoming invalidations into a bucket for each handler - // and then dispatches the batched invalidations to the corresponding handler. - void DispatchInvalidationsToHandlers(const ObjectIdPayloadMap& id_payloads, - IncomingNotificationSource source); - - // Calls the given handler method for each handler that has registered IDs. - void EmitOnNotificationsEnabled(); - void EmitOnNotificationsDisabled(NotificationsDisabledReason reason); - - private: - typedef std::map<invalidation::ObjectId, - SyncNotifierObserver*, - ObjectIdLessThan> ObjectIdHandlerMap; - - ObserverList<SyncNotifierObserver> handlers_; - ObjectIdHandlerMap id_to_handler_map_; - - DISALLOW_COPY_AND_ASSIGN(SyncNotifierHelper); -}; - -} // namespace syncer - -#endif // SYNC_NOTIFIER_SYNC_NOTIFIER_HELPER_H_ diff --git a/sync/notifier/sync_notifier_helper_unittest.cc b/sync/notifier/sync_notifier_helper_unittest.cc deleted file mode 100644 index 24d8871..0000000 --- a/sync/notifier/sync_notifier_helper_unittest.cc +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright (c) 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. - -#include "google/cacheinvalidation/types.pb.h" -#include "sync/notifier/sync_notifier_helper.h" -#include "sync/notifier/mock_sync_notifier_observer.h" -#include "testing/gtest/include/gtest/gtest.h" - -using testing::StrictMock; - -namespace syncer { - -class SyncNotifierHelperTest : public testing::Test { - protected: - SyncNotifierHelperTest() - : kObjectId1(ipc::invalidation::ObjectSource::TEST, "a"), - kObjectId2(ipc::invalidation::ObjectSource::TEST, "b"), - kObjectId3(ipc::invalidation::ObjectSource::TEST, "c") { - } - - invalidation::ObjectId kObjectId1; - invalidation::ObjectId kObjectId2; - invalidation::ObjectId kObjectId3; -}; - -// Basic check that registrations are correctly updated for one handler. -TEST_F(SyncNotifierHelperTest, Basic) { - SyncNotifierHelper helper; - StrictMock<MockSyncNotifierObserver> observer; - ObjectIdSet ids; - ids.insert(kObjectId1); - ids.insert(kObjectId2); - helper.UpdateRegisteredIds(&observer, ids); - - ObjectIdPayloadMap dispatched_payloads; - dispatched_payloads[kObjectId1] = "1"; - dispatched_payloads[kObjectId2] = "2"; - dispatched_payloads[kObjectId3] = "3"; - - // A object ID with no registration should be ignored. - ObjectIdPayloadMap expected_payload1; - expected_payload1[kObjectId1] = "1"; - expected_payload1[kObjectId2] = "2"; - EXPECT_CALL(observer, OnIncomingNotification(expected_payload1, - REMOTE_NOTIFICATION)); - helper.DispatchInvalidationsToHandlers(dispatched_payloads, - REMOTE_NOTIFICATION); - - // Removed object IDs should not be notified, newly-added ones should. - ids.erase(kObjectId1); - ids.insert(kObjectId3); - helper.UpdateRegisteredIds(&observer, ids); - - ObjectIdPayloadMap expected_payload2; - expected_payload2[kObjectId2] = "2"; - expected_payload2[kObjectId3] = "3"; - EXPECT_CALL(observer, OnIncomingNotification(expected_payload2, - REMOTE_NOTIFICATION)); - helper.DispatchInvalidationsToHandlers(dispatched_payloads, - REMOTE_NOTIFICATION); -} - -// Tests that we correctly bucket and dispatch invalidations on multiple objects -// to the corresponding handlers. -TEST_F(SyncNotifierHelperTest, MultipleHandlers) { - SyncNotifierHelper helper; - StrictMock<MockSyncNotifierObserver> observer; - ObjectIdSet ids; - ids.insert(kObjectId1); - ids.insert(kObjectId2); - helper.UpdateRegisteredIds(&observer, ids); - StrictMock<MockSyncNotifierObserver> observer2; - ObjectIdSet ids2; - ids2.insert(kObjectId3); - helper.UpdateRegisteredIds(&observer2, ids2); - - ObjectIdPayloadMap expected_payload1; - expected_payload1[kObjectId1] = "1"; - expected_payload1[kObjectId2] = "2"; - EXPECT_CALL(observer, OnIncomingNotification(expected_payload1, - REMOTE_NOTIFICATION)); - ObjectIdPayloadMap expected_payload2; - expected_payload2[kObjectId3] = "3"; - EXPECT_CALL(observer2, OnIncomingNotification(expected_payload2, - REMOTE_NOTIFICATION)); - - ObjectIdPayloadMap dispatched_payloads; - dispatched_payloads[kObjectId1] = "1"; - dispatched_payloads[kObjectId2] = "2"; - dispatched_payloads[kObjectId3] = "3"; - helper.DispatchInvalidationsToHandlers(dispatched_payloads, - REMOTE_NOTIFICATION); - - // Also verify that the callbacks for OnNotificationsEnabled/Disabled work. - EXPECT_CALL(observer, OnNotificationsEnabled()); - EXPECT_CALL(observer2, OnNotificationsEnabled()); - EXPECT_CALL(observer, - OnNotificationsDisabled(TRANSIENT_NOTIFICATION_ERROR)); - EXPECT_CALL(observer2, - OnNotificationsDisabled(TRANSIENT_NOTIFICATION_ERROR)); - helper.EmitOnNotificationsEnabled(); - helper.EmitOnNotificationsDisabled(TRANSIENT_NOTIFICATION_ERROR); -} - -// Multiple registrations by different handlers on the same object ID should -// cause a CHECK. -TEST_F(SyncNotifierHelperTest, MultipleRegistration) { - SyncNotifierHelper helper; - StrictMock<MockSyncNotifierObserver> observer; - ObjectIdSet ids; - ids.insert(kObjectId1); - ids.insert(kObjectId2); - helper.UpdateRegisteredIds(&observer, ids); - - StrictMock<MockSyncNotifierObserver> observer2; - EXPECT_DEATH({ helper.UpdateRegisteredIds(&observer2, ids); }, - "Duplicate registration for .*"); -} - -// Make sure that passing an empty set to UpdateRegisteredIds clears the -// corresponding entries for the handler. -TEST_F(SyncNotifierHelperTest, EmptySetUnregisters) { - SyncNotifierHelper helper; - StrictMock<MockSyncNotifierObserver> observer; - ObjectIdSet ids; - ids.insert(kObjectId1); - ids.insert(kObjectId2); - helper.UpdateRegisteredIds(&observer, ids); - // Control observer. - StrictMock<MockSyncNotifierObserver> observer2; - ObjectIdSet ids2; - ids2.insert(kObjectId3); - helper.UpdateRegisteredIds(&observer2, ids2); - // Unregister the first observer. It should not receive any further callbacks. - helper.UpdateRegisteredIds(&observer, ObjectIdSet()); - - ObjectIdPayloadMap expected_payload2; - expected_payload2[kObjectId3] = "3"; - EXPECT_CALL(observer2, OnIncomingNotification(expected_payload2, - REMOTE_NOTIFICATION)); - EXPECT_CALL(observer2, OnNotificationsEnabled()); - EXPECT_CALL(observer2, - OnNotificationsDisabled(TRANSIENT_NOTIFICATION_ERROR)); - - ObjectIdPayloadMap dispatched_payloads; - dispatched_payloads[kObjectId1] = "1"; - dispatched_payloads[kObjectId2] = "2"; - dispatched_payloads[kObjectId3] = "3"; - helper.DispatchInvalidationsToHandlers(dispatched_payloads, - REMOTE_NOTIFICATION); - helper.EmitOnNotificationsEnabled(); - helper.EmitOnNotificationsDisabled(TRANSIENT_NOTIFICATION_ERROR); -} - -} // namespace syncer diff --git a/sync/notifier/sync_notifier_registrar.cc b/sync/notifier/sync_notifier_registrar.cc new file mode 100644 index 0000000..02c00b6 --- /dev/null +++ b/sync/notifier/sync_notifier_registrar.cc @@ -0,0 +1,129 @@ +// Copyright (c) 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. + +#include "sync/notifier/sync_notifier_registrar.h" + +#include <cstddef> +#include <utility> + +#include "base/logging.h" + +namespace syncer { + +SyncNotifierRegistrar::SyncNotifierRegistrar() {} + +SyncNotifierRegistrar::~SyncNotifierRegistrar() { + DCHECK(thread_checker_.CalledOnValidThread()); +} + +void SyncNotifierRegistrar::RegisterHandler(SyncNotifierObserver* handler) { + DCHECK(thread_checker_.CalledOnValidThread()); + CHECK(handler); + CHECK(!handlers_.HasObserver(handler)); + handlers_.AddObserver(handler); +} + +void SyncNotifierRegistrar::UpdateRegisteredIds( + SyncNotifierObserver* handler, + const ObjectIdSet& ids) { + DCHECK(thread_checker_.CalledOnValidThread()); + CHECK(handler); + CHECK(handlers_.HasObserver(handler)); + // Remove all existing entries for |handler|. + for (IdHandlerMap::iterator it = id_to_handler_map_.begin(); + it != id_to_handler_map_.end(); ) { + if (it->second == handler) { + IdHandlerMap::iterator erase_it = it; + ++it; + id_to_handler_map_.erase(erase_it); + } else { + ++it; + } + } + + // Now add the entries for |handler|. We keep track of the last insertion + // point so we only traverse the map once to insert all the new entries. + IdHandlerMap::iterator insert_it = id_to_handler_map_.begin(); + for (ObjectIdSet::const_iterator it = ids.begin(); it != ids.end(); ++it) { + insert_it = + id_to_handler_map_.insert(insert_it, std::make_pair(*it, handler)); + CHECK_EQ(handler, insert_it->second) + << "Duplicate registration: trying to register " + << ObjectIdToString(insert_it->first) << " for " + << handler << " when it's already registered for " + << insert_it->second; + } +} + +void SyncNotifierRegistrar::UnregisterHandler(SyncNotifierObserver* handler) { + DCHECK(thread_checker_.CalledOnValidThread()); + CHECK(handler); + CHECK(handlers_.HasObserver(handler)); + handlers_.RemoveObserver(handler); +} + +ObjectIdSet SyncNotifierRegistrar::GetAllRegisteredIds() const { + DCHECK(thread_checker_.CalledOnValidThread()); + ObjectIdSet registered_ids; + for (IdHandlerMap::const_iterator it = id_to_handler_map_.begin(); + it != id_to_handler_map_.end(); ++it) { + registered_ids.insert(it->first); + } + return registered_ids; +} + +void SyncNotifierRegistrar::DispatchInvalidationsToHandlers( + const ObjectIdPayloadMap& id_payloads, + IncomingNotificationSource source) { + DCHECK(thread_checker_.CalledOnValidThread()); + // If we have no handlers, there's nothing to do. + if (!handlers_.might_have_observers()) { + return; + } + + typedef std::map<SyncNotifierObserver*, ObjectIdPayloadMap> DispatchMap; + DispatchMap dispatch_map; + for (ObjectIdPayloadMap::const_iterator it = id_payloads.begin(); + it != id_payloads.end(); ++it) { + SyncNotifierObserver* const handler = ObjectIdToHandler(it->first); + // Filter out invalidations for IDs with no handler. + if (handler) + dispatch_map[handler].insert(*it); + } + + // Emit invalidations only for handlers in |handlers_|. + ObserverListBase<SyncNotifierObserver>::Iterator it(handlers_); + SyncNotifierObserver* handler = NULL; + while ((handler = it.GetNext()) != NULL) { + DispatchMap::const_iterator dispatch_it = dispatch_map.find(handler); + if (dispatch_it != dispatch_map.end()) + handler->OnIncomingNotification(dispatch_it->second, source); + } +} + +void SyncNotifierRegistrar::EmitOnNotificationsEnabled() { + DCHECK(thread_checker_.CalledOnValidThread()); + FOR_EACH_OBSERVER(SyncNotifierObserver, handlers_, OnNotificationsEnabled()); +} + +void SyncNotifierRegistrar::EmitOnNotificationsDisabled( + NotificationsDisabledReason reason) { + DCHECK(thread_checker_.CalledOnValidThread()); + FOR_EACH_OBSERVER(SyncNotifierObserver, handlers_, + OnNotificationsDisabled(reason)); +} + +void SyncNotifierRegistrar::DetachFromThreadForTest() { + DCHECK(thread_checker_.CalledOnValidThread()); + thread_checker_.DetachFromThread(); +} + +SyncNotifierObserver* SyncNotifierRegistrar::ObjectIdToHandler( + const invalidation::ObjectId& id) { + DCHECK(thread_checker_.CalledOnValidThread()); + IdHandlerMap::const_iterator it = id_to_handler_map_.find(id); + return (it == id_to_handler_map_.end()) ? NULL : it->second; +} + +} // namespace syncer diff --git a/sync/notifier/sync_notifier_registrar.h b/sync/notifier/sync_notifier_registrar.h new file mode 100644 index 0000000..949e3ca --- /dev/null +++ b/sync/notifier/sync_notifier_registrar.h @@ -0,0 +1,82 @@ +// Copyright (c) 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_NOTIFIER_SYNC_NOTIFIER_REGISTRAR_H_ +#define SYNC_NOTIFIER_SYNC_NOTIFIER_REGISTRAR_H_ + +#include <map> + +#include "base/basictypes.h" +#include "base/observer_list.h" +#include "base/threading/thread_checker.h" +#include "sync/notifier/invalidation_util.h" +#include "sync/notifier/object_id_payload_map.h" +#include "sync/notifier/sync_notifier_observer.h" + +namespace invalidation { +class ObjectId; +} // namespace invalidation + +namespace syncer { + +// A helper class for implementations of the SyncNotifier interface. It helps +// keep track of registered handlers and which object ID registrations are +// associated with which handlers, so implementors can just reuse the logic +// here to dispatch invalidations and other interesting notifications. +class SyncNotifierRegistrar { + public: + SyncNotifierRegistrar(); + ~SyncNotifierRegistrar(); + + // Starts sending notifications to |handler|. |handler| must not be NULL, + // and it must already be registered. + void RegisterHandler(SyncNotifierObserver* handler); + + + // Updates the set of ObjectIds associated with |handler|. |handler| must + // not be NULL, and must already be registered. An ID must be registered for + // at most one handler. + void UpdateRegisteredIds(SyncNotifierObserver* handler, + const ObjectIdSet& ids); + + // Stops sending notifications to |handler|. |handler| must not be NULL, and + // it must already be registered. Note that this doesn't unregister the IDs + // associated with |handler|. + void UnregisterHandler(SyncNotifierObserver* handler); + + // Returns the set of all IDs that are registered to some handler (even + // handlers that have been unregistered). + ObjectIdSet GetAllRegisteredIds() const; + + // Sorts incoming invalidations into a bucket for each handler and then + // dispatches the batched invalidations to the corresponding handler. + // Invalidations for IDs with no corresponding handler are dropped, as are + // invalidations for handlers that are not added. + void DispatchInvalidationsToHandlers(const ObjectIdPayloadMap& id_payloads, + IncomingNotificationSource source); + + // Calls the given handler method for each handler that has registered IDs. + void EmitOnNotificationsEnabled(); + void EmitOnNotificationsDisabled(NotificationsDisabledReason reason); + + // Needed for death tests. + void DetachFromThreadForTest(); + + private: + typedef std::map<invalidation::ObjectId, SyncNotifierObserver*, + ObjectIdLessThan> + IdHandlerMap; + + SyncNotifierObserver* ObjectIdToHandler(const invalidation::ObjectId& id); + + base::ThreadChecker thread_checker_; + ObserverList<SyncNotifierObserver> handlers_; + IdHandlerMap id_to_handler_map_; + + DISALLOW_COPY_AND_ASSIGN(SyncNotifierRegistrar); +}; + +} // namespace syncer + +#endif // SYNC_NOTIFIER_SYNC_NOTIFIER_REGISTRAR_H_ diff --git a/sync/notifier/sync_notifier_registrar_unittest.cc b/sync/notifier/sync_notifier_registrar_unittest.cc new file mode 100644 index 0000000..b30252b --- /dev/null +++ b/sync/notifier/sync_notifier_registrar_unittest.cc @@ -0,0 +1,248 @@ +// Copyright (c) 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. + +#include "google/cacheinvalidation/types.pb.h" +#include "sync/notifier/mock_sync_notifier_observer.h" +#include "sync/notifier/sync_notifier_registrar.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace syncer { + +namespace { + +using testing::InSequence; +using testing::Mock; +using testing::StrictMock; + +class SyncNotifierRegistrarTest : public testing::Test { + protected: + SyncNotifierRegistrarTest() + : kObjectId1(ipc::invalidation::ObjectSource::TEST, "a"), + kObjectId2(ipc::invalidation::ObjectSource::TEST, "b"), + kObjectId3(ipc::invalidation::ObjectSource::TEST, "c"), + kObjectId4(ipc::invalidation::ObjectSource::TEST, "d") { + } + + const invalidation::ObjectId kObjectId1; + const invalidation::ObjectId kObjectId2; + const invalidation::ObjectId kObjectId3; + const invalidation::ObjectId kObjectId4; +}; + +// Register a handler, register some IDs for that handler, and then unregister +// the handler, dispatching invalidations in between. The handler should only +// see invalidations when its registered and its IDs are registered. +TEST_F(SyncNotifierRegistrarTest, Basic) { + StrictMock<MockSyncNotifierObserver> handler; + + SyncNotifierRegistrar registrar; + + registrar.RegisterHandler(&handler); + + ObjectIdPayloadMap payloads; + payloads[kObjectId1] = "1"; + payloads[kObjectId2] = "2"; + payloads[kObjectId3] = "3"; + + // Should be ignored since no IDs are registered to |handler|. + registrar.DispatchInvalidationsToHandlers(payloads, REMOTE_NOTIFICATION); + + Mock::VerifyAndClearExpectations(&handler); + + ObjectIdSet ids; + ids.insert(kObjectId1); + ids.insert(kObjectId2); + registrar.UpdateRegisteredIds(&handler, ids); + + { + ObjectIdPayloadMap expected_payloads; + expected_payloads[kObjectId1] = "1"; + expected_payloads[kObjectId2] = "2"; + EXPECT_CALL(handler, OnIncomingNotification(expected_payloads, + REMOTE_NOTIFICATION)); + } + + registrar.DispatchInvalidationsToHandlers(payloads, REMOTE_NOTIFICATION); + + Mock::VerifyAndClearExpectations(&handler); + + ids.erase(kObjectId1); + ids.insert(kObjectId3); + registrar.UpdateRegisteredIds(&handler, ids); + + { + ObjectIdPayloadMap expected_payloads; + expected_payloads[kObjectId2] = "2"; + expected_payloads[kObjectId3] = "3"; + EXPECT_CALL(handler, OnIncomingNotification(expected_payloads, + REMOTE_NOTIFICATION)); + } + + // Removed object IDs should not be notified, newly-added ones should. + registrar.DispatchInvalidationsToHandlers(payloads, REMOTE_NOTIFICATION); + + Mock::VerifyAndClearExpectations(&handler); + + registrar.UnregisterHandler(&handler); + + // Should be ignored since |handler| isn't registered anymore. + registrar.DispatchInvalidationsToHandlers(payloads, REMOTE_NOTIFICATION); +} + +// Register handlers and some IDs for those handlers, register a handler with +// no IDs, and register a handler with some IDs but unregister it. Then, +// dispatch some notifications and invalidations. Handlers that are registered +// should get notifications, and the ones that have registered IDs should +// receive invalidations for those IDs. +TEST_F(SyncNotifierRegistrarTest, MultipleHandlers) { + StrictMock<MockSyncNotifierObserver> handler1; + EXPECT_CALL(handler1, OnNotificationsEnabled()); + { + ObjectIdPayloadMap expected_payloads; + expected_payloads[kObjectId1] = "1"; + expected_payloads[kObjectId2] = "2"; + EXPECT_CALL(handler1, OnIncomingNotification(expected_payloads, + REMOTE_NOTIFICATION)); + } + EXPECT_CALL(handler1, + OnNotificationsDisabled(TRANSIENT_NOTIFICATION_ERROR)); + + StrictMock<MockSyncNotifierObserver> handler2; + EXPECT_CALL(handler2, OnNotificationsEnabled()); + { + ObjectIdPayloadMap expected_payloads; + expected_payloads[kObjectId3] = "3"; + EXPECT_CALL(handler2, OnIncomingNotification(expected_payloads, + REMOTE_NOTIFICATION)); + } + EXPECT_CALL(handler2, + OnNotificationsDisabled(TRANSIENT_NOTIFICATION_ERROR)); + + StrictMock<MockSyncNotifierObserver> handler3; + EXPECT_CALL(handler3, OnNotificationsEnabled()); + EXPECT_CALL(handler3, + OnNotificationsDisabled(TRANSIENT_NOTIFICATION_ERROR)); + + StrictMock<MockSyncNotifierObserver> handler4; + + SyncNotifierRegistrar registrar; + + registrar.RegisterHandler(&handler1); + registrar.RegisterHandler(&handler2); + registrar.RegisterHandler(&handler3); + registrar.RegisterHandler(&handler4); + + { + ObjectIdSet ids; + ids.insert(kObjectId1); + ids.insert(kObjectId2); + registrar.UpdateRegisteredIds(&handler1, ids); + } + + { + ObjectIdSet ids; + ids.insert(kObjectId3); + registrar.UpdateRegisteredIds(&handler2, ids); + } + + // Don't register any IDs for handler3. + + { + ObjectIdSet ids; + ids.insert(kObjectId4); + registrar.UpdateRegisteredIds(&handler4, ids); + } + + registrar.UnregisterHandler(&handler4); + + registrar.EmitOnNotificationsEnabled(); + { + ObjectIdPayloadMap payloads; + payloads[kObjectId1] = "1"; + payloads[kObjectId2] = "2"; + payloads[kObjectId3] = "3"; + payloads[kObjectId4] = "4"; + registrar.DispatchInvalidationsToHandlers(payloads, REMOTE_NOTIFICATION); + } + registrar.EmitOnNotificationsDisabled(TRANSIENT_NOTIFICATION_ERROR); +} + +// Multiple registrations by different handlers on the same object ID should +// cause a CHECK. +TEST_F(SyncNotifierRegistrarTest, MultipleRegistration) { + SyncNotifierRegistrar registrar; + + StrictMock<MockSyncNotifierObserver> handler1; + registrar.RegisterHandler(&handler1); + + MockSyncNotifierObserver handler2; + registrar.RegisterHandler(&handler2); + + ObjectIdSet ids; + ids.insert(kObjectId1); + ids.insert(kObjectId2); + registrar.UpdateRegisteredIds(&handler1, ids); + + registrar.DetachFromThreadForTest(); + EXPECT_DEATH({ registrar.UpdateRegisteredIds(&handler2, ids); }, + "Duplicate registration: .*"); +} + +// Make sure that passing an empty set to UpdateRegisteredIds clears the +// corresponding entries for the handler. +TEST_F(SyncNotifierRegistrarTest, EmptySetUnregisters) { + StrictMock<MockSyncNotifierObserver> handler1; + EXPECT_CALL(handler1, OnNotificationsEnabled()); + EXPECT_CALL(handler1, + OnNotificationsDisabled(TRANSIENT_NOTIFICATION_ERROR)); + + // Control observer. + StrictMock<MockSyncNotifierObserver> handler2; + EXPECT_CALL(handler2, OnNotificationsEnabled()); + { + ObjectIdPayloadMap expected_payloads; + expected_payloads[kObjectId3] = "3"; + EXPECT_CALL(handler2, OnIncomingNotification(expected_payloads, + REMOTE_NOTIFICATION)); + } + EXPECT_CALL(handler2, + OnNotificationsDisabled(TRANSIENT_NOTIFICATION_ERROR)); + + SyncNotifierRegistrar registrar; + + registrar.RegisterHandler(&handler1); + registrar.RegisterHandler(&handler2); + + { + ObjectIdSet ids; + ids.insert(kObjectId1); + ids.insert(kObjectId2); + registrar.UpdateRegisteredIds(&handler1, ids); + } + + { + ObjectIdSet ids; + ids.insert(kObjectId3); + registrar.UpdateRegisteredIds(&handler2, ids); + } + + // Unregister the IDs for the first observer. It should not receive any + // further invalidations. + registrar.UpdateRegisteredIds(&handler1, ObjectIdSet()); + + registrar.EmitOnNotificationsEnabled(); + { + ObjectIdPayloadMap payloads; + payloads[kObjectId1] = "1"; + payloads[kObjectId2] = "2"; + payloads[kObjectId3] = "3"; + registrar.DispatchInvalidationsToHandlers(payloads, + REMOTE_NOTIFICATION); + } + registrar.EmitOnNotificationsDisabled(TRANSIENT_NOTIFICATION_ERROR); +} + +} // namespace + +} // namespace syncer diff --git a/sync/sync.gyp b/sync/sync.gyp index 3583c87..d559f1b 100644 --- a/sync/sync.gyp +++ b/sync/sync.gyp @@ -257,9 +257,9 @@ 'notifier/sync_notifier.h', 'notifier/sync_notifier_factory.cc', 'notifier/sync_notifier_factory.h', - 'notifier/sync_notifier_helper.cc', - 'notifier/sync_notifier_helper.h', 'notifier/sync_notifier_observer.h', + 'notifier/sync_notifier_registrar.cc', + 'notifier/sync_notifier_registrar.h', ], 'conditions': [ ['OS != "android"', { @@ -654,7 +654,7 @@ 'notifier/p2p_notifier_unittest.cc', 'notifier/push_client_channel_unittest.cc', 'notifier/registration_manager_unittest.cc', - 'notifier/sync_notifier_helper_unittest.cc', + 'notifier/sync_notifier_registrar_unittest.cc', ], }], ], diff --git a/sync/tools/sync_listen_notifications.cc b/sync/tools/sync_listen_notifications.cc index 5eb05b6..6d8adb0 100644 --- a/sync/tools/sync_listen_notifications.cc +++ b/sync/tools/sync_listen_notifications.cc @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <cstddef> #include <cstdio> #include <string> @@ -242,13 +243,15 @@ int SyncListenNotificationsMain(int argc, char* argv[]) { const char kUniqueId[] = "fake_unique_id"; sync_notifier->SetUniqueId(kUniqueId); sync_notifier->UpdateCredentials(email, token); + // Listen for notifications for all known types. + sync_notifier->RegisterHandler(¬ification_printer); sync_notifier->UpdateRegisteredIds( ¬ification_printer, ModelTypeSetToObjectIdSet(ModelTypeSet::All())); ui_loop.Run(); - sync_notifier->UpdateRegisteredIds(¬ification_printer, ObjectIdSet()); + sync_notifier->UnregisterHandler(¬ification_printer); io_thread.Stop(); return 0; } |