/* * WIN32 Events for POSIX * Author: Mahmoud Al-Qudsi * Copyright (C) 2011 - 2013 by NeoSmart Technologies * This code is released under the terms of the MIT License * Port to boost D.Wojtal */ #include #include #include #include #include #include #ifdef WFMO #include #include #endif namespace neosmart { #ifdef WFMO struct neosmart_wfmo_t_ { boost::mutex Mutex; boost::condition_variable CVariable; union { int FiredEvent; //WFSO int EventsLeft; //WFMO } Status; bool StillWaiting; int RefCount; bool WaitAll; }; typedef neosmart_wfmo_t_ *neosmart_wfmo_t; struct neosmart_wfmo_info_t_ { neosmart_wfmo_t Waiter; int WaitIndex; }; typedef neosmart_wfmo_info_t_ *neosmart_wfmo_info_t; #endif struct neosmart_event_t_ { bool AutoReset; boost::condition_variable CVariable; boost::mutex Mutex; bool State; #ifdef WFMO std::deque RegisteredWaits; #endif }; #ifdef WFMO bool RemoveExpiredWaitHelper(neosmart_wfmo_info_t_ wait) { bool result = wait.Waiter->Mutex.try_lock(); if (!result) { return false; } if (wait.Waiter->StillWaiting == false) { --wait.Waiter->RefCount; assert(wait.Waiter->RefCount >= 0); if (wait.Waiter->RefCount == 0) { wait.Waiter->Destroy(); delete wait.Waiter; } else { wait.Waiter->Mutex.unlock(); } return true; } wait.Waiter->Mutex.unlock(); return false; } #endif neosmart_event_t CreateEvent(bool manualReset, bool initialState) { neosmart_event_t event = new neosmart_event_t_; event->State = false; event->AutoReset = !manualReset; if (initialState) { int result = SetEvent(event); assert(result == 0); } return event; } int UnlockedWaitForEvent(neosmart_event_t event, uint64_t milliseconds) { int result = 0; if (!event->State) { //Zero-timeout event state check optimization if (milliseconds == 0) { return ETIMEDOUT; } do { //Regardless of whether it's an auto-reset or manual-reset event: //wait to obtain the event, then lock anyone else out if (milliseconds != (uint64_t)-1) { boost::mutex::scoped_lock lock(event->Mutex); result = event->CVariable.timed_wait(lock, boost::posix_time::milliseconds(milliseconds)); } else { boost::mutex::scoped_lock lock(event->Mutex); event->CVariable.wait(lock); } } while (result == 0 && !event->State); if (result == 0 && event->AutoReset) { //We've only accquired the event if the wait succeeded event->State = false; } } else if (event->AutoReset) { //It's an auto-reset event that's currently available; //we need to stop anyone else from using it result = 0; event->State = false; } //Else we're trying to obtain a manual reset event with a signaled state; //don't do anything return result; } int WaitForEvent(neosmart_event_t event, uint64_t milliseconds) { if (milliseconds == 0) { if (!(event->Mutex.try_lock())) { return ETIMEDOUT; } } else { event->Mutex.lock(); } int result = UnlockedWaitForEvent(event, milliseconds); event->Mutex.unlock(); return result; } #ifdef WFMO int WaitForMultipleEvents(neosmart_event_t *events, int count, bool waitAll, uint64_t milliseconds) { int unused; return WaitForMultipleEvents(events, count, waitAll, milliseconds, unused); } int WaitForMultipleEvents(neosmart_event_t *events, int count, bool waitAll, uint64_t milliseconds, int &waitIndex) { neosmart_wfmo_t wfmo = new neosmart_wfmo_t_; int result = 0; bool bResult = true; neosmart_wfmo_info_t_ waitInfo; waitInfo.Waiter = wfmo; waitInfo.WaitIndex = -1; wfmo->WaitAll = waitAll; wfmo->StillWaiting = true; wfmo->RefCount = 1; if (waitAll) { wfmo->Status.EventsLeft = count; } else { wfmo->Status.FiredEvent = -1; } boost::mutex::scoped_lock lock(wfmo->Mutex); bool done = false; waitIndex = -1; for (int i = 0; i < count; ++i) { waitInfo.WaitIndex = i; //Must not release lock until RegisteredWait is potentially added events[i]->Mutex.lock(); //Before adding this wait to the list of registered waits, let's clean up old, expired waits while we have the event lock anyway events[i]->RegisteredWaits.erase( std::remove_if( events[i]->RegisteredWaits.begin(), events[i]->RegisteredWaits.end(), RemoveExpiredWaitHelper), events[i]->RegisteredWaits.end()); if (UnlockedWaitForEvent(events[i], 0) == 0) { events[i]->Mutex.unlock(); if (waitAll) { --wfmo->Status.EventsLeft; assert(wfmo->Status.EventsLeft >= 0); } else { wfmo->Status.FiredEvent = i; waitIndex = i; done = true; break; } } else { events[i]->RegisteredWaits.push_back(waitInfo); ++wfmo->RefCount; events[i]->Mutex.unlock(); } } if (!done) { if (milliseconds == 0) { result = ETIMEDOUT; done = true; } } while (!done) { //One (or more) of the events we're monitoring has been triggered? //If we're waiting for all events, assume we're done and check if there's an event that hasn't fired //But if we're waiting for just one event, assume we're not done until we find a fired event done = (waitAll && wfmo->Status.EventsLeft == 0) || (!waitAll && wfmo->Status.FiredEvent != -1); if (!done) { if (milliseconds) { tempResult = wfmo->CVariable.timed_wait(lock, boost::posix_time::milliseconds(milliseconds)); } else { boost::mutex::scoped_lock lock(wfmo->Mutex); wfmo->CVariable.wait(lock); } if (bResult != true) { break; } } } waitIndex = wfmo->Status.FiredEvent; wfmo->StillWaiting = false; --wfmo->RefCount; assert(wfmo->RefCount >= 0); if (!(wfmo->RefCount == 0)) { wfmo->Mutex.unlock(); } return waitIndex; } #endif int DestroyEvent(neosmart_event_t event) { int result = 0; #ifdef WFMO event->Mutex.lock(); event->RegisteredWaits.erase(std::remove_if(event->RegisteredWaits.begin(), event->RegisteredWaits.end(), RemoveExpiredWaitHelper), event->RegisteredWaits.end()); event->Mutex.unlock(); #endif delete event; return 0; } int SetEvent(neosmart_event_t event) { event->Mutex.lock(); event->State = true; //Depending on the event type, we either trigger everyone or only one if (event->AutoReset) { #ifdef WFMO while (!event->RegisteredWaits.empty()) { neosmart_wfmo_info_t i = &event->RegisteredWaits.front(); bool tempa = i->Waiter->Mutex.try_lock(); --i->Waiter->RefCount; assert(i->Waiter->RefCount >= 0); if (!i->Waiter->StillWaiting) { if (i->Waiter->RefCount == 0) { i->Waiter->Destroy(); delete i->Waiter; } else { i->Waiter->Mutex.unlock(); } event->RegisteredWaits.pop_front(); continue; } event->State = false; if (i->Waiter->WaitAll) { --i->Waiter->Status.EventsLeft; assert(i->Waiter->Status.EventsLeft >= 0); //We technically should do i->Waiter->StillWaiting = Waiter->Status.EventsLeft != 0 //but the only time it'll be equal to zero is if we're the last event, so no one //else will be checking the StillWaiting flag. We're good to go without it. } else { i->Waiter->Status.FiredEvent = i->WaitIndex; i->Waiter->StillWaiting = false; } i->Waiter->Mutex.unlock(); i->Waiter->CVariable.notify_one(); event->RegisteredWaits.pop_front(); event->Mutex.unlock(); return 0; } #endif //event->State can be false if compiled with WFMO support if (event->State) { event->Mutex.unlock(); event->CVariable.notify_one(); return 0; } } else { #ifdef WFMO for (size_t i = 0; i < event->RegisteredWaits.size(); ++i) { neosmart_wfmo_info_t info = &event->RegisteredWaits[i]; info->Waiter->Mutex.lock(); --info->Waiter->RefCount; assert(info->Waiter->RefCount >= 0); if (!info->Waiter->StillWaiting) { if (info->Waiter->RefCount == 0) { info->Waiter->Destroy(); delete info->Waiter; } else { info->Waiter->Mutex.lock(); } continue; } if (info->Waiter->WaitAll) { --info->Waiter->Status.EventsLeft; assert(info->Waiter->Status.EventsLeft >= 0); //We technically should do i->Waiter->StillWaiting = Waiter->Status.EventsLeft != 0 //but the only time it'll be equal to zero is if we're the last event, so no one //else will be checking the StillWaiting flag. We're good to go without it. } else { info->Waiter->Status.FiredEvent = info->WaitIndex; info->Waiter->StillWaiting = false; } info->Waiter->Mutex.unlock(); info->Waiter->CVariable.notify_one(); } event->RegisteredWaits.clear(); #endif event->Mutex.unlock(); event->CVariable.notify_all(); } return 0; } int ResetEvent(neosmart_event_t event) { event->Mutex.lock(); event->State = false; event->Mutex.unlock(); return 0; } #ifdef PULSE int PulseEvent(neosmart_event_t event) { //This may look like it's a horribly inefficient kludge with the sole intention of reducing code duplication, //but in reality this is what any PulseEvent() implementation must look like. The only overhead (function //calls aside, which your compiler will likely optimize away, anyway), is if only WFMO auto-reset waits are active //there will be overhead to unnecessarily obtain the event mutex for ResetEvent() after. In all other cases (being //no pending waits, WFMO manual-reset waits, or any WFSO waits), the event mutex must first be released for the //waiting thread to resume action prior to locking the mutex again in order to set the event state to unsignaled, //or else the waiting threads will loop back into a wait (due to checks for spurious CVariable wakeups). int result = SetEvent(event); assert(result == 0); result = ResetEvent(event); assert(result == 0); return 0; } #endif }