blob: ee1dee71063825d762333ec04f164ecf88afbb91 [file] [log] [blame]
[email protected]44106182012-04-06 03:53:021// Copyright (c) 2012 The Chromium Authors. All rights reserved.
[email protected]1c4947f2009-01-15 22:25:112// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
[email protected]f7b98b32013-02-05 08:14:155#include "base/synchronization/waitable_event_watcher.h"
6
[email protected]329be052013-02-04 18:14:287#include "base/bind.h"
8#include "base/callback.h"
avi9b6f42932015-12-26 22:15:149#include "base/macros.h"
atuchin54d83152017-05-10 06:30:0110#include "base/memory/ptr_util.h"
[email protected]f7b98b32013-02-05 08:14:1511#include "base/run_loop.h"
[email protected]44f9c952011-01-02 06:05:3912#include "base/synchronization/waitable_event.h"
Gabriel Charettec7108742019-08-23 03:31:4013#include "base/test/task_environment.h"
[email protected]ce072a72010-12-31 20:02:1614#include "base/threading/platform_thread.h"
atuchin54d83152017-05-10 06:30:0115#include "base/threading/sequenced_task_runner_handle.h"
Sami Kyostila3f49cb572018-11-19 13:01:0916#include "base/threading/thread_task_runner_handle.h"
avi9b6f42932015-12-26 22:15:1417#include "build/build_config.h"
[email protected]1c4947f2009-01-15 22:25:1118#include "testing/gtest/include/gtest/gtest.h"
19
[email protected]44f9c952011-01-02 06:05:3920namespace base {
[email protected]1c4947f2009-01-15 22:25:1121
22namespace {
23
Sami Kyostila3f49cb572018-11-19 13:01:0924// The main thread types on which each waitable event should be tested.
Gabriel Charette694c3c332019-08-19 14:53:0525const test::TaskEnvironment::MainThreadType testing_main_threads[] = {
26 test::TaskEnvironment::MainThreadType::DEFAULT,
27 test::TaskEnvironment::MainThreadType::IO,
[email protected]840246b2012-07-18 08:06:5028#if !defined(OS_IOS) // iOS does not allow direct running of the UI loop.
Gabriel Charette694c3c332019-08-19 14:53:0529 test::TaskEnvironment::MainThreadType::UI,
[email protected]840246b2012-07-18 08:06:5030#endif
31};
32
[email protected]329be052013-02-04 18:14:2833void QuitWhenSignaled(WaitableEvent* event) {
Gabriel Charette53a9ef812017-07-26 12:36:2334 RunLoop::QuitCurrentWhenIdleDeprecated();
[email protected]329be052013-02-04 18:14:2835}
[email protected]1eaa54792013-01-31 23:14:2736
[email protected]329be052013-02-04 18:14:2837class DecrementCountContainer {
[email protected]1eaa54792013-01-31 23:14:2738 public:
Robert Sesekb44f8962017-06-30 21:21:1339 explicit DecrementCountContainer(int* counter) : counter_(counter) {}
[email protected]329be052013-02-04 18:14:2840 void OnWaitableEventSignaled(WaitableEvent* object) {
atuchin54d83152017-05-10 06:30:0141 // NOTE: |object| may be already deleted.
[email protected]1c4947f2009-01-15 22:25:1142 --(*counter_);
43 }
Robert Sesekb44f8962017-06-30 21:21:1344
[email protected]1c4947f2009-01-15 22:25:1145 private:
46 int* counter_;
47};
48
Robert Sesek3b3333772017-06-30 18:21:5649} // namespace
50
51class WaitableEventWatcherTest
Gabriel Charette694c3c332019-08-19 14:53:0552 : public testing::TestWithParam<test::TaskEnvironment::MainThreadType> {};
Robert Sesek3b3333772017-06-30 18:21:5653
Robert Sesekcfab3ad2017-08-01 16:25:4354TEST_P(WaitableEventWatcherTest, BasicSignalManual) {
Gabriel Charette694c3c332019-08-19 14:53:0555 test::TaskEnvironment task_environment(GetParam());
[email protected]1c4947f2009-01-15 22:25:1156
57 // A manual-reset event that is not yet signaled.
gab75d72332016-06-01 21:15:3358 WaitableEvent event(WaitableEvent::ResetPolicy::MANUAL,
59 WaitableEvent::InitialState::NOT_SIGNALED);
[email protected]1c4947f2009-01-15 22:25:1160
61 WaitableEventWatcher watcher;
Hajime Hoshi138652c92018-01-12 15:11:4462 watcher.StartWatching(&event, BindOnce(&QuitWhenSignaled),
63 SequencedTaskRunnerHandle::Get());
[email protected]1c4947f2009-01-15 22:25:1164
65 event.Signal();
66
fdoray10224582016-06-30 18:17:3967 RunLoop().Run();
Robert Sesekcfab3ad2017-08-01 16:25:4368
69 EXPECT_TRUE(event.IsSignaled());
70}
71
72TEST_P(WaitableEventWatcherTest, BasicSignalAutomatic) {
Gabriel Charette694c3c332019-08-19 14:53:0573 test::TaskEnvironment task_environment(GetParam());
Robert Sesekcfab3ad2017-08-01 16:25:4374
75 WaitableEvent event(WaitableEvent::ResetPolicy::AUTOMATIC,
76 WaitableEvent::InitialState::NOT_SIGNALED);
77
78 WaitableEventWatcher watcher;
Hajime Hoshi138652c92018-01-12 15:11:4479 watcher.StartWatching(&event, BindOnce(&QuitWhenSignaled),
80 SequencedTaskRunnerHandle::Get());
Robert Sesekcfab3ad2017-08-01 16:25:4381
82 event.Signal();
83
84 RunLoop().Run();
85
86 // The WaitableEventWatcher consumes the event signal.
87 EXPECT_FALSE(event.IsSignaled());
[email protected]1c4947f2009-01-15 22:25:1188}
89
Robert Sesek3b3333772017-06-30 18:21:5690TEST_P(WaitableEventWatcherTest, BasicCancel) {
Gabriel Charette694c3c332019-08-19 14:53:0591 test::TaskEnvironment task_environment(GetParam());
[email protected]1c4947f2009-01-15 22:25:1192
93 // A manual-reset event that is not yet signaled.
gab75d72332016-06-01 21:15:3394 WaitableEvent event(WaitableEvent::ResetPolicy::MANUAL,
95 WaitableEvent::InitialState::NOT_SIGNALED);
[email protected]1c4947f2009-01-15 22:25:1196
97 WaitableEventWatcher watcher;
98
Hajime Hoshi138652c92018-01-12 15:11:4499 watcher.StartWatching(&event, BindOnce(&QuitWhenSignaled),
100 SequencedTaskRunnerHandle::Get());
[email protected]1c4947f2009-01-15 22:25:11101
102 watcher.StopWatching();
103}
104
Robert Sesek3b3333772017-06-30 18:21:56105TEST_P(WaitableEventWatcherTest, CancelAfterSet) {
Gabriel Charette694c3c332019-08-19 14:53:05106 test::TaskEnvironment task_environment(GetParam());
[email protected]1c4947f2009-01-15 22:25:11107
108 // A manual-reset event that is not yet signaled.
gab75d72332016-06-01 21:15:33109 WaitableEvent event(WaitableEvent::ResetPolicy::MANUAL,
110 WaitableEvent::InitialState::NOT_SIGNALED);
[email protected]1c4947f2009-01-15 22:25:11111
112 WaitableEventWatcher watcher;
113
114 int counter = 1;
[email protected]329be052013-02-04 18:14:28115 DecrementCountContainer delegate(&counter);
tzik130cfd0c2017-04-18 03:49:05116 WaitableEventWatcher::EventCallback callback = BindOnce(
117 &DecrementCountContainer::OnWaitableEventSignaled, Unretained(&delegate));
Hajime Hoshi138652c92018-01-12 15:11:44118 watcher.StartWatching(&event, std::move(callback),
119 SequencedTaskRunnerHandle::Get());
[email protected]1c4947f2009-01-15 22:25:11120
121 event.Signal();
122
123 // Let the background thread do its business
Hajime Hoshi138652c92018-01-12 15:11:44124 PlatformThread::Sleep(TimeDelta::FromMilliseconds(30));
[email protected]1c4947f2009-01-15 22:25:11125
126 watcher.StopWatching();
127
[email protected]f7b98b32013-02-05 08:14:15128 RunLoop().RunUntilIdle();
[email protected]1c4947f2009-01-15 22:25:11129
130 // Our delegate should not have fired.
131 EXPECT_EQ(1, counter);
132}
133
Sami Kyostila3f49cb572018-11-19 13:01:09134TEST_P(WaitableEventWatcherTest, OutlivesTaskEnvironment) {
135 // Simulate a task environment that dies before an WaitableEventWatcher. This
[email protected]1c4947f2009-01-15 22:25:11136 // ordinarily doesn't happen when people use the Thread class, but it can
137 // happen when people use the Singleton pattern or atexit.
gab75d72332016-06-01 21:15:33138 WaitableEvent event(WaitableEvent::ResetPolicy::MANUAL,
139 WaitableEvent::InitialState::NOT_SIGNALED);
[email protected]1c4947f2009-01-15 22:25:11140 {
Hajime Hoshi138652c92018-01-12 15:11:44141 std::unique_ptr<WaitableEventWatcher> watcher;
[email protected]1c4947f2009-01-15 22:25:11142 {
Gabriel Charette694c3c332019-08-19 14:53:05143 test::TaskEnvironment task_environment(GetParam());
Hajime Hoshi138652c92018-01-12 15:11:44144 watcher = std::make_unique<WaitableEventWatcher>();
[email protected]1c4947f2009-01-15 22:25:11145
Hajime Hoshi138652c92018-01-12 15:11:44146 watcher->StartWatching(&event, BindOnce(&QuitWhenSignaled),
147 SequencedTaskRunnerHandle::Get());
[email protected]1c4947f2009-01-15 22:25:11148 }
149 }
150}
151
Robert Sesekcfab3ad2017-08-01 16:25:43152TEST_P(WaitableEventWatcherTest, SignaledAtStartManual) {
Gabriel Charette694c3c332019-08-19 14:53:05153 test::TaskEnvironment task_environment(GetParam());
Robert Sesekb44f8962017-06-30 21:21:13154
155 WaitableEvent event(WaitableEvent::ResetPolicy::MANUAL,
156 WaitableEvent::InitialState::SIGNALED);
157
158 WaitableEventWatcher watcher;
Hajime Hoshi138652c92018-01-12 15:11:44159 watcher.StartWatching(&event, BindOnce(&QuitWhenSignaled),
160 SequencedTaskRunnerHandle::Get());
Robert Sesekb44f8962017-06-30 21:21:13161
162 RunLoop().Run();
Robert Sesekcfab3ad2017-08-01 16:25:43163
164 EXPECT_TRUE(event.IsSignaled());
165}
166
167TEST_P(WaitableEventWatcherTest, SignaledAtStartAutomatic) {
Gabriel Charette694c3c332019-08-19 14:53:05168 test::TaskEnvironment task_environment(GetParam());
Robert Sesekcfab3ad2017-08-01 16:25:43169
170 WaitableEvent event(WaitableEvent::ResetPolicy::AUTOMATIC,
171 WaitableEvent::InitialState::SIGNALED);
172
173 WaitableEventWatcher watcher;
Hajime Hoshi138652c92018-01-12 15:11:44174 watcher.StartWatching(&event, BindOnce(&QuitWhenSignaled),
175 SequencedTaskRunnerHandle::Get());
Robert Sesekcfab3ad2017-08-01 16:25:43176
177 RunLoop().Run();
178
179 // The watcher consumes the event signal.
180 EXPECT_FALSE(event.IsSignaled());
Robert Sesekb44f8962017-06-30 21:21:13181}
182
183TEST_P(WaitableEventWatcherTest, StartWatchingInCallback) {
Gabriel Charette694c3c332019-08-19 14:53:05184 test::TaskEnvironment task_environment(GetParam());
Robert Sesekb44f8962017-06-30 21:21:13185
186 WaitableEvent event(WaitableEvent::ResetPolicy::MANUAL,
187 WaitableEvent::InitialState::NOT_SIGNALED);
188
189 WaitableEventWatcher watcher;
190 watcher.StartWatching(
Hajime Hoshi138652c92018-01-12 15:11:44191 &event,
192 BindOnce(
193 [](WaitableEventWatcher* watcher, WaitableEvent* event) {
194 // |event| is manual, so the second watcher will run
195 // immediately.
196 watcher->StartWatching(event, BindOnce(&QuitWhenSignaled),
197 SequencedTaskRunnerHandle::Get());
198 },
199 &watcher),
200 SequencedTaskRunnerHandle::Get());
Robert Sesekb44f8962017-06-30 21:21:13201
202 event.Signal();
203
204 RunLoop().Run();
205}
206
Robert Sesekcfab3ad2017-08-01 16:25:43207TEST_P(WaitableEventWatcherTest, MultipleWatchersManual) {
Gabriel Charette694c3c332019-08-19 14:53:05208 test::TaskEnvironment task_environment(GetParam());
Robert Sesekcfab3ad2017-08-01 16:25:43209
210 WaitableEvent event(WaitableEvent::ResetPolicy::MANUAL,
211 WaitableEvent::InitialState::NOT_SIGNALED);
212
213 int counter1 = 0;
214 int counter2 = 0;
215
216 auto callback = [](RunLoop* run_loop, int* counter, WaitableEvent* event) {
217 ++(*counter);
218 run_loop->QuitWhenIdle();
219 };
220
221 RunLoop run_loop;
222
223 WaitableEventWatcher watcher1;
224 watcher1.StartWatching(
Hajime Hoshi138652c92018-01-12 15:11:44225 &event, BindOnce(callback, Unretained(&run_loop), Unretained(&counter1)),
226 SequencedTaskRunnerHandle::Get());
Robert Sesekcfab3ad2017-08-01 16:25:43227
228 WaitableEventWatcher watcher2;
229 watcher2.StartWatching(
Hajime Hoshi138652c92018-01-12 15:11:44230 &event, BindOnce(callback, Unretained(&run_loop), Unretained(&counter2)),
231 SequencedTaskRunnerHandle::Get());
Robert Sesekcfab3ad2017-08-01 16:25:43232
233 event.Signal();
234 run_loop.Run();
235
236 EXPECT_EQ(1, counter1);
237 EXPECT_EQ(1, counter2);
238 EXPECT_TRUE(event.IsSignaled());
239}
240
241// Tests that only one async waiter gets called back for an auto-reset event.
242TEST_P(WaitableEventWatcherTest, MultipleWatchersAutomatic) {
Gabriel Charette694c3c332019-08-19 14:53:05243 test::TaskEnvironment task_environment(GetParam());
Robert Sesekcfab3ad2017-08-01 16:25:43244
245 WaitableEvent event(WaitableEvent::ResetPolicy::AUTOMATIC,
246 WaitableEvent::InitialState::NOT_SIGNALED);
247
248 int counter1 = 0;
249 int counter2 = 0;
250
251 auto callback = [](RunLoop** run_loop, int* counter, WaitableEvent* event) {
252 ++(*counter);
253 (*run_loop)->QuitWhenIdle();
254 };
255
256 // The same RunLoop instance cannot be Run more than once, and it is
257 // undefined which watcher will get called back first. Have the callback
258 // dereference this pointer to quit the loop, which will be updated on each
259 // Run.
260 RunLoop* current_run_loop;
261
262 WaitableEventWatcher watcher1;
263 watcher1.StartWatching(
264 &event,
Hajime Hoshi138652c92018-01-12 15:11:44265 BindOnce(callback, Unretained(&current_run_loop), Unretained(&counter1)),
266 SequencedTaskRunnerHandle::Get());
Robert Sesekcfab3ad2017-08-01 16:25:43267
268 WaitableEventWatcher watcher2;
269 watcher2.StartWatching(
270 &event,
Hajime Hoshi138652c92018-01-12 15:11:44271 BindOnce(callback, Unretained(&current_run_loop), Unretained(&counter2)),
272 SequencedTaskRunnerHandle::Get());
Robert Sesekcfab3ad2017-08-01 16:25:43273
274 event.Signal();
275 {
276 RunLoop run_loop;
277 current_run_loop = &run_loop;
278 run_loop.Run();
279 }
280
281 // Only one of the waiters should have been signaled.
282 EXPECT_TRUE((counter1 == 1) ^ (counter2 == 1));
283
284 EXPECT_FALSE(event.IsSignaled());
285
286 event.Signal();
287 {
288 RunLoop run_loop;
289 current_run_loop = &run_loop;
290 run_loop.Run();
291 }
292
293 EXPECT_FALSE(event.IsSignaled());
294
295 // The other watcher should have been signaled.
296 EXPECT_EQ(1, counter1);
297 EXPECT_EQ(1, counter2);
298}
299
Robert Sesek3b3333772017-06-30 18:21:56300// To help detect errors around deleting WaitableEventWatcher, an additional
301// bool parameter is used to test sleeping between watching and deletion.
302class WaitableEventWatcherDeletionTest
Sami Kyostila3f49cb572018-11-19 13:01:09303 : public testing::TestWithParam<
Gabriel Charette694c3c332019-08-19 14:53:05304 std::tuple<test::TaskEnvironment::MainThreadType, bool>> {};
Robert Sesek3b3333772017-06-30 18:21:56305
306TEST_P(WaitableEventWatcherDeletionTest, DeleteUnder) {
Gabriel Charette694c3c332019-08-19 14:53:05307 test::TaskEnvironment::MainThreadType main_thread_type;
Robert Sesek3b3333772017-06-30 18:21:56308 bool delay_after_delete;
Sami Kyostila3f49cb572018-11-19 13:01:09309 std::tie(main_thread_type, delay_after_delete) = GetParam();
Robert Sesek3b3333772017-06-30 18:21:56310
[email protected]c891ab92009-03-26 18:28:19311 // Delete the WaitableEvent out from under the Watcher. This is explictly
312 // allowed by the interface.
313
Gabriel Charette694c3c332019-08-19 14:53:05314 test::TaskEnvironment task_environment(main_thread_type);
[email protected]c891ab92009-03-26 18:28:19315
316 {
317 WaitableEventWatcher watcher;
318
atuchin54d83152017-05-10 06:30:01319 auto* event = new WaitableEvent(WaitableEvent::ResetPolicy::AUTOMATIC,
320 WaitableEvent::InitialState::NOT_SIGNALED);
[email protected]329be052013-02-04 18:14:28321
Hajime Hoshi138652c92018-01-12 15:11:44322 watcher.StartWatching(event, BindOnce(&QuitWhenSignaled),
323 SequencedTaskRunnerHandle::Get());
atuchin54d83152017-05-10 06:30:01324
325 if (delay_after_delete) {
326 // On Windows that sleep() improves the chance to catch some problems.
327 // It postpones the dtor |watcher| (which immediately cancel the waiting)
328 // and gives some time to run to a created background thread.
329 // Unfortunately, that thread is under OS control and we can't
330 // manipulate it directly.
Hajime Hoshi138652c92018-01-12 15:11:44331 PlatformThread::Sleep(TimeDelta::FromMilliseconds(30));
atuchin54d83152017-05-10 06:30:01332 }
333
[email protected]c891ab92009-03-26 18:28:19334 delete event;
335 }
336}
337
Robert Sesek3b3333772017-06-30 18:21:56338TEST_P(WaitableEventWatcherDeletionTest, SignalAndDelete) {
Gabriel Charette694c3c332019-08-19 14:53:05339 test::TaskEnvironment::MainThreadType main_thread_type;
Robert Sesek3b3333772017-06-30 18:21:56340 bool delay_after_delete;
Sami Kyostila3f49cb572018-11-19 13:01:09341 std::tie(main_thread_type, delay_after_delete) = GetParam();
Robert Sesek3b3333772017-06-30 18:21:56342
atuchin54d83152017-05-10 06:30:01343 // Signal and immediately delete the WaitableEvent out from under the Watcher.
344
Gabriel Charette694c3c332019-08-19 14:53:05345 test::TaskEnvironment task_environment(main_thread_type);
atuchin54d83152017-05-10 06:30:01346
347 {
348 WaitableEventWatcher watcher;
349
Jeremy Roman9532f252017-08-16 23:27:24350 auto event = std::make_unique<WaitableEvent>(
atuchin54d83152017-05-10 06:30:01351 WaitableEvent::ResetPolicy::AUTOMATIC,
352 WaitableEvent::InitialState::NOT_SIGNALED);
353
Hajime Hoshi138652c92018-01-12 15:11:44354 watcher.StartWatching(event.get(), BindOnce(&QuitWhenSignaled),
355 SequencedTaskRunnerHandle::Get());
atuchin54d83152017-05-10 06:30:01356 event->Signal();
357 event.reset();
358
359 if (delay_after_delete) {
360 // On Windows that sleep() improves the chance to catch some problems.
361 // It postpones the dtor |watcher| (which immediately cancel the waiting)
362 // and gives some time to run to a created background thread.
363 // Unfortunately, that thread is under OS control and we can't
364 // manipulate it directly.
Hajime Hoshi138652c92018-01-12 15:11:44365 PlatformThread::Sleep(TimeDelta::FromMilliseconds(30));
atuchin54d83152017-05-10 06:30:01366 }
367
368 // Wait for the watcher callback.
369 RunLoop().Run();
370 }
371}
372
Robert Sesek6d38e782017-07-13 00:46:50373// Tests deleting the WaitableEventWatcher between signaling the event and
374// when the callback should be run.
375TEST_P(WaitableEventWatcherDeletionTest, DeleteWatcherBeforeCallback) {
Gabriel Charette694c3c332019-08-19 14:53:05376 test::TaskEnvironment::MainThreadType main_thread_type;
Robert Sesek6d38e782017-07-13 00:46:50377 bool delay_after_delete;
Sami Kyostila3f49cb572018-11-19 13:01:09378 std::tie(main_thread_type, delay_after_delete) = GetParam();
Robert Sesek6d38e782017-07-13 00:46:50379
Gabriel Charette694c3c332019-08-19 14:53:05380 test::TaskEnvironment task_environment(main_thread_type);
Robert Sesek6d38e782017-07-13 00:46:50381 scoped_refptr<SingleThreadTaskRunner> task_runner =
Sami Kyostila3f49cb572018-11-19 13:01:09382 ThreadTaskRunnerHandle::Get();
Robert Sesek6d38e782017-07-13 00:46:50383
384 // Flag used to esnure that the |watcher_callback| never runs.
385 bool did_callback = false;
386
387 WaitableEvent event(WaitableEvent::ResetPolicy::AUTOMATIC,
388 WaitableEvent::InitialState::NOT_SIGNALED);
Jeremy Roman9532f252017-08-16 23:27:24389 auto watcher = std::make_unique<WaitableEventWatcher>();
Robert Sesek6d38e782017-07-13 00:46:50390
391 // Queue up a series of tasks:
392 // 1. StartWatching the WaitableEvent
393 // 2. Signal the event (which will result in another task getting posted to
394 // the |task_runner|)
395 // 3. Delete the WaitableEventWatcher
396 // 4. WaitableEventWatcher callback should run (from #2)
397
398 WaitableEventWatcher::EventCallback watcher_callback = BindOnce(
399 [](bool* did_callback, WaitableEvent*) {
400 *did_callback = true;
401 },
402 Unretained(&did_callback));
403
404 task_runner->PostTask(
405 FROM_HERE, BindOnce(IgnoreResult(&WaitableEventWatcher::StartWatching),
406 Unretained(watcher.get()), Unretained(&event),
Hajime Hoshi138652c92018-01-12 15:11:44407 std::move(watcher_callback), task_runner));
Robert Sesek6d38e782017-07-13 00:46:50408 task_runner->PostTask(FROM_HERE,
409 BindOnce(&WaitableEvent::Signal, Unretained(&event)));
410 task_runner->DeleteSoon(FROM_HERE, std::move(watcher));
411 if (delay_after_delete) {
412 task_runner->PostTask(FROM_HERE, BindOnce(&PlatformThread::Sleep,
413 TimeDelta::FromMilliseconds(30)));
414 }
415
416 RunLoop().RunUntilIdle();
417
418 EXPECT_FALSE(did_callback);
419}
420
Victor Costan033b9ac2019-01-29 00:52:16421INSTANTIATE_TEST_SUITE_P(,
422 WaitableEventWatcherTest,
423 testing::ValuesIn(testing_main_threads));
[email protected]b57d33c52009-01-15 22:58:53424
Victor Costan033b9ac2019-01-29 00:52:16425INSTANTIATE_TEST_SUITE_P(
Robert Sesek3b3333772017-06-30 18:21:56426 ,
427 WaitableEventWatcherDeletionTest,
Sami Kyostila3f49cb572018-11-19 13:01:09428 testing::Combine(testing::ValuesIn(testing_main_threads), testing::Bool()));
[email protected]44f9c952011-01-02 06:05:39429
430} // namespace base