/** D Language Synchronization Management Contains the monitor structure used by objects to implement the synchronized language statement. It also extends the D concept of monitor with support for condition variables. Authors: Jeremie Pelletier */ module dlib.Monitor; import std.c.stdlib : malloc, free; import dlib.Module; import dlib.Thread; version(Windows) { import sys.windows.Information; import sys.windows.Processes; } else version(Posix) { import sys.posix.pthread; } else static assert(0); private { // Critical objects are allocated by the compiler, we only need to keep // track of the ones we do initialize. Monitor* _criticalList; Mutex _criticalMutex; version(Windows) { bool _hasCondVar; } else version(Posix) { pthread_mutexattr_t _criticalAttr; } } /** Initialize the runtime monitor manager */ package void MonitorInit() { version(Windows) { MutexInit(&_criticalMutex); // Use native condition variables on NT 6.0 (Vista) and up OSVERSIONINFO ver = void; ver.dwOSVersionInfoSize = OSVERSIONINFO.sizeof; .GetVersionEx(&ver); _hasCondVar = ver.dwMajorVersion >= 6; // Load the condition variable routines if(_hasCondVar && !InitializeConditionVariable) { Module mod = Module.Load("Kernel32.dll"); mod.GetProc(cast(void**)&InitializeConditionVariable, "InitializeConditionVariable"); mod.GetProc(cast(void**)&SleepConditionVariableCS, "SleepConditionVariableCS"); mod.GetProc(cast(void**)&WakeConditionVariable, "WakeConditionVariable"); mod.GetProc(cast(void**)&WakeAllConditionVariable, "WakeAllConditionVariable"); } } else version(Posix) { pthread_mutexattr_init(&_criticalAttr); pthread_mutexattr_settype(&_criticalAttr, PTHREAD_MUTEX_RECURSIVE_NP); pthread_mutex_init(&_monitorMutex, &_monitorAttr); } else static assert(0); } /** Destroy the runtime monitor manager */ package void MonitorDestroy() { // Destroy the criticals, the last entry is always recursive, this is so // the first monitor can start the list chain. if(_criticalList) while(_criticalList != _criticalList.next) { MutexDestroy(&_criticalList.mutex); _criticalList = _criticalList.next; } MutexDestroy(&_criticalMutex); version(Posix) pthread_mutexattr_destroy(&_criticalAttr); } version(Windows) { alias CRITICAL_SECTION Mutex; union Condition { CONDITION_VARIABLE var; struct { // TODO CRITICAL_SECTION lock; uint nEntries; } } alias InitializeCriticalSection MutexInit; alias DeleteCriticalSection MutexDestroy; alias EnterCriticalSection MutexEnter; alias LeaveCriticalSection MutexLeave; } else version(Posix) { alias pthread_mutex_t Mutex; alias pthread_cond_t Condition; void MutexInit(Mutex* mutex) { pthread_mutex_init(mutex, null); } alias pthread_mutex_destroy MutexDestroy; alias pthread_mutex_lock MutexEnter; alias pthread_mutex_unlock MutexLeave; } else static assert(0, "TODO: unsupported host."); /** The monitor structure is the central synchronization object of the D language. It is automatically handled using the synchronized statement, or the Object methods. Oh and please don't make this a class! If you can't figure out why, you shouldn't mess with the runtime in the first place! ;) */ struct Monitor { /** Initialize the mutex part of the monitor */ void Init() { MutexInit(&mutex); } /** Initialize both the mutex and the condition variable of the monitor */ void InitCond() in { assert(!cond); } body { cond = new Condition; MutexInit(&mutex); version(Windows) { if(_hasCondVar) { InitializeConditionVariable(&cond.var); } else { assert(0);// TODO } } else version(Posix) { pthread_cond_init(cond, null); } else static assert(0); } /** Destroy both the mutex and the condition variable of the monitor */ void Destroy() { MutexDestroy(&mutex); if(cond) { version(Windows) { if(!_hasCondVar) assert(0);// TODO } else version(Posix) { pthread_cond_destroy(cond); } else static assert(0); } } /** Lock the mutex */ void Enter() { MutexEnter(&mutex); } /** Unlock the mutex */ void Exit() { MutexLeave(&mutex); } /** Causes the calling thread to wait for the condition variable to be notified. */ void Wait(uint interval = -1) { if(!cond) InitCond(); version(Windows) { if(_hasCondVar) { SleepConditionVariableCS(&cond.var, &mutex, interval); } else { assert(0);// TODO } } else version(Posix) { if(time != -1) assert("TODO: implement pthread_cond_timedwait"); pthread_cond_wait(cond, &mutex); } else static assert(0); } /** Notify the first waiting thread */ void Notify() { if(!cond) return; version(Windows) { if(_hasCondVar) { WakeConditionVariable(&cond.var); } else { assert(0);// TODO } } else version(Posix) { pthread_cond_signal(cond); } else static assert(0); } /** Notify all waiting threads */ void NotifyAll() { if(!cond) return; version(Windows) { if(_hasCondVar) { WakeAllConditionVariable(&cond.var); } else { assert(0);// TODO } } else version(Posix) { pthread_cond_broadcast(cond); } else static assert(0); } package: Mutex mutex; // Also used by memory manager to bootstrap union { Condition* cond; private Monitor* next; // Only used for criticals } } // Reserved for dlib, do not use. void CriticalEnter(Monitor* monitor) { if(!monitor.next) { MutexEnter(&_criticalMutex); // if, in the meantime, another thread didn't set it if(!monitor.next) { monitor.next = _criticalList ? _criticalList : monitor; _criticalList = monitor; MutexInit(&monitor.mutex); } MutexLeave(&_criticalMutex); } MutexEnter(&monitor.mutex); } unittest { class Foo {} Foo foo = new Foo; void Run() { Trace("wait.."); foo.Wait(); Trace("GREAT SUCCESS"); } RuntimeThread myThread = RuntimeThread.Create(&Run); .Sleep(100); Trace("signal.."); foo.Notify(); .Sleep(100); Pause; }