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