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