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