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