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