1// wait.cpp - written and placed in the public domain by Wei Dai 2 3#include "pch.h" 4#include "wait.h" 5#include "misc.h" 6 7#ifdef SOCKETS_AVAILABLE 8 9#ifdef USE_BERKELEY_STYLE_SOCKETS 10#include <errno.h> 11#include <sys/types.h> 12#include <sys/time.h> 13#include <unistd.h> 14#endif 15 16NAMESPACE_BEGIN(CryptoPP) 17 18unsigned int WaitObjectContainer::MaxWaitObjects() 19{ 20#ifdef USE_WINDOWS_STYLE_SOCKETS 21 return MAXIMUM_WAIT_OBJECTS * (MAXIMUM_WAIT_OBJECTS-1); 22#else 23 return FD_SETSIZE; 24#endif 25} 26 27WaitObjectContainer::WaitObjectContainer(WaitObjectsTracer* tracer) 28 : m_tracer(tracer), m_eventTimer(Timer::MILLISECONDS) 29 , m_sameResultCount(0), m_noWaitTimer(Timer::MILLISECONDS) 30{ 31 Clear(); 32 m_eventTimer.StartTimer(); 33} 34 35void WaitObjectContainer::Clear() 36{ 37#ifdef USE_WINDOWS_STYLE_SOCKETS 38 m_handles.clear(); 39#else 40 m_maxFd = 0; 41 FD_ZERO(&m_readfds); 42 FD_ZERO(&m_writefds); 43#endif 44 m_noWait = false; 45 m_firstEventTime = 0; 46} 47 48inline void WaitObjectContainer::SetLastResult(LastResultType result) 49{ 50 if (result == m_lastResult) 51 m_sameResultCount++; 52 else 53 { 54 m_lastResult = result; 55 m_sameResultCount = 0; 56 } 57} 58 59void WaitObjectContainer::DetectNoWait(LastResultType result, CallStack const& callStack) 60{ 61 if (result == m_lastResult && m_noWaitTimer.ElapsedTime() > 1000) 62 { 63 if (m_sameResultCount > m_noWaitTimer.ElapsedTime()) 64 { 65 if (m_tracer) 66 { 67 std::string desc = "No wait loop detected - m_lastResult: "; 68 desc.append(IntToString(m_lastResult)).append(", call stack:"); 69 for (CallStack const* cs = &callStack; cs; cs = cs->Prev()) 70 desc.append("\n- ").append(cs->Format()); 71 m_tracer->TraceNoWaitLoop(desc); 72 } 73 try { throw 0; } catch (...) {} // help debugger break 74 } 75 76 m_noWaitTimer.StartTimer(); 77 m_sameResultCount = 0; 78 } 79} 80 81void WaitObjectContainer::SetNoWait(CallStack const& callStack) 82{ 83 DetectNoWait(LASTRESULT_NOWAIT, CallStack("WaitObjectContainer::SetNoWait()", &callStack)); 84 m_noWait = true; 85} 86 87void WaitObjectContainer::ScheduleEvent(double milliseconds, CallStack const& callStack) 88{ 89 if (milliseconds <= 3) 90 DetectNoWait(LASTRESULT_SCHEDULED, CallStack("WaitObjectContainer::ScheduleEvent()", &callStack)); 91 double thisEventTime = m_eventTimer.ElapsedTimeAsDouble() + milliseconds; 92 if (!m_firstEventTime || thisEventTime < m_firstEventTime) 93 m_firstEventTime = thisEventTime; 94} 95 96#ifdef USE_WINDOWS_STYLE_SOCKETS 97 98struct WaitingThreadData 99{ 100 bool waitingToWait, terminate; 101 HANDLE startWaiting, stopWaiting; 102 const HANDLE *waitHandles; 103 unsigned int count; 104 HANDLE threadHandle; 105 DWORD threadId; 106 DWORD* error; 107}; 108 109WaitObjectContainer::~WaitObjectContainer() 110{ 111 try // don't let exceptions escape destructor 112 { 113 if (!m_threads.empty()) 114 { 115 HANDLE threadHandles[MAXIMUM_WAIT_OBJECTS]; 116 unsigned int i; 117 for (i=0; i<m_threads.size(); i++) 118 { 119 WaitingThreadData &thread = *m_threads[i]; 120 while (!thread.waitingToWait) // spin until thread is in the initial "waiting to wait" state 121 Sleep(0); 122 thread.terminate = true; 123 threadHandles[i] = thread.threadHandle; 124 } 125 PulseEvent(m_startWaiting); 126 ::WaitForMultipleObjects((DWORD)m_threads.size(), threadHandles, TRUE, INFINITE); 127 for (i=0; i<m_threads.size(); i++) 128 CloseHandle(threadHandles[i]); 129 CloseHandle(m_startWaiting); 130 CloseHandle(m_stopWaiting); 131 } 132 } 133 catch (...) 134 { 135 } 136} 137 138 139void WaitObjectContainer::AddHandle(HANDLE handle, CallStack const& callStack) 140{ 141 DetectNoWait(m_handles.size(), CallStack("WaitObjectContainer::AddHandle()", &callStack)); 142 m_handles.push_back(handle); 143} 144 145DWORD WINAPI WaitingThread(LPVOID lParam) 146{ 147 std::auto_ptr<WaitingThreadData> pThread((WaitingThreadData *)lParam); 148 WaitingThreadData &thread = *pThread; 149 std::vector<HANDLE> handles; 150 151 while (true) 152 { 153 thread.waitingToWait = true; 154 ::WaitForSingleObject(thread.startWaiting, INFINITE); 155 thread.waitingToWait = false; 156 157 if (thread.terminate) 158 break; 159 if (!thread.count) 160 continue; 161 162 handles.resize(thread.count + 1); 163 handles[0] = thread.stopWaiting; 164 std::copy(thread.waitHandles, thread.waitHandles+thread.count, handles.begin()+1); 165 166 DWORD result = ::WaitForMultipleObjects((DWORD)handles.size(), &handles[0], FALSE, INFINITE); 167 168 if (result == WAIT_OBJECT_0) 169 continue; // another thread finished waiting first, so do nothing 170 SetEvent(thread.stopWaiting); 171 if (!(result > WAIT_OBJECT_0 && result < WAIT_OBJECT_0 + handles.size())) 172 { 173 assert(!"error in WaitingThread"); // break here so we can see which thread has an error 174 *thread.error = ::GetLastError(); 175 } 176 } 177 178 return S_OK; // return a value here to avoid compiler warning 179} 180 181void WaitObjectContainer::CreateThreads(unsigned int count) 182{ 183 size_t currentCount = m_threads.size(); 184 if (currentCount == 0) 185 { 186 m_startWaiting = ::CreateEvent(NULL, TRUE, FALSE, NULL); 187 m_stopWaiting = ::CreateEvent(NULL, TRUE, FALSE, NULL); 188 } 189 190 if (currentCount < count) 191 { 192 m_threads.resize(count); 193 for (size_t i=currentCount; i<count; i++) 194 { 195 m_threads[i] = new WaitingThreadData; 196 WaitingThreadData &thread = *m_threads[i]; 197 thread.terminate = false; 198 thread.startWaiting = m_startWaiting; 199 thread.stopWaiting = m_stopWaiting; 200 thread.waitingToWait = false; 201 thread.threadHandle = CreateThread(NULL, 0, &WaitingThread, &thread, 0, &thread.threadId); 202 } 203 } 204} 205 206bool WaitObjectContainer::Wait(unsigned long milliseconds) 207{ 208 if (m_noWait || (m_handles.empty() && !m_firstEventTime)) 209 { 210 SetLastResult(LASTRESULT_NOWAIT); 211 return true; 212 } 213 214 bool timeoutIsScheduledEvent = false; 215 216 if (m_firstEventTime) 217 { 218 double timeToFirstEvent = SaturatingSubtract(m_firstEventTime, m_eventTimer.ElapsedTimeAsDouble()); 219 220 if (timeToFirstEvent <= milliseconds) 221 { 222 milliseconds = (unsigned long)timeToFirstEvent; 223 timeoutIsScheduledEvent = true; 224 } 225 226 if (m_handles.empty() || !milliseconds) 227 { 228 if (milliseconds) 229 Sleep(milliseconds); 230 SetLastResult(timeoutIsScheduledEvent ? LASTRESULT_SCHEDULED : LASTRESULT_TIMEOUT); 231 return timeoutIsScheduledEvent; 232 } 233 } 234 235 if (m_handles.size() > MAXIMUM_WAIT_OBJECTS) 236 { 237 // too many wait objects for a single WaitForMultipleObjects call, so use multiple threads 238 static const unsigned int WAIT_OBJECTS_PER_THREAD = MAXIMUM_WAIT_OBJECTS-1; 239 unsigned int nThreads = (unsigned int)((m_handles.size() + WAIT_OBJECTS_PER_THREAD - 1) / WAIT_OBJECTS_PER_THREAD); 240 if (nThreads > MAXIMUM_WAIT_OBJECTS) // still too many wait objects, maybe implement recursive threading later? 241 throw Err("WaitObjectContainer: number of wait objects exceeds limit"); 242 CreateThreads(nThreads); 243 DWORD error = S_OK; 244 245 for (unsigned int i=0; i<m_threads.size(); i++) 246 { 247 WaitingThreadData &thread = *m_threads[i]; 248 while (!thread.waitingToWait) // spin until thread is in the initial "waiting to wait" state 249 Sleep(0); 250 if (i<nThreads) 251 { 252 thread.waitHandles = &m_handles[i*WAIT_OBJECTS_PER_THREAD]; 253 thread.count = UnsignedMin(WAIT_OBJECTS_PER_THREAD, m_handles.size() - i*WAIT_OBJECTS_PER_THREAD); 254 thread.error = &error; 255 } 256 else 257 thread.count = 0; 258 } 259 260 ResetEvent(m_stopWaiting); 261 PulseEvent(m_startWaiting); 262 263 DWORD result = ::WaitForSingleObject(m_stopWaiting, milliseconds); 264 if (result == WAIT_OBJECT_0) 265 { 266 if (error == S_OK) 267 return true; 268 else 269 throw Err("WaitObjectContainer: WaitForMultipleObjects in thread failed with error " + IntToString(error)); 270 } 271 SetEvent(m_stopWaiting); 272 if (result == WAIT_TIMEOUT) 273 { 274 SetLastResult(timeoutIsScheduledEvent ? LASTRESULT_SCHEDULED : LASTRESULT_TIMEOUT); 275 return timeoutIsScheduledEvent; 276 } 277 else 278 throw Err("WaitObjectContainer: WaitForSingleObject failed with error " + IntToString(::GetLastError())); 279 } 280 else 281 { 282#if TRACE_WAIT 283 static Timer t(Timer::MICROSECONDS); 284 static unsigned long lastTime = 0; 285 unsigned long timeBeforeWait = t.ElapsedTime(); 286#endif 287 DWORD result = ::WaitForMultipleObjects((DWORD)m_handles.size(), &m_handles[0], FALSE, milliseconds); 288#if TRACE_WAIT 289 if (milliseconds > 0) 290 { 291 unsigned long timeAfterWait = t.ElapsedTime(); 292 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()); 293 lastTime = timeAfterWait; 294 } 295#endif 296 if (result >= WAIT_OBJECT_0 && result < WAIT_OBJECT_0 + m_handles.size()) 297 { 298 if (result == m_lastResult) 299 m_sameResultCount++; 300 else 301 { 302 m_lastResult = result; 303 m_sameResultCount = 0; 304 } 305 return true; 306 } 307 else if (result == WAIT_TIMEOUT) 308 { 309 SetLastResult(timeoutIsScheduledEvent ? LASTRESULT_SCHEDULED : LASTRESULT_TIMEOUT); 310 return timeoutIsScheduledEvent; 311 } 312 else 313 throw Err("WaitObjectContainer: WaitForMultipleObjects failed with error " + IntToString(::GetLastError())); 314 } 315} 316 317#else // #ifdef USE_WINDOWS_STYLE_SOCKETS 318 319void WaitObjectContainer::AddReadFd(int fd, CallStack const& callStack) // TODO: do something with callStack 320{ 321 FD_SET(fd, &m_readfds); 322 m_maxFd = STDMAX(m_maxFd, fd); 323} 324 325void WaitObjectContainer::AddWriteFd(int fd, CallStack const& callStack) // TODO: do something with callStack 326{ 327 FD_SET(fd, &m_writefds); 328 m_maxFd = STDMAX(m_maxFd, fd); 329} 330 331bool WaitObjectContainer::Wait(unsigned long milliseconds) 332{ 333 if (m_noWait || (!m_maxFd && !m_firstEventTime)) 334 return true; 335 336 bool timeoutIsScheduledEvent = false; 337 338 if (m_firstEventTime) 339 { 340 double timeToFirstEvent = SaturatingSubtract(m_firstEventTime, m_eventTimer.ElapsedTimeAsDouble()); 341 if (timeToFirstEvent <= milliseconds) 342 { 343 milliseconds = (unsigned long)timeToFirstEvent; 344 timeoutIsScheduledEvent = true; 345 } 346 } 347 348 timeval tv, *timeout; 349 350 if (milliseconds == INFINITE_TIME) 351 timeout = NULL; 352 else 353 { 354 tv.tv_sec = milliseconds / 1000; 355 tv.tv_usec = (milliseconds % 1000) * 1000; 356 timeout = &tv; 357 } 358 359 int result = select(m_maxFd+1, &m_readfds, &m_writefds, NULL, timeout); 360 361 if (result > 0) 362 return true; 363 else if (result == 0) 364 return timeoutIsScheduledEvent; 365 else 366 throw Err("WaitObjectContainer: select failed with error " + errno); 367} 368 369#endif 370 371// ******************************************************** 372 373std::string CallStack::Format() const 374{ 375 return m_info; 376} 377 378std::string CallStackWithNr::Format() const 379{ 380 return std::string(m_info) + " / nr: " + IntToString(m_nr); 381} 382 383std::string CallStackWithStr::Format() const 384{ 385 return std::string(m_info) + " / " + std::string(m_z); 386} 387 388bool Waitable::Wait(unsigned long milliseconds, CallStack const& callStack) 389{ 390 WaitObjectContainer container; 391 GetWaitObjects(container, callStack); // reduce clutter by not adding this func to stack 392 return container.Wait(milliseconds); 393} 394 395NAMESPACE_END 396 397#endif 398