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
16NAMESPACE_BEGIN(CryptoPP)
17
18unsigned 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
27WaitObjectContainer::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
35void 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
48inline 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
59void 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
81void WaitObjectContainer::SetNoWait(CallStack const& callStack)
82{
83	DetectNoWait(LASTRESULT_NOWAIT, CallStack("WaitObjectContainer::SetNoWait()", &callStack));
84	m_noWait = true;
85}
86
87void 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
98struct WaitingThreadData
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
109WaitObjectContainer::~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
139void 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
145DWORD 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
181void 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
206bool 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
319void 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
325void 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
331bool 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
373std::string CallStack::Format() const
374{
375	return m_info;
376}
377
378std::string CallStackWithNr::Format() const
379{
380	return std::string(m_info) + " / nr: " + IntToString(m_nr);
381}
382
383std::string CallStackWithStr::Format() const
384{
385	return std::string(m_info) + " / " + std::string(m_z);
386}
387
388bool 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
395NAMESPACE_END
396
397#endif
398