1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "base/callback.h" 6 #include "base/memory/ref_counted.h" 7 #include "base/message_loop.h" 8 #include "base/threading/thread.h" 9 #include "base/synchronization/waitable_event.h" 10 #include "content/browser/browser_thread.h" 11 #include "chrome/browser/sync/engine/syncapi.h" 12 #include "chrome/browser/sync/glue/ui_model_worker.h" 13 #include "testing/gtest/include/gtest/gtest.h" 14 15 using browser_sync::UIModelWorker; 16 using namespace sync_api; 17 18 // Various boilerplate, primarily for the StopWithPendingWork test. 19 20 class UIModelWorkerVisitor { 21 public: 22 UIModelWorkerVisitor(base::WaitableEvent* was_run, 23 bool quit_loop) 24 : quit_loop_when_run_(quit_loop), 25 was_run_(was_run) { } 26 virtual ~UIModelWorkerVisitor() { } 27 28 virtual void DoWork() { 29 EXPECT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::UI)); 30 was_run_->Signal(); 31 if (quit_loop_when_run_) 32 MessageLoop::current()->Quit(); 33 } 34 35 private: 36 bool quit_loop_when_run_; 37 base::WaitableEvent* was_run_; 38 DISALLOW_COPY_AND_ASSIGN(UIModelWorkerVisitor); 39 }; 40 41 // A faux-syncer that only interacts with its model safe worker. 42 class Syncer { 43 public: 44 explicit Syncer(UIModelWorker* worker) : worker_(worker) {} 45 ~Syncer() {} 46 47 void SyncShare(UIModelWorkerVisitor* visitor) { 48 scoped_ptr<Callback0::Type> c(NewCallback(visitor, 49 &UIModelWorkerVisitor::DoWork)); 50 worker_->DoWorkAndWaitUntilDone(c.get()); 51 } 52 private: 53 scoped_refptr<UIModelWorker> worker_; 54 DISALLOW_COPY_AND_ASSIGN(Syncer); 55 }; 56 57 // A task run from the SyncerThread to "sync share", ie tell the Syncer to 58 // ask its ModelSafeWorker to do something. 59 class FakeSyncShareTask : public Task { 60 public: 61 FakeSyncShareTask(Syncer* syncer, UIModelWorkerVisitor* visitor) 62 : syncer_(syncer), visitor_(visitor) { 63 } 64 virtual void Run() { 65 syncer_->SyncShare(visitor_); 66 } 67 private: 68 Syncer* syncer_; 69 UIModelWorkerVisitor* visitor_; 70 DISALLOW_COPY_AND_ASSIGN(FakeSyncShareTask); 71 }; 72 73 // A task run from the CoreThread to simulate terminating syncapi. 74 class FakeSyncapiShutdownTask : public Task { 75 public: 76 FakeSyncapiShutdownTask(base::Thread* syncer_thread, 77 UIModelWorker* worker, 78 base::WaitableEvent** jobs, 79 size_t job_count) 80 : syncer_thread_(syncer_thread), worker_(worker), jobs_(jobs), 81 job_count_(job_count), all_jobs_done_(false, false) { } 82 virtual void Run() { 83 // In real life, we would try and close a sync directory, which would 84 // result in the syncer calling it's own destructor, which results in 85 // the SyncerThread::HaltSyncer being called, which sets the 86 // syncer in RequestEarlyExit mode and waits until the Syncer finishes 87 // SyncShare to remove the syncer from it's watch. Here we just manually 88 // wait until all outstanding jobs are done to simulate what happens in 89 // SyncerThread::HaltSyncer. 90 all_jobs_done_.WaitMany(jobs_, job_count_); 91 92 // These two calls are made from SyncBackendHost::Core::DoShutdown. 93 syncer_thread_->Stop(); 94 worker_->OnSyncerShutdownComplete(); 95 } 96 private: 97 base::Thread* syncer_thread_; 98 scoped_refptr<UIModelWorker> worker_; 99 base::WaitableEvent** jobs_; 100 size_t job_count_; 101 base::WaitableEvent all_jobs_done_; 102 DISALLOW_COPY_AND_ASSIGN(FakeSyncapiShutdownTask); 103 }; 104 105 class UIModelWorkerTest : public testing::Test { 106 public: 107 UIModelWorkerTest() : faux_syncer_thread_("FauxSyncerThread"), 108 faux_core_thread_("FauxCoreThread") { } 109 110 virtual void SetUp() { 111 faux_syncer_thread_.Start(); 112 ui_thread_.reset(new BrowserThread(BrowserThread::UI, &faux_ui_loop_)); 113 bmw_ = new UIModelWorker(); 114 syncer_.reset(new Syncer(bmw_.get())); 115 } 116 117 Syncer* syncer() { return syncer_.get(); } 118 UIModelWorker* bmw() { return bmw_.get(); } 119 base::Thread* core_thread() { return &faux_core_thread_; } 120 base::Thread* syncer_thread() { return &faux_syncer_thread_; } 121 private: 122 MessageLoop faux_ui_loop_; 123 scoped_ptr<BrowserThread> ui_thread_; 124 base::Thread faux_syncer_thread_; 125 base::Thread faux_core_thread_; 126 scoped_refptr<UIModelWorker> bmw_; 127 scoped_ptr<Syncer> syncer_; 128 }; 129 130 TEST_F(UIModelWorkerTest, ScheduledWorkRunsOnUILoop) { 131 base::WaitableEvent v_was_run(false, false); 132 scoped_ptr<UIModelWorkerVisitor> v( 133 new UIModelWorkerVisitor(&v_was_run, true)); 134 135 syncer_thread()->message_loop()->PostTask(FROM_HERE, 136 new FakeSyncShareTask(syncer(), v.get())); 137 138 // We are on the UI thread, so run our loop to process the 139 // (hopefully) scheduled task from a SyncShare invocation. 140 MessageLoop::current()->Run(); 141 142 bmw()->OnSyncerShutdownComplete(); 143 bmw()->Stop(); 144 syncer_thread()->Stop(); 145 } 146 147 TEST_F(UIModelWorkerTest, StopWithPendingWork) { 148 // What we want to set up is the following: 149 // ("ui_thread" is the thread we are currently executing on) 150 // 1 - simulate the user shutting down the browser, and the ui thread needing 151 // to terminate the core thread. 152 // 2 - the core thread is where the syncapi is accessed from, and so it needs 153 // to shut down the SyncerThread. 154 // 3 - the syncer is waiting on the UIModelWorker to 155 // perform a task for it. 156 // The UIModelWorker's manual shutdown pump will save the day, as the 157 // UI thread is not actually trying to join() the core thread, it is merely 158 // waiting for the SyncerThread to give it work or to finish. After that, it 159 // will join the core thread which should succeed as the SyncerThread has left 160 // the building. Unfortunately this test as written is not provably decidable, 161 // as it will always halt on success, but it may not on failure (namely if 162 // the task scheduled by the Syncer is _never_ run). 163 core_thread()->Start(); 164 base::WaitableEvent v_ran(false, false); 165 scoped_ptr<UIModelWorkerVisitor> v(new UIModelWorkerVisitor( 166 &v_ran, false)); 167 base::WaitableEvent* jobs[] = { &v_ran }; 168 169 // The current message loop is not running, so queue a task to cause 170 // UIModelWorker::Stop() to play a crucial role. See comment below. 171 syncer_thread()->message_loop()->PostTask(FROM_HERE, 172 new FakeSyncShareTask(syncer(), v.get())); 173 174 // This is what gets the core_thread blocked on the syncer_thread. 175 core_thread()->message_loop()->PostTask(FROM_HERE, 176 new FakeSyncapiShutdownTask(syncer_thread(), bmw(), jobs, 1)); 177 178 // This is what gets the UI thread blocked until NotifyExitRequested, 179 // which is called when FakeSyncapiShutdownTask runs and deletes the syncer. 180 bmw()->Stop(); 181 182 EXPECT_FALSE(syncer_thread()->IsRunning()); 183 core_thread()->Stop(); 184 } 185 186 TEST_F(UIModelWorkerTest, HypotheticalManualPumpFlooding) { 187 // This situation should not happen in real life because the Syncer should 188 // never send more than one CallDoWork notification after early_exit_requested 189 // has been set, but our UIModelWorker is built to handle this case 190 // nonetheless. It may be needed in the future, and since we support it and 191 // it is not actually exercised in the wild this test is essential. 192 // It is identical to above except we schedule more than one visitor. 193 core_thread()->Start(); 194 195 // Our ammunition. 196 base::WaitableEvent fox1_ran(false, false); 197 scoped_ptr<UIModelWorkerVisitor> fox1(new UIModelWorkerVisitor( 198 &fox1_ran, false)); 199 base::WaitableEvent fox2_ran(false, false); 200 scoped_ptr<UIModelWorkerVisitor> fox2(new UIModelWorkerVisitor( 201 &fox2_ran, false)); 202 base::WaitableEvent fox3_ran(false, false); 203 scoped_ptr<UIModelWorkerVisitor> fox3(new UIModelWorkerVisitor( 204 &fox3_ran, false)); 205 base::WaitableEvent* jobs[] = { &fox1_ran, &fox2_ran, &fox3_ran }; 206 207 // The current message loop is not running, so queue a task to cause 208 // UIModelWorker::Stop() to play a crucial role. See comment below. 209 syncer_thread()->message_loop()->PostTask(FROM_HERE, 210 new FakeSyncShareTask(syncer(), fox1.get())); 211 syncer_thread()->message_loop()->PostTask(FROM_HERE, 212 new FakeSyncShareTask(syncer(), fox2.get())); 213 214 // This is what gets the core_thread blocked on the syncer_thread. 215 core_thread()->message_loop()->PostTask(FROM_HERE, 216 new FakeSyncapiShutdownTask(syncer_thread(), bmw(), jobs, 3)); 217 syncer_thread()->message_loop()->PostTask(FROM_HERE, 218 new FakeSyncShareTask(syncer(), fox3.get())); 219 220 // This is what gets the UI thread blocked until NotifyExitRequested, 221 // which is called when FakeSyncapiShutdownTask runs and deletes the syncer. 222 bmw()->Stop(); 223 224 // Was the thread killed? 225 EXPECT_FALSE(syncer_thread()->IsRunning()); 226 core_thread()->Stop(); 227 } 228