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