You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
397 lines
10 KiB
397 lines
10 KiB
// wait.cpp - written and placed in the public domain by Wei Dai |
|
|
|
#include "pch.h" |
|
#include "wait.h" |
|
#include "misc.h" |
|
|
|
#ifdef SOCKETS_AVAILABLE |
|
|
|
#ifdef USE_BERKELEY_STYLE_SOCKETS |
|
#include <errno.h> |
|
#include <sys/types.h> |
|
#include <sys/time.h> |
|
#include <unistd.h> |
|
#endif |
|
|
|
NAMESPACE_BEGIN(CryptoPP) |
|
|
|
unsigned int WaitObjectContainer::MaxWaitObjects() |
|
{ |
|
#ifdef USE_WINDOWS_STYLE_SOCKETS |
|
return MAXIMUM_WAIT_OBJECTS * (MAXIMUM_WAIT_OBJECTS-1); |
|
#else |
|
return FD_SETSIZE; |
|
#endif |
|
} |
|
|
|
WaitObjectContainer::WaitObjectContainer(WaitObjectsTracer* tracer) |
|
: m_tracer(tracer), m_eventTimer(Timer::MILLISECONDS) |
|
, m_sameResultCount(0), m_noWaitTimer(Timer::MILLISECONDS) |
|
{ |
|
Clear(); |
|
m_eventTimer.StartTimer(); |
|
} |
|
|
|
void WaitObjectContainer::Clear() |
|
{ |
|
#ifdef USE_WINDOWS_STYLE_SOCKETS |
|
m_handles.clear(); |
|
#else |
|
m_maxFd = 0; |
|
FD_ZERO(&m_readfds); |
|
FD_ZERO(&m_writefds); |
|
#endif |
|
m_noWait = false; |
|
m_firstEventTime = 0; |
|
} |
|
|
|
inline void WaitObjectContainer::SetLastResult(LastResultType result) |
|
{ |
|
if (result == m_lastResult) |
|
m_sameResultCount++; |
|
else |
|
{ |
|
m_lastResult = result; |
|
m_sameResultCount = 0; |
|
} |
|
} |
|
|
|
void WaitObjectContainer::DetectNoWait(LastResultType result, CallStack const& callStack) |
|
{ |
|
if (result == m_lastResult && m_noWaitTimer.ElapsedTime() > 1000) |
|
{ |
|
if (m_sameResultCount > m_noWaitTimer.ElapsedTime()) |
|
{ |
|
if (m_tracer) |
|
{ |
|
std::string desc = "No wait loop detected - m_lastResult: "; |
|
desc.append(IntToString(m_lastResult)).append(", call stack:"); |
|
for (CallStack const* cs = &callStack; cs; cs = cs->Prev()) |
|
desc.append("\n- ").append(cs->Format()); |
|
m_tracer->TraceNoWaitLoop(desc); |
|
} |
|
try { throw 0; } catch (...) {} // help debugger break |
|
} |
|
|
|
m_noWaitTimer.StartTimer(); |
|
m_sameResultCount = 0; |
|
} |
|
} |
|
|
|
void WaitObjectContainer::SetNoWait(CallStack const& callStack) |
|
{ |
|
DetectNoWait(LASTRESULT_NOWAIT, CallStack("WaitObjectContainer::SetNoWait()", &callStack)); |
|
m_noWait = true; |
|
} |
|
|
|
void WaitObjectContainer::ScheduleEvent(double milliseconds, CallStack const& callStack) |
|
{ |
|
if (milliseconds <= 3) |
|
DetectNoWait(LASTRESULT_SCHEDULED, CallStack("WaitObjectContainer::ScheduleEvent()", &callStack)); |
|
double thisEventTime = m_eventTimer.ElapsedTimeAsDouble() + milliseconds; |
|
if (!m_firstEventTime || thisEventTime < m_firstEventTime) |
|
m_firstEventTime = thisEventTime; |
|
} |
|
|
|
#ifdef USE_WINDOWS_STYLE_SOCKETS |
|
|
|
struct WaitingThreadData |
|
{ |
|
bool waitingToWait, terminate; |
|
HANDLE startWaiting, stopWaiting; |
|
const HANDLE *waitHandles; |
|
unsigned int count; |
|
HANDLE threadHandle; |
|
DWORD threadId; |
|
DWORD* error; |
|
}; |
|
|
|
WaitObjectContainer::~WaitObjectContainer() |
|
{ |
|
try // don't let exceptions escape destructor |
|
{ |
|
if (!m_threads.empty()) |
|
{ |
|
HANDLE threadHandles[MAXIMUM_WAIT_OBJECTS]; |
|
unsigned int i; |
|
for (i=0; i<m_threads.size(); i++) |
|
{ |
|
WaitingThreadData &thread = *m_threads[i]; |
|
while (!thread.waitingToWait) // spin until thread is in the initial "waiting to wait" state |
|
Sleep(0); |
|
thread.terminate = true; |
|
threadHandles[i] = thread.threadHandle; |
|
} |
|
PulseEvent(m_startWaiting); |
|
::WaitForMultipleObjects((DWORD)m_threads.size(), threadHandles, TRUE, INFINITE); |
|
for (i=0; i<m_threads.size(); i++) |
|
CloseHandle(threadHandles[i]); |
|
CloseHandle(m_startWaiting); |
|
CloseHandle(m_stopWaiting); |
|
} |
|
} |
|
catch (...) |
|
{ |
|
} |
|
} |
|
|
|
|
|
void WaitObjectContainer::AddHandle(HANDLE handle, CallStack const& callStack) |
|
{ |
|
DetectNoWait(m_handles.size(), CallStack("WaitObjectContainer::AddHandle()", &callStack)); |
|
m_handles.push_back(handle); |
|
} |
|
|
|
DWORD WINAPI WaitingThread(LPVOID lParam) |
|
{ |
|
std::auto_ptr<WaitingThreadData> pThread((WaitingThreadData *)lParam); |
|
WaitingThreadData &thread = *pThread; |
|
std::vector<HANDLE> handles; |
|
|
|
while (true) |
|
{ |
|
thread.waitingToWait = true; |
|
::WaitForSingleObject(thread.startWaiting, INFINITE); |
|
thread.waitingToWait = false; |
|
|
|
if (thread.terminate) |
|
break; |
|
if (!thread.count) |
|
continue; |
|
|
|
handles.resize(thread.count + 1); |
|
handles[0] = thread.stopWaiting; |
|
std::copy(thread.waitHandles, thread.waitHandles+thread.count, handles.begin()+1); |
|
|
|
DWORD result = ::WaitForMultipleObjects((DWORD)handles.size(), &handles[0], FALSE, INFINITE); |
|
|
|
if (result == WAIT_OBJECT_0) |
|
continue; // another thread finished waiting first, so do nothing |
|
SetEvent(thread.stopWaiting); |
|
if (!(result > WAIT_OBJECT_0 && result < WAIT_OBJECT_0 + handles.size())) |
|
{ |
|
assert(!"error in WaitingThread"); // break here so we can see which thread has an error |
|
*thread.error = ::GetLastError(); |
|
} |
|
} |
|
|
|
return S_OK; // return a value here to avoid compiler warning |
|
} |
|
|
|
void WaitObjectContainer::CreateThreads(unsigned int count) |
|
{ |
|
size_t currentCount = m_threads.size(); |
|
if (currentCount == 0) |
|
{ |
|
m_startWaiting = ::CreateEvent(NULL, TRUE, FALSE, NULL); |
|
m_stopWaiting = ::CreateEvent(NULL, TRUE, FALSE, NULL); |
|
} |
|
|
|
if (currentCount < count) |
|
{ |
|
m_threads.resize(count); |
|
for (size_t i=currentCount; i<count; i++) |
|
{ |
|
m_threads[i] = new WaitingThreadData; |
|
WaitingThreadData &thread = *m_threads[i]; |
|
thread.terminate = false; |
|
thread.startWaiting = m_startWaiting; |
|
thread.stopWaiting = m_stopWaiting; |
|
thread.waitingToWait = false; |
|
thread.threadHandle = CreateThread(NULL, 0, &WaitingThread, &thread, 0, &thread.threadId); |
|
} |
|
} |
|
} |
|
|
|
bool WaitObjectContainer::Wait(unsigned long milliseconds) |
|
{ |
|
if (m_noWait || (m_handles.empty() && !m_firstEventTime)) |
|
{ |
|
SetLastResult(LASTRESULT_NOWAIT); |
|
return true; |
|
} |
|
|
|
bool timeoutIsScheduledEvent = false; |
|
|
|
if (m_firstEventTime) |
|
{ |
|
double timeToFirstEvent = SaturatingSubtract(m_firstEventTime, m_eventTimer.ElapsedTimeAsDouble()); |
|
|
|
if (timeToFirstEvent <= milliseconds) |
|
{ |
|
milliseconds = (unsigned long)timeToFirstEvent; |
|
timeoutIsScheduledEvent = true; |
|
} |
|
|
|
if (m_handles.empty() || !milliseconds) |
|
{ |
|
if (milliseconds) |
|
Sleep(milliseconds); |
|
SetLastResult(timeoutIsScheduledEvent ? LASTRESULT_SCHEDULED : LASTRESULT_TIMEOUT); |
|
return timeoutIsScheduledEvent; |
|
} |
|
} |
|
|
|
if (m_handles.size() > MAXIMUM_WAIT_OBJECTS) |
|
{ |
|
// too many wait objects for a single WaitForMultipleObjects call, so use multiple threads |
|
static const unsigned int WAIT_OBJECTS_PER_THREAD = MAXIMUM_WAIT_OBJECTS-1; |
|
unsigned int nThreads = (unsigned int)((m_handles.size() + WAIT_OBJECTS_PER_THREAD - 1) / WAIT_OBJECTS_PER_THREAD); |
|
if (nThreads > MAXIMUM_WAIT_OBJECTS) // still too many wait objects, maybe implement recursive threading later? |
|
throw Err("WaitObjectContainer: number of wait objects exceeds limit"); |
|
CreateThreads(nThreads); |
|
DWORD error = S_OK; |
|
|
|
for (unsigned int i=0; i<m_threads.size(); i++) |
|
{ |
|
WaitingThreadData &thread = *m_threads[i]; |
|
while (!thread.waitingToWait) // spin until thread is in the initial "waiting to wait" state |
|
Sleep(0); |
|
if (i<nThreads) |
|
{ |
|
thread.waitHandles = &m_handles[i*WAIT_OBJECTS_PER_THREAD]; |
|
thread.count = UnsignedMin(WAIT_OBJECTS_PER_THREAD, m_handles.size() - i*WAIT_OBJECTS_PER_THREAD); |
|
thread.error = &error; |
|
} |
|
else |
|
thread.count = 0; |
|
} |
|
|
|
ResetEvent(m_stopWaiting); |
|
PulseEvent(m_startWaiting); |
|
|
|
DWORD result = ::WaitForSingleObject(m_stopWaiting, milliseconds); |
|
if (result == WAIT_OBJECT_0) |
|
{ |
|
if (error == S_OK) |
|
return true; |
|
else |
|
throw Err("WaitObjectContainer: WaitForMultipleObjects in thread failed with error " + IntToString(error)); |
|
} |
|
SetEvent(m_stopWaiting); |
|
if (result == WAIT_TIMEOUT) |
|
{ |
|
SetLastResult(timeoutIsScheduledEvent ? LASTRESULT_SCHEDULED : LASTRESULT_TIMEOUT); |
|
return timeoutIsScheduledEvent; |
|
} |
|
else |
|
throw Err("WaitObjectContainer: WaitForSingleObject failed with error " + IntToString(::GetLastError())); |
|
} |
|
else |
|
{ |
|
#if TRACE_WAIT |
|
static Timer t(Timer::MICROSECONDS); |
|
static unsigned long lastTime = 0; |
|
unsigned long timeBeforeWait = t.ElapsedTime(); |
|
#endif |
|
DWORD result = ::WaitForMultipleObjects((DWORD)m_handles.size(), &m_handles[0], FALSE, milliseconds); |
|
#if TRACE_WAIT |
|
if (milliseconds > 0) |
|
{ |
|
unsigned long timeAfterWait = t.ElapsedTime(); |
|
OutputDebugString(("Handles " + IntToString(m_handles.size()) + ", Woke up by " + IntToString(result-WAIT_OBJECT_0) + ", Busied for " + IntToString(timeBeforeWait-lastTime) + " us, Waited for " + IntToString(timeAfterWait-timeBeforeWait) + " us, max " + IntToString(milliseconds) + "ms\n").c_str()); |
|
lastTime = timeAfterWait; |
|
} |
|
#endif |
|
if (result >= WAIT_OBJECT_0 && result < WAIT_OBJECT_0 + m_handles.size()) |
|
{ |
|
if (result == m_lastResult) |
|
m_sameResultCount++; |
|
else |
|
{ |
|
m_lastResult = result; |
|
m_sameResultCount = 0; |
|
} |
|
return true; |
|
} |
|
else if (result == WAIT_TIMEOUT) |
|
{ |
|
SetLastResult(timeoutIsScheduledEvent ? LASTRESULT_SCHEDULED : LASTRESULT_TIMEOUT); |
|
return timeoutIsScheduledEvent; |
|
} |
|
else |
|
throw Err("WaitObjectContainer: WaitForMultipleObjects failed with error " + IntToString(::GetLastError())); |
|
} |
|
} |
|
|
|
#else // #ifdef USE_WINDOWS_STYLE_SOCKETS |
|
|
|
void WaitObjectContainer::AddReadFd(int fd, CallStack const& callStack) // TODO: do something with callStack |
|
{ |
|
FD_SET(fd, &m_readfds); |
|
m_maxFd = STDMAX(m_maxFd, fd); |
|
} |
|
|
|
void WaitObjectContainer::AddWriteFd(int fd, CallStack const& callStack) // TODO: do something with callStack |
|
{ |
|
FD_SET(fd, &m_writefds); |
|
m_maxFd = STDMAX(m_maxFd, fd); |
|
} |
|
|
|
bool WaitObjectContainer::Wait(unsigned long milliseconds) |
|
{ |
|
if (m_noWait || (!m_maxFd && !m_firstEventTime)) |
|
return true; |
|
|
|
bool timeoutIsScheduledEvent = false; |
|
|
|
if (m_firstEventTime) |
|
{ |
|
double timeToFirstEvent = SaturatingSubtract(m_firstEventTime, m_eventTimer.ElapsedTimeAsDouble()); |
|
if (timeToFirstEvent <= milliseconds) |
|
{ |
|
milliseconds = (unsigned long)timeToFirstEvent; |
|
timeoutIsScheduledEvent = true; |
|
} |
|
} |
|
|
|
timeval tv, *timeout; |
|
|
|
if (milliseconds == INFINITE_TIME) |
|
timeout = NULL; |
|
else |
|
{ |
|
tv.tv_sec = milliseconds / 1000; |
|
tv.tv_usec = (milliseconds % 1000) * 1000; |
|
timeout = &tv; |
|
} |
|
|
|
int result = select(m_maxFd+1, &m_readfds, &m_writefds, NULL, timeout); |
|
|
|
if (result > 0) |
|
return true; |
|
else if (result == 0) |
|
return timeoutIsScheduledEvent; |
|
else |
|
throw Err("WaitObjectContainer: select failed with error " + errno); |
|
} |
|
|
|
#endif |
|
|
|
// ******************************************************** |
|
|
|
std::string CallStack::Format() const |
|
{ |
|
return m_info; |
|
} |
|
|
|
std::string CallStackWithNr::Format() const |
|
{ |
|
return std::string(m_info) + " / nr: " + IntToString(m_nr); |
|
} |
|
|
|
std::string CallStackWithStr::Format() const |
|
{ |
|
return std::string(m_info) + " / " + std::string(m_z); |
|
} |
|
|
|
bool Waitable::Wait(unsigned long milliseconds, CallStack const& callStack) |
|
{ |
|
WaitObjectContainer container; |
|
GetWaitObjects(container, callStack); // reduce clutter by not adding this func to stack |
|
return container.Wait(milliseconds); |
|
} |
|
|
|
NAMESPACE_END |
|
|
|
#endif
|
|
|