1 /**
2 Mutexes, semaphores and condition variables.
3 
4 Copyright: Sean Kelly 2005 - 2009.
5 Copyright: Guillaume Piolat 2016 - 2018.
6 License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
7 */
8 // This contains part of druntime's core.sys.mutex, core.sys.semaphore core.sys.condition and
9 // Modified to make it @nogc nothrow
10 module dplug.core.sync;
11 
12 import core.time;
13 import core.atomic;
14 
15 import dplug.core.vec;
16 import dplug.core.nogc;
17 
18 import core.stdc.stdio;
19 import core.stdc.stdlib: malloc, free;
20 
21 // emulate conditions variable on Windows
22 // (this was originally in Phobos for XP compatibility)
23 version = emulateCondVarWin32;
24 
25 version (OSX)
26     version = Darwin;
27 else version (iOS)
28     version = Darwin;
29 else version (TVOS)
30     version = Darwin;
31 else version (WatchOS)
32     version = Darwin;
33 
34 version( Windows )
35 {
36     import core.sys.windows.windows;
37 
38     extern (Windows) nothrow @nogc
39     {
40         void InitializeCriticalSectionAndSpinCount(CRITICAL_SECTION * lpCriticalSection, DWORD dwSpinCount);
41     }
42 }
43 else version( Darwin )
44 {
45     import core.sys.posix.pthread;
46     import core.sync.config;
47     import core.stdc.errno;
48     import core.sys.posix.time;
49     static if (__VERSION__ < 2084)
50         import core.sys.osx.mach.semaphore; // was removed with DMDFE 2.084
51     else 
52         import core.sys.darwin.mach.semaphore;
53 }
54 else version( Posix )
55 {
56     import core.sync.config;
57     import core.stdc.errno;
58     import core.sys.posix.pthread;
59     import core.sys.posix.semaphore;
60     import core.sys.posix.time;
61 }
62 else
63 {
64     static assert(false, "Platform not supported");
65 }
66 
67 
68 //
69 // MUTEX
70 //
71 
72 /// Returns: A newly created `UnchekedMutex`.
73 UncheckedMutex makeMutex() nothrow @nogc
74 {
75     return UncheckedMutex(42);
76 }
77 
78 private enum PosixMutexAlignment = 64; // Wild guess, no measurements done
79 
80 // Cargo-culting the spin-count in WTF::Lock
81 // See: https://webkit.org/blog/6161/locking-in-webkit/
82 private enum CriticalSectionSpinCount = 40;
83 
84 struct UncheckedMutex
85 {
86 nothrow @nogc:
87 
88     private this(int dummyArg)
89     {
90         assert(!_created);
91         lazyThreadSafeInitialization();
92     }
93 
94     ~this()
95     {
96         // RACE CONDITION HERE
97         // TODO: not easy to make the mutex destruction thread-safe, because one of the
98         // thread must wait. Ignore that for now.
99 
100         void* mutexHandle = atomicLoad(_mutex); // Can be the mutex handle, or null.
101 
102         if (mutexHandle !is null)
103         {
104             // Now, the mutex could have been destroyed by another thread already.
105             // Make a cas to ensure we are first to attempt it.
106 
107             if (cas(&_mutex, &mutexHandle, null))
108             {
109                 destroyMutex(mutexHandle);
110             }
111         }
112 
113         _created = 0;
114     }
115 
116     @disable this(this);
117 
118     /// Lock mutex, and create it in a thread-safe way if it doens't exist yet.
119     /// This is useful when globals access must be protected by a mutex.
120     void lockLazy() @trusted
121     {
122         lazyThreadSafeInitialization();
123         lock();
124     }
125 
126     /// Lock mutex
127     void lock()
128     {
129         // No sync needed, else would have used lockLazy()
130         version( Windows )
131         {
132             EnterCriticalSection( cast(CRITICAL_SECTION*) _mutex ); 
133         }
134         else version( Posix )
135         {
136             assumeNothrowNoGC(
137                 (pthread_mutex_t* handle)
138                 {
139                     int res = pthread_mutex_lock(handle);
140                     if (res != 0)
141                         assert(false);
142                 })(handleAddr());
143         }
144     }
145 
146     // undocumented function for internal use
147     void unlock()
148     {
149         version( Windows )
150         {
151             LeaveCriticalSection( cast(CRITICAL_SECTION*) _mutex );
152         }
153         else version( Posix )
154         {
155             assumeNothrowNoGC(
156                 (pthread_mutex_t* handle)
157                 {
158                     int res = pthread_mutex_unlock(handle);
159                     if (res != 0)
160                         assert(false);
161                 })(handleAddr());
162         }
163     }
164 
165     bool tryLock()
166     {
167         version( Windows )
168         {
169             return TryEnterCriticalSection( cast(CRITICAL_SECTION*) _mutex ) != 0;
170         }
171         else version( Posix )
172         {
173             int result = assumeNothrowNoGC(
174                 (pthread_mutex_t* handle)
175                 {
176                     return pthread_mutex_trylock(handle);
177                 })(handleAddr());
178             return result == 0;
179         }
180     }
181 
182 private:
183     // on Windows, this is a CRITICAL_SECTION*.
184     // else, this is a pthread_mutex_t*
185     void* _mutex;
186 
187     // Work-around for Issue 16636
188     // https://issues.dlang.org/show_bug.cgi?id=16636
189     // Still crash with LDC somehow
190     long _created = 0;
191 
192     static void* createMutex()
193     {
194         version( Windows )
195         {
196             CRITICAL_SECTION* critSec = cast(CRITICAL_SECTION*) malloc(CRITICAL_SECTION.sizeof);
197             InitializeCriticalSectionAndSpinCount( critSec, CriticalSectionSpinCount );
198             return critSec;
199         }
200         else version( Posix )
201         {
202             pthread_mutex_t* pmtx = cast(pthread_mutex_t*)( alignedMalloc(pthread_mutex_t.sizeof, PosixMutexAlignment) );
203 
204             assumeNothrowNoGC(
205                 (pthread_mutex_t* handle)
206                 {
207                     pthread_mutexattr_t attr = void;
208                     pthread_mutexattr_init( &attr );
209                     pthread_mutexattr_settype( &attr, PTHREAD_MUTEX_RECURSIVE );
210 
211                     version (Darwin)
212                     {
213                         // Note: disabled since this breaks thread pool.
214                         /+
215                             // OSX mutexes are fair by default, but this has a cost for contended locks
216                             // Disable fairness.
217                             // https://blog.mozilla.org/nfroyd/2017/03/29/on-mutex-performance-part-1/
218                             enum _PTHREAD_MUTEX_POLICY_FIRSTFIT = 2;
219                             pthread_mutexattr_setpolicy_np(& attr, _PTHREAD_MUTEX_POLICY_FIRSTFIT);
220                         +/
221                     }
222 
223                     pthread_mutex_init( handle, &attr );
224 
225                 })(pmtx);
226                 return pmtx;
227         }
228         else
229             static assert(false);
230     }
231 
232     static void destroyMutex(void* mutex)
233     {
234         if (mutex is null)
235             return;
236 
237         // TODO: this should be thread-safe, for example a cas with local variable name or _created
238 
239         version( Windows )
240         {
241             DeleteCriticalSection( cast(CRITICAL_SECTION*) mutex );
242             free(mutex);
243         }
244         else version( Posix )
245         {
246             assumeNothrowNoGC(
247                 (pthread_mutex_t* handle)
248                 {
249                     pthread_mutex_destroy(handle);
250                 })( cast(pthread_mutex_t*) mutex );
251             alignedFree(mutex, PosixMutexAlignment);
252         }
253         else
254             static assert(false);
255     }
256 
257     void lazyThreadSafeInitialization() @trusted
258     {
259         // Is there an existing mutex already?
260         if (atomicLoad(_mutex) !is null)
261             return;
262 
263         // Create one mutex.
264         void* mtx = createMutex();
265         void* p = cast(void*)mtx;
266         void** here = &_mutex;
267         void* ifThis = null;
268 
269         // Try to set _mutex.
270         if (!cas(here, &ifThis, p))
271         {
272             // Another thread created _mutex first. Destroy our useless instance.
273             destroyMutex(mtx);
274         }
275     }
276 
277 package:
278     version( Posix )
279     {
280         pthread_mutex_t* handleAddr() nothrow @nogc
281         {
282             return cast(pthread_mutex_t*) _mutex;
283         }
284     }
285 }
286 
287 unittest
288 {
289     UncheckedMutex mutex = makeMutex();
290     foreach(i; 0..100)
291     {
292         mutex.lock();
293         mutex.unlock();
294 
295         if (mutex.tryLock)
296             mutex.unlock();
297     }
298     mutex.destroy();
299 }
300 
301 unittest
302 {
303     __gshared UncheckedMutex mutex2;
304     mutex2.lockLazy();
305     mutex2.unlock();
306 }
307 
308 
309 
310 //
311 // SEMAPHORE
312 //
313 
314 /// Returns: A newly created `UncheckedSemaphore`
315 UncheckedSemaphore makeSemaphore(uint count) nothrow @nogc
316 {
317     return UncheckedSemaphore(count);
318 }
319 
320 struct UncheckedSemaphore
321 {
322     private this( uint count ) nothrow @nogc
323     {
324         version( Windows )
325         {
326             m_hndl = CreateSemaphoreA( null, count, int.max, null );
327             if( m_hndl == m_hndl.init )
328                 assert(false);
329         }
330         else version( Darwin )
331         {
332             mach_port_t task = assumeNothrowNoGC(
333                 ()
334                 {
335                     return mach_task_self();
336                 })();
337 
338             kern_return_t rc = assumeNothrowNoGC(
339                 (mach_port_t t, semaphore_t* handle, uint count)
340                 {
341                     return semaphore_create(t, handle, SYNC_POLICY_FIFO, count );
342                 })(task, &m_hndl, count);
343 
344             if( rc )
345                  assert(false);
346         }
347         else version( Posix )
348         {
349             int rc = sem_init( &m_hndl, 0, count );
350             if( rc )
351                 assert(false);
352         }
353         _created = 1;
354     }
355 
356     ~this() nothrow @nogc
357     {
358         if (_created)
359         {
360             version( Windows )
361             {
362                 BOOL rc = CloseHandle( m_hndl );
363                 assert( rc, "Unable to destroy semaphore" );
364             }
365             else version( Darwin )
366             {
367                 mach_port_t task = assumeNothrowNoGC(
368                     ()
369                     {
370                         return mach_task_self();
371                     })();
372 
373                 kern_return_t rc = assumeNothrowNoGC(
374                     (mach_port_t t, semaphore_t handle)
375                     {
376                         return semaphore_destroy( t, handle );
377                     })(task, m_hndl);
378 
379                 assert( !rc, "Unable to destroy semaphore" );
380             }
381             else version( Posix )
382             {
383                 int rc = sem_destroy( &m_hndl );
384                 assert( !rc, "Unable to destroy semaphore" );
385             }
386             _created = 0;
387         }
388     }
389 
390     @disable this(this);
391 
392     void wait() nothrow @nogc
393     {
394         version( Windows )
395         {
396             DWORD rc = WaitForSingleObject( m_hndl, INFINITE );
397             assert( rc == WAIT_OBJECT_0 );
398         }
399         else version( Darwin )
400         {
401             while( true )
402             {
403                 auto rc = assumeNothrowNoGC(
404                     (semaphore_t handle)
405                     {
406                         return semaphore_wait(handle);
407                     })(m_hndl);
408                 if( !rc )
409                     return;
410                 if( rc == KERN_ABORTED && errno == EINTR )
411                     continue;
412                 assert(false);
413             }
414         }
415         else version( Posix )
416         {
417             while( true )
418             {
419                 if (!assumeNothrowNoGC(
420                     (sem_t* handle)
421                     {
422                         return sem_wait(handle);
423                     })(&m_hndl))
424                     return;
425                 if( errno != EINTR )
426                     assert(false);
427             }
428         }
429     }
430 
431     bool wait( Duration period ) nothrow @nogc
432     {
433         assert( !period.isNegative );
434 
435         version( Windows )
436         {
437             auto maxWaitMillis = dur!("msecs")( uint.max - 1 );
438 
439             while( period > maxWaitMillis )
440             {
441                 auto rc = WaitForSingleObject( m_hndl, cast(uint)
442                                                maxWaitMillis.total!"msecs" );
443                 switch( rc )
444                 {
445                     case WAIT_OBJECT_0:
446                         return true;
447                     case WAIT_TIMEOUT:
448                         period -= maxWaitMillis;
449                         continue;
450                     default:
451                          assert(false);
452                 }
453             }
454             switch( WaitForSingleObject( m_hndl, cast(uint) period.total!"msecs" ) )
455             {
456                 case WAIT_OBJECT_0:
457                     return true;
458                 case WAIT_TIMEOUT:
459                     return false;
460                 default:
461                     assert(false);
462             }
463         }
464         else version( Darwin )
465         {
466             mach_timespec_t t = void;
467             (cast(byte*) &t)[0 .. t.sizeof] = 0;
468 
469             if( period.total!"seconds" > t.tv_sec.max )
470             {
471                 t.tv_sec  = t.tv_sec.max;
472                 t.tv_nsec = cast(typeof(t.tv_nsec)) period.split!("seconds", "nsecs")().nsecs;
473             }
474             else
475                 period.split!("seconds", "nsecs")(t.tv_sec, t.tv_nsec);
476             while( true )
477             {
478                 auto rc = assumeNothrowNoGC(
479                             (semaphore_t handle, mach_timespec_t t)
480                             {
481                                 return semaphore_timedwait(handle, t);
482                             })(m_hndl, t);
483                 if( !rc )
484                     return true;
485                 if( rc == KERN_OPERATION_TIMED_OUT )
486                     return false;
487                 if( rc != KERN_ABORTED || errno != EINTR )
488                      assert(false);
489             }
490         }
491         else version( Posix )
492         {
493             timespec t;
494 
495             assumeNothrowNoGC(
496                 (ref timespec t, Duration period)
497                 {
498                     mktspec( t, period );
499                 })(t, period);
500 
501             while( true )
502             {
503                 if (! ((sem_t* handle, timespec* t)
504                        {
505                             return sem_timedwait(handle, t);
506                        })(&m_hndl, &t))
507                     return true;
508                 if( errno == ETIMEDOUT )
509                     return false;
510                 if( errno != EINTR )
511                     assert(false);
512             }
513         }
514     }
515 
516     void notify()  nothrow @nogc
517     {
518         version( Windows )
519         {
520             if( !ReleaseSemaphore( m_hndl, 1, null ) )
521                 assert(false);
522         }
523         else version( Darwin )
524         {
525            auto rc = assumeNothrowNoGC(
526                         (semaphore_t handle)
527                         {
528                             return semaphore_signal(handle);
529                         })(m_hndl);
530             if( rc )
531                 assert(false);
532         }
533         else version( Posix )
534         {
535             int rc = sem_post( &m_hndl );
536             if( rc )
537                 assert(false);
538         }
539     }
540 
541     bool tryWait() nothrow @nogc
542     {
543         version( Windows )
544         {
545             switch( WaitForSingleObject( m_hndl, 0 ) )
546             {
547                 case WAIT_OBJECT_0:
548                     return true;
549                 case WAIT_TIMEOUT:
550                     return false;
551                 default:
552                     assert(false);
553             }
554         }
555         else version( Darwin )
556         {
557             return wait( dur!"hnsecs"(0) );
558         }
559         else version( Posix )
560         {
561             while( true )
562             {
563                 if( !sem_trywait( &m_hndl ) )
564                     return true;
565                 if( errno == EAGAIN )
566                     return false;
567                 if( errno != EINTR )
568                     assert(false);
569             }
570         }
571     }
572 
573 
574 private:
575     version( Windows )
576     {
577         HANDLE  m_hndl;
578     }
579     else version( Darwin )
580     {
581         semaphore_t m_hndl;
582     }
583     else version( Posix )
584     {
585         sem_t   m_hndl;
586     }
587     ulong _created = 0;
588 }
589 
590 
591 unittest
592 {
593     foreach(j; 0..4)
594     {
595         UncheckedSemaphore semaphore = makeSemaphore(1);
596         foreach(i; 0..100)
597         {
598             semaphore.wait();
599             semaphore.notify();
600             if (semaphore.tryWait())
601                 semaphore.notify();
602         }
603     }
604 }
605 
606 
607 
608 //
609 // CONDITION VARIABLE
610 //
611 
612 
613 ConditionVariable makeConditionVariable() nothrow @nogc
614 {
615     return ConditionVariable(42);
616 }
617 
618 /**
619 * This struct represents a condition variable as conceived by C.A.R. Hoare.  As
620 * per Mesa type monitors however, "signal" has been replaced with "notify" to
621 * indicate that control is not transferred to the waiter when a notification
622 * is sent.
623 */
624 struct ConditionVariable
625 {
626 public:
627 nothrow:
628 @nogc:
629 
630     /// Initializes a condition variable.
631     this(int dummy)
632     {
633         version( Windows )
634         {
635             m_blockLock = CreateSemaphoreA( null, 1, 1, null );
636             if( m_blockLock == m_blockLock.init )
637                 assert(false);
638             m_blockQueue = CreateSemaphoreA( null, 0, int.max, null );
639             if( m_blockQueue == m_blockQueue.init )
640                 assert(false);
641 
642             m_unblockLock = cast(CRITICAL_SECTION*) malloc(CRITICAL_SECTION.sizeof);
643             InitializeCriticalSectionAndSpinCount( m_unblockLock, CriticalSectionSpinCount );
644         }
645         else version( Posix )
646         {
647             _handle = cast(pthread_cond_t*)( alignedMalloc(pthread_cond_t.sizeof, PosixMutexAlignment) );
648 
649             int rc = pthread_cond_init( handleAddr(), null );
650             if( rc )
651                 assert(false);
652         }
653     }
654 
655 
656     ~this()
657     {
658         version( Windows )
659         {
660             CloseHandle( m_blockLock );
661             CloseHandle( m_blockQueue );
662             if (m_unblockLock !is null)
663             {
664                 DeleteCriticalSection( m_unblockLock );
665                 free(m_unblockLock);
666                 m_unblockLock = null;
667             }
668         }
669         else version( Posix )
670         {
671             if (_handle !is null)
672             {
673                 int rc = pthread_cond_destroy( handleAddr() );
674                 assert( !rc, "Unable to destroy condition" );
675                 alignedFree(_handle, PosixMutexAlignment);
676                 _handle = null;
677             }
678         }
679     }
680 
681     /// Wait until notified.
682     /// The associated mutex should always be the same for this condition variable.
683     void wait(UncheckedMutex* assocMutex)
684     {
685         version( Windows )
686         {
687             timedWait( INFINITE, assocMutex );
688         }
689         else version( Posix )
690         {
691             int rc = pthread_cond_wait( handleAddr(), assocMutex.handleAddr() );
692             if( rc )
693                 assert(false);
694         }
695     }
696 
697     /// Notifies one waiter.
698     void notifyOne()
699     {
700         version( Windows )
701         {
702             notifyImpl( false );
703         }
704         else version( Posix )
705         {
706             int rc = pthread_cond_signal( handleAddr() );
707             if( rc )
708                 assert(false);
709         }
710     }
711 
712 
713     /// Notifies all waiters.
714     void notifyAll()
715     {
716         version( Windows )
717         {
718             notifyImpl( true );
719         }
720         else version( Posix )
721         {
722             int rc = pthread_cond_broadcast( handleAddr() );
723             if( rc )
724                 assert(false);
725         }
726     }
727 
728     version(Posix)
729     {
730         pthread_cond_t* handleAddr() nothrow @nogc
731         {
732             return _handle;
733         }
734     }
735 
736 
737 private:
738     version( Windows )
739     {
740         bool timedWait( DWORD timeout, UncheckedMutex* assocMutex )
741         {
742             int   numSignalsLeft;
743             int   numWaitersGone;
744             DWORD rc;
745 
746             rc = WaitForSingleObject( m_blockLock, INFINITE );
747             assert( rc == WAIT_OBJECT_0 );
748 
749             m_numWaitersBlocked++;
750 
751             rc = ReleaseSemaphore( m_blockLock, 1, null );
752             assert( rc );
753 
754             assocMutex.unlock();
755 
756             rc = WaitForSingleObject( m_blockQueue, timeout );
757             assert( rc == WAIT_OBJECT_0 || rc == WAIT_TIMEOUT );
758             bool timedOut = (rc == WAIT_TIMEOUT);
759 
760             EnterCriticalSection( m_unblockLock );
761 
762             if( (numSignalsLeft = m_numWaitersToUnblock) != 0 )
763             {
764                 if ( timedOut )
765                 {
766                     // timeout (or canceled)
767                     if( m_numWaitersBlocked != 0 )
768                     {
769                         m_numWaitersBlocked--;
770                         // do not unblock next waiter below (already unblocked)
771                         numSignalsLeft = 0;
772                     }
773                     else
774                     {
775                         // spurious wakeup pending!!
776                         m_numWaitersGone = 1;
777                     }
778                 }
779                 if( --m_numWaitersToUnblock == 0 )
780                 {
781                     if( m_numWaitersBlocked != 0 )
782                     {
783                         // open the gate
784                         rc = ReleaseSemaphore( m_blockLock, 1, null );
785                         assert( rc );
786                         // do not open the gate below again
787                         numSignalsLeft = 0;
788                     }
789                     else if( (numWaitersGone = m_numWaitersGone) != 0 )
790                     {
791                         m_numWaitersGone = 0;
792                     }
793                 }
794             }
795             else if( ++m_numWaitersGone == int.max / 2 )
796             {
797                 // timeout/canceled or spurious event :-)
798                 rc = WaitForSingleObject( m_blockLock, INFINITE );
799                 assert( rc == WAIT_OBJECT_0 );
800                 // something is going on here - test of timeouts?
801                 m_numWaitersBlocked -= m_numWaitersGone;
802                 rc = ReleaseSemaphore( m_blockLock, 1, null );
803                 assert( rc == WAIT_OBJECT_0 );
804                 m_numWaitersGone = 0;
805             }
806 
807             LeaveCriticalSection( m_unblockLock );
808 
809             if( numSignalsLeft == 1 )
810             {
811                 // better now than spurious later (same as ResetEvent)
812                 for( ; numWaitersGone > 0; --numWaitersGone )
813                 {
814                     rc = WaitForSingleObject( m_blockQueue, INFINITE );
815                     assert( rc == WAIT_OBJECT_0 );
816                 }
817                 // open the gate
818                 rc = ReleaseSemaphore( m_blockLock, 1, null );
819                 assert( rc );
820             }
821             else if( numSignalsLeft != 0 )
822             {
823                 // unblock next waiter
824                 rc = ReleaseSemaphore( m_blockQueue, 1, null );
825                 assert( rc );
826             }
827             assocMutex.lock();
828             return !timedOut;
829         }
830 
831 
832         void notifyImpl( bool all )
833         {
834             DWORD rc;
835 
836             EnterCriticalSection( m_unblockLock );
837 
838             if( m_numWaitersToUnblock != 0 )
839             {
840                 if( m_numWaitersBlocked == 0 )
841                 {
842                     LeaveCriticalSection( m_unblockLock );
843                     return;
844                 }
845                 if( all )
846                 {
847                     m_numWaitersToUnblock += m_numWaitersBlocked;
848                     m_numWaitersBlocked = 0;
849                 }
850                 else
851                 {
852                     m_numWaitersToUnblock++;
853                     m_numWaitersBlocked--;
854                 }
855                 LeaveCriticalSection( m_unblockLock );
856             }
857             else if( m_numWaitersBlocked > m_numWaitersGone )
858             {
859                 rc = WaitForSingleObject( m_blockLock, INFINITE );
860                 assert( rc == WAIT_OBJECT_0 );
861                 if( 0 != m_numWaitersGone )
862                 {
863                     m_numWaitersBlocked -= m_numWaitersGone;
864                     m_numWaitersGone = 0;
865                 }
866                 if( all )
867                 {
868                     m_numWaitersToUnblock = m_numWaitersBlocked;
869                     m_numWaitersBlocked = 0;
870                 }
871                 else
872                 {
873                     m_numWaitersToUnblock = 1;
874                     m_numWaitersBlocked--;
875                 }
876                 LeaveCriticalSection( m_unblockLock );
877                 rc = ReleaseSemaphore( m_blockQueue, 1, null );
878                 assert( rc );
879             }
880             else
881             {
882                 LeaveCriticalSection( m_unblockLock );
883             }
884         }
885 
886 
887         // NOTE: This implementation uses Algorithm 8c as described here:
888         //       http://groups.google.com/group/comp.programming.threads/
889         //              browse_frm/thread/1692bdec8040ba40/e7a5f9d40e86503a
890         HANDLE              m_blockLock;    // auto-reset event (now semaphore)
891         HANDLE              m_blockQueue;   // auto-reset event (now semaphore)
892         CRITICAL_SECTION*   m_unblockLock           = null;  // internal mutex/CS
893         int                 m_numWaitersGone        = 0;
894         int                 m_numWaitersBlocked     = 0;
895         int                 m_numWaitersToUnblock   = 0;
896     }
897     else version( Posix )
898     {
899         pthread_cond_t*     _handle;
900     }
901 }
902 
903 unittest
904 {
905     import dplug.core.thread;
906 
907     auto mutex = makeMutex();
908     auto condvar = makeConditionVariable();
909 
910     bool finished = false;
911 
912     // Launch a thread that wait on this condition
913     Thread t = launchInAThread(
914         () {
915             mutex.lock();
916             while(!finished)
917                 condvar.wait(&mutex);
918             mutex.unlock();
919         });
920 
921     // Notify termination
922     mutex.lock();
923         finished = true;
924     mutex.unlock();
925     condvar.notifyOne();
926 
927     t.join();
928 }