1 : // Copyright 2014 Google Inc. All Rights Reserved.
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : #include "syzygy/kasko/upload_thread.h"
16 :
17 : #include "base/bind.h"
18 : #include "base/bind_helpers.h"
19 : #include "base/callback.h"
20 : #include "base/location.h"
21 : #include "base/macros.h"
22 : #include "base/files/file_path.h"
23 : #include "base/files/scoped_temp_dir.h"
24 : #include "base/memory/scoped_ptr.h"
25 : #include "base/message_loop/message_loop.h"
26 : #include "base/synchronization/lock.h"
27 : #include "base/synchronization/waitable_event.h"
28 : #include "base/threading/platform_thread.h"
29 : #include "base/threading/thread.h"
30 : #include "base/time/time.h"
31 : #include "gtest/gtest.h"
32 : #include "syzygy/kasko/waitable_timer.h"
33 :
34 : namespace kasko {
35 :
36 : namespace {
37 :
38 : // Implements a WaitableTimer that can be triggered by tests.
39 : class WaitableTimerMock : public WaitableTimer {
40 : public:
41 : WaitableTimerMock()
42 : : unmatched_activations_(0),
43 : event_(false, false),
44 E : timer_activated_(false, false) {}
45 :
46 E : ~WaitableTimerMock() override { EXPECT_EQ(0, unmatched_activations_); }
47 :
48 : // WaitableTimer implementation
49 E : void Start() override {
50 E : base::AutoLock auto_lock(lock_);
51 E : event_.Reset();
52 E : ++unmatched_activations_;
53 E : timer_activated_.Signal();
54 E : }
55 :
56 E : HANDLE GetHANDLE() override { return event_.handle(); }
57 :
58 : // Returns true if Start() has been called. Resets after Trigger() is invoked.
59 E : bool IsActivated() { return timer_activated_.IsSignaled(); }
60 :
61 : // Signals the timer event. Call WaitForActivation() first.
62 E : void Trigger() {
63 : {
64 E : base::AutoLock auto_lock(lock_);
65 E : EXPECT_EQ(0, unmatched_activations_);
66 E : event_.Signal();
67 E : }
68 E : }
69 :
70 : // Blocks until the timer is activated. Each call to Start() releases one call
71 : // to WaitForActivation().
72 E : void WaitForActivation() {
73 : {
74 E : base::AutoLock auto_lock(lock_);
75 E : --unmatched_activations_;
76 E : }
77 E : while (true) {
78 : {
79 E : base::AutoLock auto_lock(lock_);
80 E : if (unmatched_activations_ >= 0)
81 E : return;
82 E : }
83 E : timer_activated_.Wait();
84 E : }
85 E : }
86 :
87 : private:
88 : int unmatched_activations_;
89 : base::WaitableEvent event_;
90 : base::WaitableEvent timer_activated_;
91 : base::Lock lock_;
92 :
93 : DISALLOW_COPY_AND_ASSIGN(WaitableTimerMock);
94 : };
95 :
96 : // Configures an UploadThread instance for testing.
97 : class TestInstance {
98 : public:
99 : // Creates an UploadThread with a unique exclusive path.
100 E : explicit TestInstance(const base::Closure& uploader) {
101 E : exclusive_path_dir_.CreateUniqueTempDir();
102 E : timer_ = new WaitableTimerMock();
103 : instance_ = UploadThread::Create(exclusive_path_dir_.path(),
104 E : make_scoped_ptr(timer_), uploader);
105 E : }
106 :
107 : // Creates an UploadThread that shares the same exclusive path as |other|.
108 E : TestInstance(const TestInstance& other, const base::Closure& uploader) {
109 E : timer_ = new WaitableTimerMock();
110 : instance_ = UploadThread::Create(other.exclusive_path_dir_.path(),
111 E : make_scoped_ptr(timer_), uploader);
112 E : }
113 :
114 E : ~TestInstance() {}
115 :
116 E : UploadThread* get() { return instance_.get(); }
117 E : WaitableTimerMock* timer() { return timer_; }
118 :
119 : private:
120 : // The exclusive path.
121 : base::ScopedTempDir exclusive_path_dir_;
122 : scoped_ptr<UploadThread> instance_;
123 : WaitableTimerMock* timer_;
124 :
125 : DISALLOW_COPY_AND_ASSIGN(TestInstance);
126 : };
127 :
128 : // Returns a mock uploader that signals |event|.
129 E : base::Closure MakeUploader(base::WaitableEvent* event) {
130 E : return base::Bind(&base::WaitableEvent::Signal, base::Unretained(event));
131 E : }
132 :
133 : // A mock uploader that signals |upload_started| and then blocks on
134 : // |unblock_upload|.
135 : void BlockingUpload(base::WaitableEvent* upload_started,
136 E : base::WaitableEvent* unblock_upload) {
137 E : upload_started->Signal();
138 E : unblock_upload->Wait();
139 E : }
140 :
141 : // Signals |join_started|, invokes upload_thread->Join(), and then signals
142 : // |join_completed|.
143 : void DoJoin(UploadThread* upload_thread,
144 : base::WaitableEvent* join_started,
145 E : base::WaitableEvent* join_completed) {
146 E : join_started->Signal();
147 E : upload_thread->Join();
148 E : join_completed->Signal();
149 E : }
150 :
151 : } // namespace
152 :
153 E : TEST(UploadThreadTest, BasicTest) {
154 E : base::WaitableEvent upload_event(false, false);
155 E : TestInstance instance(MakeUploader(&upload_event));
156 :
157 E : ASSERT_TRUE(instance.get());
158 E : EXPECT_FALSE(instance.timer()->IsActivated());
159 :
160 : // Start the thread, and it will activate the timer.
161 E : instance.get()->Start();
162 E : instance.timer()->WaitForActivation();
163 :
164 : // No upload occurs til the timer goes off.
165 E : EXPECT_FALSE(upload_event.IsSignaled());
166 :
167 : // When the timer goes off, an upload is recorded.
168 E : instance.timer()->Trigger();
169 E : upload_event.Wait();
170 :
171 : // The thread goes back to reactivate the timer.
172 E : instance.timer()->WaitForActivation();
173 :
174 : // Triggering again causes another upload.
175 E : instance.timer()->Trigger();
176 E : upload_event.Wait();
177 :
178 : // The thread goes back to reactivate the timer.
179 E : instance.timer()->WaitForActivation();
180 :
181 : // UploadOneNowAsync triggers an upload without the timer trigger.
182 E : instance.get()->UploadOneNowAsync();
183 E : upload_event.Wait();
184 :
185 : // The timer is reset after handling an upload requested via
186 : // UploadOneNowAsync().
187 E : instance.timer()->WaitForActivation();
188 :
189 : // Stop and shut down the thread.
190 E : instance.get()->Stop();
191 E : instance.get()->Join();
192 :
193 : // No more uploads occurred.
194 E : EXPECT_FALSE(upload_event.IsSignaled());
195 E : }
196 :
197 E : TEST(UploadThreadTest, OnlyOneActivates) {
198 E : base::WaitableEvent upload_event_1(false, false);
199 E : TestInstance instance_1(MakeUploader(&upload_event_1));
200 :
201 E : ASSERT_TRUE(instance_1.get());
202 E : ASSERT_TRUE(instance_1.timer());
203 E : EXPECT_FALSE(instance_1.timer()->IsActivated());
204 :
205 E : base::WaitableEvent upload_event_2(false, false);
206 : // Pass instance_1 to share the exclusive path.
207 E : TestInstance instance_2(instance_1, MakeUploader(&upload_event_2));
208 :
209 E : ASSERT_TRUE(instance_2.get());
210 E : ASSERT_TRUE(instance_2.timer());
211 E : EXPECT_FALSE(instance_2.timer()->IsActivated());
212 :
213 : // Start the threads.
214 E : instance_1.get()->Start();
215 E : instance_1.timer()->WaitForActivation();
216 :
217 E : instance_2.get()->Start();
218 : // Give a broken implementation a chance to activate the timer.
219 E : base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
220 E : EXPECT_FALSE(instance_2.timer()->IsActivated());
221 :
222 E : instance_1.timer()->Trigger();
223 E : upload_event_1.Wait();
224 :
225 E : EXPECT_FALSE(upload_event_2.IsSignaled());
226 E : EXPECT_FALSE(instance_2.timer()->IsActivated());
227 :
228 E : instance_1.timer()->WaitForActivation();
229 :
230 : // UploadOneNowAsync triggers an upload without the timer trigger.
231 E : instance_1.get()->UploadOneNowAsync();
232 E : upload_event_1.Wait();
233 E : instance_1.timer()->WaitForActivation();
234 :
235 E : instance_2.get()->UploadOneNowAsync();
236 E : upload_event_1.Wait();
237 E : instance_1.timer()->WaitForActivation();
238 :
239 : // Give a broken implementation a chance to do something unexpected.
240 E : base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
241 E : EXPECT_FALSE(instance_2.timer()->IsActivated());
242 E : EXPECT_FALSE(upload_event_2.IsSignaled());
243 :
244 : // Shut down the active thread. The 2nd thread should take over.
245 E : instance_1.get()->Join();
246 E : instance_2.timer()->WaitForActivation();
247 E : instance_2.timer()->Trigger();
248 E : upload_event_2.Wait();
249 :
250 E : instance_2.timer()->WaitForActivation();
251 E : instance_2.get()->UploadOneNowAsync();
252 E : upload_event_2.Wait();
253 E : instance_2.timer()->WaitForActivation();
254 :
255 E : instance_2.get()->Join();
256 E : }
257 :
258 E : TEST(UploadThreadTest, SimultaneousActivationOnSeparatePaths) {
259 E : base::WaitableEvent upload_event_1(false, false);
260 E : TestInstance instance_1(MakeUploader(&upload_event_1));
261 :
262 E : ASSERT_TRUE(instance_1.get());
263 E : ASSERT_TRUE(instance_1.timer());
264 E : EXPECT_FALSE(instance_1.timer()->IsActivated());
265 :
266 E : base::WaitableEvent upload_event_2(false, false);
267 : // Since we don't pass instance_1 here, the second instance will use a new
268 : // exclusive path.
269 E : TestInstance instance_2(MakeUploader(&upload_event_2));
270 :
271 E : ASSERT_TRUE(instance_2.get());
272 E : ASSERT_TRUE(instance_2.timer());
273 E : EXPECT_FALSE(instance_2.timer()->IsActivated());
274 :
275 E : instance_1.get()->Start();
276 E : instance_1.timer()->WaitForActivation();
277 :
278 E : instance_2.get()->Start();
279 E : instance_2.timer()->WaitForActivation();
280 :
281 E : instance_1.timer()->Trigger();
282 E : upload_event_1.Wait();
283 :
284 : // Give a broken implementation a chance to do something unexpected.
285 E : base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
286 E : EXPECT_FALSE(upload_event_2.IsSignaled());
287 :
288 E : instance_2.timer()->Trigger();
289 E : upload_event_2.Wait();
290 :
291 : // Give a broken implementation a chance to do something unexpected.
292 E : base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
293 E : EXPECT_FALSE(upload_event_1.IsSignaled());
294 :
295 E : instance_1.timer()->WaitForActivation();
296 E : instance_2.timer()->WaitForActivation();
297 :
298 E : instance_2.timer()->Trigger();
299 E : upload_event_2.Wait();
300 E : instance_2.timer()->WaitForActivation();
301 :
302 E : instance_1.timer()->Trigger();
303 E : upload_event_1.Wait();
304 E : instance_1.timer()->WaitForActivation();
305 :
306 E : instance_2.get()->UploadOneNowAsync();
307 E : upload_event_2.Wait();
308 E : instance_2.timer()->WaitForActivation();
309 :
310 : // Give a broken implementation a chance to do something unexpected.
311 E : base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
312 E : EXPECT_FALSE(upload_event_1.IsSignaled());
313 :
314 E : instance_1.get()->UploadOneNowAsync();
315 E : upload_event_1.Wait();
316 E : instance_1.timer()->WaitForActivation();
317 :
318 : // Give a broken implementation a chance to do something unexpected.
319 E : base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
320 E : EXPECT_FALSE(upload_event_2.IsSignaled());
321 :
322 E : instance_1.get()->Join();
323 E : instance_2.get()->Join();
324 E : }
325 :
326 E : TEST(UploadThreadTest, JoinBlocksOnUploadCompletion) {
327 E : base::Thread join_thread("join thread");
328 :
329 E : base::WaitableEvent upload_started(false, false);
330 E : base::WaitableEvent unblock_upload(false, false);
331 E : base::WaitableEvent join_started(false, false);
332 E : base::WaitableEvent join_completed(false, false);
333 :
334 : TestInstance instance(base::Bind(&BlockingUpload,
335 : base::Unretained(&upload_started),
336 E : base::Unretained(&unblock_upload)));
337 :
338 E : ASSERT_TRUE(instance.get());
339 E : ASSERT_TRUE(instance.timer());
340 :
341 E : instance.get()->Start();
342 E : instance.timer()->WaitForActivation();
343 E : instance.timer()->Trigger();
344 E : upload_started.Wait();
345 E : EXPECT_TRUE(join_thread.Start());
346 : join_thread.message_loop()->PostTask(FROM_HERE, base::Bind(
347 : &DoJoin, base::Unretained(instance.get()),
348 E : base::Unretained(&join_started), base::Unretained(&join_completed)));
349 E : join_started.Wait();
350 :
351 : // A small wait to allow a chance for a broken Join to return early.
352 E : base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
353 :
354 : // Release the blocking upload.
355 E : unblock_upload.Signal();
356 : // Implementation detail: the UploadThread will reset the timer before
357 : // checking the stop event.
358 E : instance.timer()->WaitForActivation();
359 E : join_completed.Wait();
360 E : }
361 :
362 E : TEST(UploadThreadTest, UploadOneNowAsyncGuarantees) {
363 E : base::Thread join_thread("join thread");
364 :
365 E : base::WaitableEvent upload_started(false, false);
366 E : base::WaitableEvent unblock_upload(false, false);
367 :
368 : TestInstance instance(base::Bind(&BlockingUpload,
369 : base::Unretained(&upload_started),
370 E : base::Unretained(&unblock_upload)));
371 :
372 E : ASSERT_TRUE(instance.get());
373 E : ASSERT_TRUE(instance.timer());
374 :
375 : // Basic case.
376 E : instance.get()->Start();
377 E : instance.timer()->WaitForActivation();
378 E : instance.get()->UploadOneNowAsync();
379 E : upload_started.Wait();
380 E : unblock_upload.Signal();
381 :
382 : // If a request is received while an upload is in progress the request is
383 : // honored immediately after the previous upload completes.
384 E : instance.timer()->WaitForActivation();
385 E : instance.timer()->Trigger();
386 E : upload_started.Wait();
387 : // The thread is now blocking on |unblock_upload|.
388 : // Request an upload.
389 E : instance.get()->UploadOneNowAsync();
390 : // End the initial upload.
391 E : unblock_upload.Signal();
392 : // Implementation detail: the timer will be reset before the pending upload
393 : // request is detected.
394 E : instance.timer()->WaitForActivation();
395 : // Now the requested upload should take place.
396 E : upload_started.Wait();
397 E : unblock_upload.Signal();
398 :
399 : // If a request is received when another request is already pending (not yet
400 : // started) the second request is ignored.
401 E : instance.timer()->WaitForActivation();
402 E : instance.timer()->Trigger();
403 E : upload_started.Wait();
404 : // The thread is now blocking on |unblock_upload|.
405 : // Request an upload.
406 E : instance.get()->UploadOneNowAsync();
407 : // Request a second upload - this request should be a no-op.
408 E : instance.get()->UploadOneNowAsync();
409 : // End the initial upload.
410 E : unblock_upload.Signal();
411 : // Implementation detail: the timer will be reset before the pending upload
412 : // request is detected.
413 E : instance.timer()->WaitForActivation();
414 : // Now the first requested upload should take place.
415 E : upload_started.Wait();
416 E : unblock_upload.Signal();
417 E : instance.timer()->WaitForActivation();
418 : // A small wait to allow a broken implementation to handle the second request.
419 E : base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
420 E : EXPECT_FALSE(upload_started.IsSignaled());
421 :
422 : // Any request received before Stop() is called will be honoured, even if it
423 : // has not started yet.
424 : // Trigger a scheduled upload.
425 E : instance.timer()->Trigger();
426 E : upload_started.Wait();
427 : // The scheduled upload is blocking.
428 : // Request an upload.
429 E : instance.get()->UploadOneNowAsync();
430 : // The requested upload has not started yet. Invoke Stop() on the
431 : // UploadThread.
432 E : instance.get()->Stop();
433 : // End the initial upload.
434 E : unblock_upload.Signal();
435 : // Implementation detail: the timer will be reset before the pending upload
436 : // request is detected.
437 E : instance.timer()->WaitForActivation();
438 : // Now the requested upload should take place, even though Stop() was called.
439 E : upload_started.Wait();
440 : // If we get here, the second upload occurred. Now unblock it.
441 E : unblock_upload.Signal();
442 : // Implementation detail: the timer will be reset before the stop request is
443 : // detected.
444 E : instance.timer()->WaitForActivation();
445 E : instance.get()->Join();
446 E : }
447 :
448 : } // namespace kasko
|