Crypto++  5.6.5
Free C++ class library of cryptographic schemes
wait.cpp
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 
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 
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 
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 
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 
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 
109 void WaitObjectContainer::SetNoWait(CallStack const& callStack)
110 {
111  DetectNoWait(LastResultType(LASTRESULT_NOWAIT), CallStack("WaitObjectContainer::SetNoWait()", &callStack));
112  m_noWait = true;
113 }
114 
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 
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 
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 
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 
198 DWORD WINAPI WaitingThread(LPVOID lParam)
199 {
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 
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 
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 
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 
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 
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 
459 std::string CallStack::Format() const
460 {
461  return m_info;
462 }
463 
464 std::string CallStackWithNr::Format() const
465 {
466  return std::string(m_info) + " / nr: " + IntToString(m_nr);
467 }
468 
469 std::string CallStackWithStr::Format() const
470 {
471  return std::string(m_info) + " / " + std::string(m_z);
472 }
473 
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
Base class for all exceptions thrown by the library.
Definition: cryptlib.h:140
container of wait objects
Definition: wait.h:169
high resolution timer
Definition: hrtimer.h:57
Utility functions for the Crypto++ library.
Classes for automatic resource management.
Library configuration file.
Pointer that overloads operator ->
Definition: smartptr.h:39
virtual void GetWaitObjects(WaitObjectContainer &container, CallStack const &callStack)=0
Retrieves waitable objects.
T1 SaturatingSubtract(const T1 &a, const T2 &b)
Performs a saturating subtract clamped at 0.
Definition: misc.h:847
const T1 UnsignedMin(const T1 &a, const T2 &b)
Safe comparison of values that could be neagtive and incorrectly promoted.
Definition: misc.h:512
#define CRYPTOPP_ASSERT(exp)
Debugging and diagnostic assertion.
Definition: trap.h:62
const unsigned long INFINITE_TIME
Represents infinite time.
Definition: cryptlib.h:111
bool Wait(unsigned long milliseconds, CallStack const &callStack)
Wait on this object.
Definition: wait.cpp:474
std::string IntToString(T value, unsigned int base=10)
Converts a value to a string.
Definition: misc.h:539
const T & STDMAX(const T &a, const T &b)
Replacement function for std::max.
Definition: misc.h:487
Crypto++ library namespace.