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