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