• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2   This file is part of Valgrind, a dynamic binary instrumentation
3   framework.
4 
5   Copyright (C) 2008-2008 Google Inc
6      opensource@google.com
7 
8   This program is free software; you can redistribute it and/or
9   modify it under the terms of the GNU General Public License as
10   published by the Free Software Foundation; either version 2 of the
11   License, or (at your option) any later version.
12 
13   This program is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16   General Public License for more details.
17 
18   You should have received a copy of the GNU General Public License
19   along with this program; if not, write to the Free Software
20   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
21   02111-1307, USA.
22 
23   The GNU General Public License is contained in the file COPYING.
24 */
25 
26 /* Author: Timur Iskhodzhanov <opensource@google.com>
27 
28  This file contains a set of Windows-specific unit tests for
29  a data race detection tool.
30 */
31 
32 #include <gtest/gtest.h>
33 #include "test_utils.h"
34 #include "gtest_fixture_injection.h"
35 
DummyWorker()36 void DummyWorker() {
37 }
38 
LongWorker()39 void LongWorker() {
40   Sleep(1);
41   volatile int i = 1 << 20;
42   while(i--);
43 }
44 
WriteWorker(int * var)45 void WriteWorker(int *var) {
46   LongWorker();
47   *var = 42;
48 }
49 
VeryLongWriteWorker(int * var)50 void VeryLongWriteWorker(int *var) {
51   Sleep(1000);
52   *var = 42;
53 }
54 
TEST(NegativeTests,WindowsCreateThreadFailureTest)55 TEST(NegativeTests, WindowsCreateThreadFailureTest) {  // {{{1
56   HANDLE t = ::CreateThread(0, -1,
57                            (LPTHREAD_START_ROUTINE)DummyWorker, 0, 0, 0);
58   CHECK(t == 0);
59 }
60 
TEST(NegativeTests,DISABLED_WindowsCreateThreadSuspendedTest)61 TEST(NegativeTests, DISABLED_WindowsCreateThreadSuspendedTest) {  // {{{1
62   // Hangs under TSan, see
63   // http://code.google.com/p/data-race-test/issues/detail?id=61
64   int *var = new int;
65   HANDLE t = ::CreateThread(0, 0,
66                            (LPTHREAD_START_ROUTINE)WriteWorker, var,
67                            CREATE_SUSPENDED, 0);
68   CHECK(t > 0);
69   EXPECT_EQ(WAIT_TIMEOUT,  ::WaitForSingleObject(t, 200));
70   *var = 1;
71   EXPECT_EQ(1, ResumeThread(t));
72   EXPECT_EQ(WAIT_OBJECT_0, ::WaitForSingleObject(t, INFINITE));
73   EXPECT_EQ(42, *var);
74   delete var;
75 }
76 
TEST(NegativeTests,WindowsThreadStackSizeTest)77 TEST(NegativeTests, WindowsThreadStackSizeTest) {  // {{{1
78 // Just spawn few threads with different stack sizes.
79   int sizes[3] = {1 << 19, 1 << 21, 1 << 22};
80   for (int i = 0; i < 3; i++) {
81     HANDLE t = ::CreateThread(0, sizes[i],
82                              (LPTHREAD_START_ROUTINE)DummyWorker, 0, 0, 0);
83     CHECK(t > 0);
84     EXPECT_EQ(WAIT_OBJECT_0, ::WaitForSingleObject(t, INFINITE));
85     CloseHandle(t);
86   }
87 }
88 
TEST(NegativeTests,WindowsJoinWithTimeout)89 TEST(NegativeTests, WindowsJoinWithTimeout) {  // {{{1
90   HANDLE t = ::CreateThread(0, 0,
91                             (LPTHREAD_START_ROUTINE)LongWorker, 0, 0, 0);
92   ASSERT_TRUE(t > 0);
93   EXPECT_EQ(WAIT_TIMEOUT,  ::WaitForSingleObject(t, 1));
94   EXPECT_EQ(WAIT_OBJECT_0, ::WaitForSingleObject(t, INFINITE));
95   CloseHandle(t);
96 }
97 
TEST(NegativeTests,HappensBeforeOnThreadJoin)98 TEST(NegativeTests, HappensBeforeOnThreadJoin) {  // {{{1
99   int *var = new int;
100   HANDLE t = ::CreateThread(0, 0,
101                             (LPTHREAD_START_ROUTINE)WriteWorker, var, 0, 0);
102   ASSERT_TRUE(t > 0);
103   // Calling WaitForSingleObject two times to make sure the H-B arc
104   // is created on the second call. There was a bug that the thread handle
105   // was deleted even when WaitForSingleObject timed out.
106   EXPECT_EQ(WAIT_TIMEOUT,  ::WaitForSingleObject(t, 1));
107   EXPECT_EQ(WAIT_OBJECT_0, ::WaitForSingleObject(t, INFINITE));
108   EXPECT_EQ(*var, 42);
109   CloseHandle(t);
110   delete var;
111 }
112 
TEST(NegativeTests,HappensBeforeOnThreadJoinTidReuse)113 TEST(NegativeTests, HappensBeforeOnThreadJoinTidReuse) {  // {{{1
114   HANDLE t1 = ::CreateThread(0, 0, (LPTHREAD_START_ROUTINE)DummyWorker, 0, 0, 0);
115   CloseHandle(t1);
116   Sleep(1000);
117 
118   int *var = new int;
119   HANDLE t2 = ::CreateThread(0, 0,
120                              (LPTHREAD_START_ROUTINE)WriteWorker, var, 0, 0);
121   printf("t1 = %d, t2 = %d\n");
122   CHECK(t2 > 0);
123   CHECK(WAIT_OBJECT_0 == ::WaitForSingleObject(t2, INFINITE));
124   CHECK(*var == 42);
125   delete var;
126 }
127 
TEST(NegativeTests,WaitForMultipleObjectsWaitAllTest)128 TEST(NegativeTests, WaitForMultipleObjectsWaitAllTest) {
129   int var1 = 13,
130       var2 = 13;
131   HANDLE t1 = ::CreateThread(0, 0,
132                             (LPTHREAD_START_ROUTINE)WriteWorker, &var1, 0, 0),
133          t2 = ::CreateThread(0, 0,
134                             (LPTHREAD_START_ROUTINE)WriteWorker, &var2, 0, 0);
135   ASSERT_TRUE(t1 > 0);
136   ASSERT_TRUE(t2 > 0);
137 
138   HANDLE handles[2] = {t1, t2};
139   // Calling WaitForMultipleObjectsTest two times to make sure the H-B arc
140   // are created on the second call.
141   EXPECT_EQ(WAIT_TIMEOUT,  ::WaitForMultipleObjects(2, handles, TRUE, 1));
142   EXPECT_EQ(WAIT_OBJECT_0, ::WaitForMultipleObjects(2, handles, TRUE, INFINITE));
143   EXPECT_EQ(var1, 42);
144   EXPECT_EQ(var2, 42);
145   CloseHandle(t1);
146   CloseHandle(t2);
147 }
148 
TEST(NegativeTests,WaitForMultipleObjectsWaitOneTest)149 TEST(NegativeTests, WaitForMultipleObjectsWaitOneTest) {
150   int var1 = 13,
151       var2 = 13;
152   HANDLE t1 = ::CreateThread(0, 0,
153                             (LPTHREAD_START_ROUTINE)VeryLongWriteWorker, &var1, 0, 0),
154          t2 = ::CreateThread(0, 0,
155                             (LPTHREAD_START_ROUTINE)WriteWorker, &var2, 0, 0);
156   ASSERT_TRUE(t1 > 0);
157   ASSERT_TRUE(t2 > 0);
158 
159   HANDLE handles[2] = {t1, t2};
160   // Calling WaitForMultipleObjectsTest two times to make sure the H-B arc
161   // are created on the second call.
162   EXPECT_EQ(WAIT_TIMEOUT,  ::WaitForMultipleObjects(2, handles, FALSE, 1));
163   EXPECT_EQ(WAIT_OBJECT_0 + 1, ::WaitForMultipleObjects(2, handles, FALSE, INFINITE));
164   EXPECT_EQ(var2, 42);
165   EXPECT_EQ(WAIT_OBJECT_0, ::WaitForMultipleObjects(1, handles, FALSE, INFINITE));
166   EXPECT_EQ(var1, 42);
167   CloseHandle(t1);
168   CloseHandle(t2);
169 }
170 
171 namespace RegisterWaitForSingleObjectTest {  // {{{1
172 StealthNotification *n = NULL;
173 HANDLE monitored_object = NULL;
174 
SignalStealthNotification()175 void SignalStealthNotification() {
176   n->wait();
177   SetEvent(monitored_object);
178 }
179 
foo()180 void foo() { }
181 
DoneWaiting(void * param,BOOLEAN timed_out)182 void CALLBACK DoneWaiting(void *param, BOOLEAN timed_out) {
183   int *i = (int*)param;
184   foo();  // make sure this function has a call. See issue 24.
185   (*i)++;
186 }
187 
TEST(NegativeTests,WindowsRegisterWaitForSingleObjectTest)188 TEST(NegativeTests, WindowsRegisterWaitForSingleObjectTest) {  // {{{1
189   // These are very tricky false positive found while testing Chromium.
190   //
191   // Report #1:
192   //   Everything after UnregisterWaitEx(*, INVALID_HANDLE_VALUE) happens-after
193   //   execution of DoneWaiting callback. Currently, we don't catch this h-b.
194   //
195   // Report #2:
196   //   The callback thread is re-used between Registet/Unregister/Register calls
197   //   so we miss h-b between "int *obj = ..." and DoneWaiting on the second
198   //   iteration.
199   for (int i = 0; i < 2; i++) {
200     n = new StealthNotification();
201     int *obj = new int(0);
202     HANDLE wait_object = NULL;
203 
204     monitored_object = ::CreateEvent(NULL, false, false, NULL);
205     printf("monitored_object = %p\n", monitored_object);
206     MyThread mt(SignalStealthNotification);
207     mt.Start();
208     ANNOTATE_TRACE_MEMORY(obj);
209     CHECK(0 != ::RegisterWaitForSingleObject(&wait_object, monitored_object,
210                                              DoneWaiting, obj, INFINITE,
211                                              WT_EXECUTEINWAITTHREAD | WT_EXECUTEONLYONCE));
212     printf("wait_object      = %p\n", wait_object);
213     n->signal();
214     mt.Join();
215     Sleep(1000);
216     CHECK(0 != ::UnregisterWaitEx(wait_object, INVALID_HANDLE_VALUE));
217     (*obj)++;
218     CHECK(*obj == 2);
219     CloseHandle(monitored_object);
220     delete n;
221     delete obj;
222   }
223 }
224 }
225 
226 namespace QueueUserWorkItemTests {
Callback(void * param)227 DWORD CALLBACK Callback(void *param) {
228   int *ptr = (int*)param;
229   (*ptr)++;
230   delete ptr;
231   return 0;
232 }
233 
TEST(NegativeTests,WindowsQueueUserWorkItemTest)234 TEST(NegativeTests, WindowsQueueUserWorkItemTest) {
235   // False positive:
236   //   The callback thread is allocated from a thread pool and can be re-used.
237   //   As a result, we may miss h-b between "int *obj = ..." and Callback execution.
238   for (int i = 0; i < 5; i++) {
239     int *obj = new int(0);
240     ANNOTATE_TRACE_MEMORY(obj);
241     CHECK(QueueUserWorkItem(Callback, obj, i % 2 ? WT_EXECUTELONGFUNCTION : 0));
242     Sleep(500);
243   }
244 }
245 
246 int GLOB = 42;
247 
Callback2(void * param)248 DWORD CALLBACK Callback2(void *param) {
249   StealthNotification *ptr = (StealthNotification*)param;
250   GLOB++;
251   Sleep(100);
252   ptr->signal();
253   return 0;
254 }
255 
TEST(PositiveTests,WindowsQueueUserWorkItemTest)256 TEST(PositiveTests, WindowsQueueUserWorkItemTest) {
257   ANNOTATE_EXPECT_RACE_FOR_TSAN(&GLOB, "PositiveTests.WindowsQueueUserWorkItemTest");
258 
259   const int N_THREAD = 5;
260   StealthNotification n[N_THREAD];
261 
262   for (int i = 0; i < N_THREAD; i++)
263     CHECK(QueueUserWorkItem(Callback2, &n[i], i % 2 ? WT_EXECUTELONGFUNCTION : 0));
264 
265   for (int i = 0; i < N_THREAD; i++)
266     n[i].wait();
267 }
268 }
269 
270 namespace WindowsCriticalSectionTest {  // {{{1
271 CRITICAL_SECTION cs;
272 
TEST(NegativeTests,WindowsCriticalSectionTest)273 TEST(NegativeTests, WindowsCriticalSectionTest) {
274   InitializeCriticalSection(&cs);
275   EnterCriticalSection(&cs);
276   TryEnterCriticalSection(&cs);
277   LeaveCriticalSection(&cs);
278   DeleteCriticalSection(&cs);
279 }
280 }  // namespace
281 
282 
283 namespace WindowsSRWLockTest {  // {{{1
284 #if WINVER >= 0x0600 // Vista or Windows Server 2000
285 SRWLOCK SRWLock;
286 int *obj;
287 
Reader()288 void Reader() {
289   AcquireSRWLockShared(&SRWLock);
290   CHECK(*obj <= 2 && *obj >= 0);
291   ReleaseSRWLockShared(&SRWLock);
292 }
293 
Writer()294 void Writer() {
295   AcquireSRWLockExclusive(&SRWLock);
296   (*obj)++;
297   ReleaseSRWLockExclusive(&SRWLock);
298 }
299 
300 #if 0  // This doesn't work in older versions of Windows.
301 void TryReader() {
302   if (TryAcquireSRWLockShared(&SRWLock)) {
303     CHECK(*obj <= 2 && *obj >= 0);
304     ReleaseSRWLockShared(&SRWLock);
305   }
306 }
307 
308 void TryWriter() {
309   if (TryAcquireSRWLockExclusive(&SRWLock)) {
310     (*obj)++;
311     ReleaseSRWLockExclusive(&SRWLock);
312   }
313 }
314 #endif
315 
TEST(NegativeTests,WindowsSRWLockTest)316 TEST(NegativeTests, WindowsSRWLockTest) {
317   InitializeSRWLock(&SRWLock);
318   obj = new int(0);
319   ANNOTATE_TRACE_MEMORY(obj);
320   MyThreadArray t(Reader, Writer, Reader, Writer);
321   t.Start();
322   t.Join();
323   AcquireSRWLockShared(&SRWLock);
324   ReleaseSRWLockShared(&SRWLock);
325   CHECK(*obj == 2);
326   delete obj;
327 }
328 
TEST(NegativeTests,WindowsSRWLockHackyInitializationTest)329 TEST(NegativeTests, WindowsSRWLockHackyInitializationTest) {
330   // A similar pattern has been found on Chromium media_unittests
331   InitializeSRWLock(&SRWLock);
332   AcquireSRWLockExclusive(&SRWLock);
333   // Leave the lock acquired
334 
335   // Reset the lock
336   InitializeSRWLock(&SRWLock);
337   obj = new int(0);
338   MyThreadArray t(Reader, Writer, Reader, Writer);
339   t.Start();
340   t.Join();
341   delete obj;
342 }
343 #endif // WINVER >= 0x0600
344 }  // namespace
345 
346 namespace WindowsConditionVariableSRWTest {  // {{{1
347 #if WINVER >= 0x0600 // Vista or Windows Server 2000
348 SRWLOCK SRWLock;
349 CONDITION_VARIABLE cv;
350 bool cond;
351 int *obj;
352 
353 StealthNotification n;
354 
WaiterSRW()355 void WaiterSRW() {
356   *obj = 1;
357   n.wait();
358   AcquireSRWLockExclusive(&SRWLock);
359   cond = true;
360   WakeConditionVariable(&cv);
361   ReleaseSRWLockExclusive(&SRWLock);
362 }
363 
WakerSRW()364 void WakerSRW() {
365   AcquireSRWLockExclusive(&SRWLock);
366   n.signal();
367   while (!cond) {
368     SleepConditionVariableSRW(&cv, &SRWLock, 10, 0);
369   }
370   ReleaseSRWLockExclusive(&SRWLock);
371   CHECK(*obj == 1);
372   *obj = 2;
373 }
374 
TEST(NegativeTests,WindowsConditionVariableSRWTest)375 TEST(NegativeTests, WindowsConditionVariableSRWTest) {
376   InitializeSRWLock(&SRWLock);
377   InitializeConditionVariable(&cv);
378   obj = new int(0);
379   cond = false;
380   ANNOTATE_TRACE_MEMORY(obj);
381   MyThreadArray t(WaiterSRW, WakerSRW);
382   t.Start();
383   t.Join();
384   CHECK(*obj == 2);
385   delete obj;
386 }
387 #endif // WINVER >= 0x0600
388 }  // namespace
389 
390 
391 namespace WindowsInterlockedListTest {  // {{{1
392 SLIST_HEADER list;
393 
394 struct Item {
395   SLIST_ENTRY entry;
396   int foo;
397 };
398 
Push()399 void Push() {
400   Item *item = new Item;
401   item->foo = 42;
402   InterlockedPushEntrySList(&list, (PSINGLE_LIST_ENTRY)item);
403 }
404 
Pop()405 void Pop() {
406   Item *item;
407   while (0 == (item = (Item*)InterlockedPopEntrySList(&list))) {
408     Sleep(1);
409   }
410   CHECK(item->foo == 42);
411   delete item;
412 }
413 
TEST(NegativeTests,WindowsInterlockedListTest)414 TEST(NegativeTests, WindowsInterlockedListTest) {
415   InitializeSListHead(&list);
416   MyThreadArray t(Push, Pop);
417   t.Start();
418   t.Join();
419 }
420 
421 }  // namespace
422 
423 namespace FileSystemReports {  // {{{1
424 
425 // This is a test for the flaky report found in
426 // Chromium net_unittests.
427 //
428 // Looks like the test is sensitive to memory allocations / scheduling order,
429 // so you shouldn't run other tests while investigating the issue.
430 // The report is ~50% flaky.
431 
432 HANDLE hDone = NULL;
433 
CreateFileJob()434 void CreateFileJob() {
435   HANDLE hFile = CreateFileA("ZZZ\\tmpfile", GENERIC_READ | GENERIC_WRITE,
436                       FILE_SHARE_READ, NULL, CREATE_ALWAYS,
437                       FILE_ATTRIBUTE_NORMAL, NULL);
438   CloseHandle(hFile);
439   DWORD attr1 = GetFileAttributes("ZZZ");  // "Concurrent write" is here.
440 }
441 
PrintDirectoryListingJob(void * param)442 DWORD CALLBACK PrintDirectoryListingJob(void *param) {
443   Sleep(500);
444   WIN32_FIND_DATAA data;
445 
446   // "Current write" is here.
447   HANDLE hFind = FindFirstFileA("ZZZ/*", &data);
448   CHECK(hFind != INVALID_HANDLE_VALUE);
449 
450   CloseHandle(hFind);
451   SetEvent(hDone);
452   return 0;
453 }
454 
455 // This test is not very friendly to bots environment, so you should only
456 // run it manually.
TEST(NegativeTests,DISABLED_CreateFileVsFindFirstFileTest)457 TEST(NegativeTests, DISABLED_CreateFileVsFindFirstFileTest) {
458   hDone = ::CreateEvent(NULL, false, false, NULL);
459 
460   ::CreateDirectory("ZZZ", NULL);
461 
462   // Run PrintDirectoryListingJob in a concurrent thread.
463   CHECK(::QueueUserWorkItem(PrintDirectoryListingJob, NULL,
464                           WT_EXECUTELONGFUNCTION));
465   CreateFileJob();
466 
467   ::WaitForSingleObject(hDone, INFINITE);
468   ::CloseHandle(hDone);
469   CHECK(::DeleteFile("ZZZ\\tmpfile"));
470   CHECK(::RemoveDirectory("ZZZ"));
471 }
472 
473 }  //namespace
474 
475 namespace WindowsAtomicsTests { // {{{1
476 // This test should not give us any reports if compiled with proper MSVS flags.
477 // The Atomic_{Read,Write} functions are ignored in racecheck_unittest.ignore
478 
479 int GLOB = 42;
480 
Atomic_Read(volatile const int * ptr)481 inline int Atomic_Read(volatile const int* ptr) {
482   // MSVS volatile gives us atomicity.
483   int value = *ptr;
484   return value;
485 }
486 
Atomic_Write(volatile int * ptr,int value)487 inline void Atomic_Write(volatile int* ptr, int value) {
488   // MSVS volatile gives us atomicity.
489   *ptr = value;
490 }
491 
Worker()492 void Worker() {
493   int value = Atomic_Read(&GLOB);
494   Atomic_Write(&GLOB, ~value);
495 }
496 
TEST(NegativeTests,WindowsAtomicsTests)497 TEST(NegativeTests, WindowsAtomicsTests) {
498   MyThreadArray mta(Worker, Worker);
499   mta.Start();
500   mta.Join();
501 }
502 
503 }  // namespace
504 
505 namespace WindowsSemaphoreTests {
Poster(int * var,HANDLE sem)506 void Poster(int *var, HANDLE sem) {
507   *var = 1;
508   ReleaseSemaphore(sem, 1, NULL);
509 }
510 
Waiter(int * var,HANDLE sem)511 void Waiter(int *var, HANDLE sem) {
512   DWORD ret = ::WaitForSingleObject(sem, INFINITE);
513   ASSERT_EQ(ret, WAIT_OBJECT_0);
514   EXPECT_EQ(*var, 1);
515 }
516 
TEST(NegativeTests,SimpleSemaphoreTest)517 TEST(NegativeTests, SimpleSemaphoreTest) {
518   HANDLE sem = CreateSemaphore(NULL,
519                                0 /* initial count */,
520                                20 /* max count */,
521                                NULL);
522   ASSERT_TRUE(sem != NULL);
523 
524   {
525     int VAR = 0;
526     ThreadPool tp(2);
527     tp.StartWorkers();
528     tp.Add(NewCallback(Waiter, &VAR, sem));
529     tp.Add(NewCallback(Poster, &VAR, sem));
530   }
531 
532   CloseHandle(sem);
533 }
534 
TEST(NegativeTests,DISABLED_SemaphoreNameReuseTest)535 TEST(NegativeTests, DISABLED_SemaphoreNameReuseTest) {
536   // TODO(timurrrr): Semaphore reuse is not yet understood by TSan.
537   const char NAME[] = "SemaphoreZZZ";
538   HANDLE h1 = CreateSemaphore(NULL, 0, 10, NAME),
539          h2 = CreateSemaphore(NULL, 0, 15, NAME);
540   ASSERT_TRUE(h1 != NULL);
541   ASSERT_TRUE(h2 != NULL);
542 
543   // h1 and h2 refer to the same semaphore but are not equal.
544   EXPECT_NE(h1, h2);
545 
546   {
547     int VAR = 0;
548     ThreadPool tp(2);
549     tp.StartWorkers();
550     tp.Add(NewCallback(Waiter, &VAR, h1));
551     tp.Add(NewCallback(Poster, &VAR, h2));
552   }
553 
554   CloseHandle(h1);
555   CloseHandle(h2);
556 }
557 
558 }
559 
560 namespace HandleReuseTests {
561 
Waker(int * var,HANDLE h)562 void Waker(int *var, HANDLE h) {
563   *var = 1;
564   SetEvent(h);
565 }
566 
Waiter(int * var,HANDLE h)567 void Waiter(int *var, HANDLE h) {
568   DWORD ret = ::WaitForSingleObject(h, INFINITE);
569   ASSERT_EQ(ret, WAIT_OBJECT_0);
570   EXPECT_EQ(*var, 1);
571 }
572 
TEST(NegativeTests,DISABLED_EventHandleReuseTest)573 TEST(NegativeTests, DISABLED_EventHandleReuseTest) {
574   // TODO(timurrrr): DuplicateHandle is not yet understood by TSan.
575   HANDLE h1 = CreateEvent(NULL, false, false, NULL);
576   ASSERT_TRUE(h1 != NULL);
577   HANDLE h2 = NULL;
578   DuplicateHandle(GetCurrentProcess(), h1,
579                   GetCurrentProcess(), &h2,
580                   0 /* access */, FALSE /* inherit*/, DUPLICATE_SAME_ACCESS);
581   ASSERT_TRUE(h2 != NULL);
582 
583   // h1 and h2 refer to the same Event but are not equal.
584   EXPECT_NE(h1, h2);
585 
586   {
587     int VAR = 0;
588     ThreadPool tp(2);
589     tp.StartWorkers();
590     tp.Add(NewCallback(Waiter, &VAR, h1));
591     tp.Add(NewCallback(Waker,  &VAR, h2));
592   }
593 
594   CloseHandle(h1);
595   CloseHandle(h2);
596 }
597 
598 }
599 // End {{{1
600  // vim:shiftwidth=2:softtabstop=2:expandtab:foldmethod=marker
601