From 7e1614d32265ed02ddd25c2d2c2a66b1d9bb9d94 Mon Sep 17 00:00:00 2001 From: Brad House Date: Mon, 13 Jan 2025 09:58:27 -0500 Subject: [PATCH] [port] Windows XP: try to support threading and event subsystems --- CMakeLists.txt | 3 + appveyor.yml | 4 +- configure.ac | 3 + src/lib/ares_config.h.cmake | 9 ++ src/lib/config-win32.h | 15 ++- src/lib/event/ares_event_win32.c | 53 +++++++- src/lib/event/ares_event_win32.h | 10 ++ src/lib/util/ares_threads.c | 222 ++++++++++++++++++++++++++++++- src/lib/util/ares_threads.h | 4 +- 9 files changed, 309 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d0516230e7..abbf1c8def 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -429,10 +429,13 @@ CHECK_SYMBOL_EXISTS (gettimeofday "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_GETTIME CHECK_SYMBOL_EXISTS (if_indextoname "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_IF_INDEXTONAME) CHECK_SYMBOL_EXISTS (if_nametoindex "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_IF_NAMETOINDEX) CHECK_SYMBOL_EXISTS (GetBestRoute2 "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_GETBESTROUTE2) +CHECK_SYMBOL_EXISTS (WSAIoctl "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_WSAIOCTL) +CHECK_SYMBOL_EXISTS (GetQueuedCompletionStatusEx "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_GETQUEUEDCOMPLETIONSTATUSEX) CHECK_SYMBOL_EXISTS (ConvertInterfaceIndexToLuid "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_CONVERTINTERFACEINDEXTOLUID) CHECK_SYMBOL_EXISTS (ConvertInterfaceLuidToNameA "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_CONVERTINTERFACELUIDTONAMEA) CHECK_SYMBOL_EXISTS (NotifyIpInterfaceChange "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_NOTIFYIPINTERFACECHANGE) CHECK_SYMBOL_EXISTS (RegisterWaitForSingleObject "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_REGISTERWAITFORSINGLEOBJECT) +CHECK_SYMBOL_EXISTS (SetFileCompletionNotificationModes "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_SETFILECOMPLETIONNOTIFICATIONMODES) CHECK_SYMBOL_EXISTS (inet_net_pton "${CMAKE_EXTRA_INCLUDE_FILES}" HAVE_INET_NET_PTON) diff --git a/appveyor.yml b/appveyor.yml index ca2201fa73..481178afb8 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -30,7 +30,7 @@ environment: SKIP_TESTS: no MSVC_SETUP_ARG: x86 MSVC_SETUP_PATH: C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars32.bat - CMAKE_EXTRA_OPTIONS: -GNinja -DCARES_BUILD_TESTS=ON -DGTEST_ROOT=C:\projects\googletest -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDebugDLL -DCARES_THREADS=OFF -DCMAKE_C_FLAGS="-D_WIN32_WINNT=0x0501" -DCMAKE_CXX_FLAGS="-D_WIN32_WINNT=0x0501" + CMAKE_EXTRA_OPTIONS: -GNinja -DCARES_BUILD_TESTS=ON -DGTEST_ROOT=C:\projects\googletest -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDebugDLL -DCMAKE_C_FLAGS="-D_WIN32_WINNT=0x0501" -DCMAKE_CXX_FLAGS="-D_WIN32_WINNT=0x0501" TOOLSDIR: ./build/bin TESTDIR: ./build/bin BUILD_GOOGLETEST: yes @@ -82,7 +82,7 @@ environment: TOOLSDIR: ./build/bin TESTDIR: ./build/bin PATH: C:\mingw-w64\i686-8.1.0-posix-dwarf-rt_v6-rev0\mingw32\bin;%PATH% - CMAKE_EXTRA_OPTIONS: -DCARES_SHARED=OFF -GNinja -DCARES_BUILD_TESTS=ON -DGTEST_ROOT=C:\projects\googletest -DCARES_THREADS=OFF -DCMAKE_C_FLAGS="-D_WIN32_WINNT=0x0501" -DCMAKE_CXX_FLAGS="-D_WIN32_WINNT=0x0501" + CMAKE_EXTRA_OPTIONS: -DCARES_SHARED=OFF -GNinja -DCARES_BUILD_TESTS=ON -DGTEST_ROOT=C:\projects\googletest -DCMAKE_C_FLAGS="-D_WIN32_WINNT=0x0501" -DCMAKE_CXX_FLAGS="-D_WIN32_WINNT=0x0501" BUILD_GOOGLETEST: yes # Disabled until AppVeyor updates their Visual Studio with this patch: diff --git a/configure.ac b/configure.ac index 06120c2991..80f40477e3 100644 --- a/configure.ac +++ b/configure.ac @@ -595,11 +595,14 @@ AC_CHECK_DECL(pipe2, [AC_DEFINE([HAVE_PIPE2], 1, [Define t AC_CHECK_DECL(kqueue, [AC_DEFINE([HAVE_KQUEUE], 1, [Define to 1 if you have `kqueue`] )], [], $cares_all_includes) AC_CHECK_DECL(epoll_create1, [AC_DEFINE([HAVE_EPOLL], 1, [Define to 1 if you have `epoll_{create1,ctl,wait}`])], [], $cares_all_includes) AC_CHECK_DECL(GetBestRoute2, [AC_DEFINE([HAVE_GETBESTROUTE2], 1, [Define to 1 if you have `GetBestRoute2`] )], [], $cares_all_includes) +AC_CHECK_DECL(GetQueuedCompletionStatusEx, [AC_DEFINE([HAVE_GETQUEUEDCOMPLETIONSTATUSEX], 1, [Define to 1 if you have `GetQueuedCompletionStatusEx`])], [], $cares_all_includes) AC_CHECK_DECL(ConvertInterfaceIndexToLuid, [AC_DEFINE([HAVE_CONVERTINTERFACEINDEXTOLUID], 1, [Define to 1 if you have `ConvertInterfaceIndexToLuid`])], [], $cares_all_includes) AC_CHECK_DECL(ConvertInterfaceLuidToNameA, [AC_DEFINE([HAVE_CONVERTINTERFACELUIDTONAMEA], 1, [Define to 1 if you have `ConvertInterfaceLuidToNameA`])], [], $cares_all_includes) AC_CHECK_DECL(NotifyIpInterfaceChange, [AC_DEFINE([HAVE_NOTIFYIPINTERFACECHANGE], 1, [Define to 1 if you have `NotifyIpInterfaceChange`] )], [], $cares_all_includes) AC_CHECK_DECL(RegisterWaitForSingleObject, [AC_DEFINE([HAVE_REGISTERWAITFORSINGLEOBJECT], 1, [Define to 1 if you have `RegisterWaitForSingleObject`])], [], $cares_all_includes) AC_CHECK_DECL(__system_property_get, [AC_DEFINE([HAVE___SYSTEM_PROPERTY_GET], 1, [Define to 1 if you have `__system_property_get`] )], [], $cares_all_includes) +AC_CHECK_DECL(SetFileCompletionNotificationModes, [AC_DEFINE([HAVE_SETFILECOMPLETIONNOTIFICATIONMODES], 1, [Define to 1 if you have `SetFileCompletionNotificationModes`])], [], $cares_all_includes) +AC_CHECK_DECL(WSAIoctl, [AC_DEFINE([HAVE_WSAIoctl], 1, [Define to 1 if you have `WSAIoctl`])], [], $cares_all_includes) dnl ############################################################################### diff --git a/src/lib/ares_config.h.cmake b/src/lib/ares_config.h.cmake index cff1818721..704ca7fa92 100644 --- a/src/lib/ares_config.h.cmake +++ b/src/lib/ares_config.h.cmake @@ -148,6 +148,12 @@ /* Define to 1 if you have the `GetBestRoute2' function. */ #cmakedefine HAVE_GETBESTROUTE2 1 +/* Define to 1 if you have the `WSAIoctl' function. */ +#cmakedefine HAVE_WSAIOCTL 1 + +/* Define to 1 if you have the `GetQueuedCompletionStatusEx' function. */ +#cmakedefine HAVE_GETQUEUEDCOMPLETIONSTATUSEX 1 + /* Define to 1 if you have the `ConvertInterfaceIndexToLuid' function. */ #cmakedefine HAVE_CONVERTINTERFACEINDEXTOLUID 1 @@ -160,6 +166,9 @@ /* Define to 1 if you have the `RegisterWaitForSingleObject' function. */ #cmakedefine HAVE_REGISTERWAITFORSINGLEOBJECT 1 +/* Define to 1 if you have the `SetFileCompletionNotificationModes' function. */ +#cmakedefine HAVE_SETFILECOMPLETIONNOTIFICATIONMODES 1 + /* Define to 1 if you have a IPv6 capable working inet_net_pton function. */ #cmakedefine HAVE_INET_NET_PTON 1 diff --git a/src/lib/config-win32.h b/src/lib/config-win32.h index fc533c7551..b2bc3ba400 100644 --- a/src/lib/config-win32.h +++ b/src/lib/config-win32.h @@ -237,10 +237,8 @@ # undef HAVE_NETIOAPI_H #endif -/* Threading support enabled for Vista+ */ -#if !defined(_WIN32_WINNT) || _WIN32_WINNT >= 0x0600 -# define CARES_THREADS 1 -#endif +/* Threading support enabled on Windows always (really XP+ only). */ +#define CARES_THREADS 1 /* ---------------------------------------------------------------- */ /* TYPEDEF REPLACEMENTS */ @@ -360,6 +358,15 @@ /* Define to 1 if you have the `RegisterWaitForSingleObject' function. */ #define HAVE_REGISTERWAITFORSINGLEOBJECT 1 +#if defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0600) +/* Define to 1 if you have the `SetFileCompletionNotificationModes' function. */ +# define HAVE_SETFILECOMPLETIONNOTIFICATIONMODES 1 +/* Define to 1 if you have the `WSAIoctl' function. */ +# define HAVE_WSAIOCTL 1 +/* Define to 1 if you have the `GetQueuedCompletionStatusEx' function. */ +# define HAVE_GETQUEUEDCOMPLETIONSTATUSEX 1 +#endif + #if defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0600) && \ !defined(__WATCOMC__) && !defined(WATT32) /* Define if you have if_nametoindex() */ diff --git a/src/lib/event/ares_event_win32.c b/src/lib/event/ares_event_win32.c index d7d1d65735..bf350d7439 100644 --- a/src/lib/event/ares_event_win32.c +++ b/src/lib/event/ares_event_win32.c @@ -404,10 +404,12 @@ static ares_slist_node_t *ares_afd_handle_create(ares_evsys_win32_t *ew) goto fail; } +#ifdef HAVE_SETFILECOMPLETIONNOTIFICATIONMODES if (!SetFileCompletionNotificationModes(afd->afd_handle, FILE_SKIP_SET_EVENT_ON_HANDLE)) { goto fail; } +#endif node = ares_slist_insert(ew->afd_handles, afd); if (node == NULL) { @@ -521,6 +523,11 @@ static ares_bool_t ares_evsys_win32_init(ares_event_thread_t *e) static ares_socket_t ares_evsys_win32_basesocket(ares_socket_t socket) { +#ifndef HAVE_WSAIOCTL + /* Assume we don't have an LSP and return the provided socket as the base + * socket. WSAIoctl() isn't supported on Windows XP or below */ + return socket; +#else while (1) { DWORD bytes; /* Not used */ ares_socket_t base_socket = ARES_SOCKET_BAD; @@ -558,6 +565,7 @@ static ares_socket_t ares_evsys_win32_basesocket(ares_socket_t socket) } return socket; +#endif } static ares_bool_t ares_evsys_win32_afd_enqueue(ares_event_t *event, @@ -906,6 +914,46 @@ static ares_bool_t ares_evsys_win32_process_socket_event( return ARES_TRUE; } +static BOOL ares_GetQueuedCompletionStatusEx( + HANDLE CompletionPort, + LPOVERLAPPED_ENTRY lpCompletionPortEntries, + ULONG ulCount, + PULONG ulNumEntriesRemoved, + DWORD dwMilliseconds, + BOOL fAlertable) +{ +#ifdef HAVE_GETQUEUEDCOMPLETIONSTATUSEX + return GetQueuedCompletionStatusEx(CompletionPort, lpCompletionPortEntries, + ulCount, ulNumEntriesRemoved, dwMilliseconds, fAlertable); +#else + ULONG i; + + (void)fAlertable; + + memset(lpCompletionPortEntries, 0, + ulCount * sizeof(*lpCompletionPortEntries)); + (*ulNumEntriesRemoved) = 0; + + for (i=0; i 0) { + return TRUE; + } + + return FALSE; +#endif +} + static size_t ares_evsys_win32_wait(ares_event_thread_t *e, unsigned long timeout_ms) { @@ -923,8 +971,9 @@ static size_t ares_evsys_win32_wait(ares_event_thread_t *e, * on subsequent attempts, ensure the timeout is 0 */ do { nentries = maxentries; - status = GetQueuedCompletionStatusEx(ew->iocp_handle, entries, nentries, - &nentries, tout, FALSE); + status = ares_GetQueuedCompletionStatusEx(ew->iocp_handle, entries, + nentries, &nentries, tout, + FALSE); /* Next loop around, we want to return instantly if there are no events to * be processed */ diff --git a/src/lib/event/ares_event_win32.h b/src/lib/event/ares_event_win32.h index 5d0274cd85..d0e72156f6 100644 --- a/src/lib/event/ares_event_win32.h +++ b/src/lib/event/ares_event_win32.h @@ -156,6 +156,16 @@ typedef NTSTATUS(NTAPI *NtCreateFile_t)( # define HANDLE_FLAG_INHERIT 0x00000001 # endif + +# if defined(_WIN32_WINNT) && (_WIN32_WINNT < 0x0600) +typedef struct _OVERLAPPED_ENTRY { + ULONG_PTR lpCompletionKey; + LPOVERLAPPED lpOverlapped; + ULONG_PTR Internal; + DWORD dwNumberOfBytesTransferred; +} OVERLAPPED_ENTRY, *LPOVERLAPPED_ENTRY; +# endif + #endif /* _WIN32 */ #endif /* __ARES_EVENT_WIN32_H */ diff --git a/src/lib/util/ares_threads.c b/src/lib/util/ares_threads.c index ab0b51afb7..2f4f5a7d04 100644 --- a/src/lib/util/ares_threads.c +++ b/src/lib/util/ares_threads.c @@ -68,6 +68,8 @@ void ares_thread_mutex_unlock(ares_thread_mutex_t *mut) LeaveCriticalSection(&mut->mutex); } +# if _WIN32_WINNT >= 0x0600 /* Vista */ + struct ares_thread_cond { CONDITION_VARIABLE cond; }; @@ -119,19 +121,225 @@ ares_status_t ares_thread_cond_wait(ares_thread_cond_t *cond, ares_status_t ares_thread_cond_timedwait(ares_thread_cond_t *cond, ares_thread_mutex_t *mut, - unsigned long timeout_ms) + size_t timeout_ms) { + DWORD tout; + if (cond == NULL || mut == NULL) { return ARES_EFORMERR; } - if (!SleepConditionVariableCS(&cond->cond, &mut->mutex, timeout_ms)) { + if (timeout_ms == SIZE_MAX) { + tout = INFINITE; + } else { + tout = (DWORD)timeout_ms; + } + + if (!SleepConditionVariableCS(&cond->cond, &mut->mutex, tout)) { + return ARES_ETIMEOUT; + } + + return ARES_SUCCESS; +} + +# else + +typedef enum { + ARES_W32_COND_SIGNAL = 0, + ARES_W32_COND_BROADCAST, + ARES_W32_COND_EVMAX, + ARES_W32_COND_NONE = ARES_W32_COND_EVMAX +} ares_w32_cond_event_t; + +struct ares_thread_cond { + HANDLE events[2]; + HANDLE gate; + CRITICAL_SECTION mutex; + size_t waiters; + ares_w32_cond_event_t event; +}; + +ares_thread_cond_t *ares_thread_cond_create(void) +{ + ares_thread_cond_t *cond; + + cond = ares_alloc_zero(sizeof(*cond)); + if (cond == NULL) { + return NULL; + } + + cond->events[ARES_W32_COND_SIGNAL] = CreateEvent(NULL, FALSE, FALSE, NULL); + if (cond->events[ARES_W32_COND_SIGNAL] == NULL) { + goto fail; + } + + cond->events[ARES_W32_COND_BROADCAST] = CreateEvent(NULL, TRUE, FALSE, NULL); + if (cond->events[ARES_W32_COND_BROADCAST] == NULL) { + goto fail; + } + + /* Use a semaphore as a gate so we don't lose signals */ + cond->gate = CreateSemaphore(NULL, 1, 1, NULL); + if (cond->gate == NULL) { + goto fail; + } + + InitializeCriticalSection(&cond->mutex); + cond->waiters = 0; + cond->event = ARES_W32_COND_NONE; + + return cond; + +fail: + ares_thread_cond_destroy(cond); + return NULL; +} + +void ares_thread_cond_destroy(ares_thread_cond_t *cond) +{ + if (cond == NULL) + return; + + if (cond->events[ARES_W32_COND_SIGNAL]) { + CloseHandle(cond->events[ARES_W32_COND_SIGNAL]); + } + if (cond->events[ARES_W32_COND_BROADCAST]) { + CloseHandle(cond->events[ARES_W32_COND_BROADCAST]); + } + if (cond->gate) { + CloseHandle(cond->gate); + } + DeleteCriticalSection(&cond->mutex); + + ares_free(cond); +} + +ares_status_t ares_thread_cond_timedwait(ares_thread_cond_t *cond, + ares_thread_mutex_t *mut, + size_t timeout_ms) +{ + DWORD rv; + DWORD wMilliseconds; + + if (cond == NULL || mutex == NULL) { + return ARES_EFORMERR; + } + + /* We may only enter when no wakeups active this will prevent the lost + * wakeup */ + WaitForSingleObject(cond->gate, INFINITE); + + EnterCriticalSection(&cond->mutex); + /* count waiters passing through */ + cond->waiters++; + LeaveCriticalSection(&cond->mutex); + + /* Open Gate */ + ReleaseSemaphore(cond->gate, 1, NULL); + + /* Release passed in mutex */ + ares_thread_mutex_unlock(mut); + + if (timeout_ms == SIZE_MAX) { + dwMilliseconds = INFINITE; + } else { + dwMilliseconds = (DWORD)timeout_ms; + } + rv = WaitForMultipleObjects(ARES_W32_COND_EVMAX, cond->events, FALSE, + dwMilliseconds); + + /* We go into a critical section to make sure cond->waiters isn't checked + * while we decrement. This is especially important for a timeout since the + * gate may not be closed. We need to check to see if a broadcast/signal was + * pending as this thread could have been preempted prior to + * EnterCriticalSection but after WaitForMultipleObjects() so we may be + * responsible for reseting the event and closing the gate */ + EnterCriticalSection(&cond->mutex); + cond->waiters--; + + if (cond->event != ARES_W32_COND_NONE && cond->waiters == 0) { + /* Last waiter needs to reset the event on(as a broadcast event is not + * automatic) and also re-open the gate */ + if (cond->event == ARES_W32_COND_BROADCAST) + ResetEvent(cond->events[ARES_W32_COND_BROADCAST]); + + /* Open Gate (closed by ares_thread_cond_broadcast()) since there are no + * more waiters for the event */ + ReleaseSemaphore(cond->gate, 1, NULL); + cond->event = ARES_W32_COND_NONE; + } else if (retval == WAIT_OBJECT_0 + ARES_W32_COND_SIGNAL) { + /* If specifically, this thread was signalled and there are more waiting, + * re-open the gate and reset the event */ + ReleaseSemaphore(cond->gate, 1, NULL); + cond->event = ARES_W32_COND_NONE; + } else { + /* This could be a standard timeout with more waiters, don't do anything */ + } + LeaveCriticalSection(&cond->mutex); + + /* re-lock the passed in mutex */ + ares_thread_mutex_lock(mut); + + if (retval == WAIT_TIMEOUT) { return ARES_ETIMEOUT; } return ARES_SUCCESS; } +ares_status_t ares_thread_cond_wait(ares_thread_cond_t *cond, + ares_thread_mutex_t *mut) +{ + return ares_thread_cond_timedwait(cond, mut, SIZE_MAX); +} + +void ares_thread_cond_broadcast(ares_thread_cond_t *cond); +{ + if (cond == NULL) + return; + + /* close gate to prevent more waiters while broadcasting */ + WaitForSingleObject(cond->gate, INFINITE); + + /* If there are waiters, send a broadcast event, + * otherwise, just reopen the gate */ + EnterCriticalSection(&cond->mutex); + + cond->event = ARES_W32_COND_BROADCAST; + if (cond->waiters) { + /* wake all waiters */ + SetEvent(cond->events[ARES_W32_COND_BROADCAST]); + } else { + /* if no waiters just reopen gate */ + ReleaseSemaphore(cond->gate, 1, NULL); + } + + LeaveCriticalSection(&cond->mutex); +} + +void ares_thread_cond_signal(ares_thread_cond_t *cond); +{ + if (cond == NULL) + return; + + /* close gate to prevent more waiters while signalling */ + WaitForSingleObject(cond->gate, INFINITE); + + + EnterCriticalSection(&cond->mutex); + cond->event = ARES_W32_COND_SIGNAL; + if (cond->waiters) { + /* wake one waiter */ + SetEvent(cond->events[ARES_W32_COND_SIGNAL]); + } else { + /* no waiters, just reopen the gate */ + ReleaseSemaphore(cond->gate, 1, NULL); + } + LeaveCriticalSection(&cond->mutex); +} + +# endif + struct ares_thread { HANDLE thread; DWORD id; @@ -322,7 +530,7 @@ ares_status_t ares_thread_cond_wait(ares_thread_cond_t *cond, return ARES_SUCCESS; } -static void ares_timespec_timeout(struct timespec *ts, unsigned long add_ms) +static void ares_timespec_timeout(struct timespec *ts, size_t add_ms) { # if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_REALTIME) clock_gettime(CLOCK_REALTIME, ts); @@ -347,7 +555,7 @@ static void ares_timespec_timeout(struct timespec *ts, unsigned long add_ms) ares_status_t ares_thread_cond_timedwait(ares_thread_cond_t *cond, ares_thread_mutex_t *mut, - unsigned long timeout_ms) + size_t timeout_ms) { struct timespec ts; @@ -355,6 +563,10 @@ ares_status_t ares_thread_cond_timedwait(ares_thread_cond_t *cond, return ARES_EFORMERR; } + if (timeout_ms == SIZE_MAX) { + return ares_thread_cond_wait(cond, mut); + } + ares_timespec_timeout(&ts, timeout_ms); if (pthread_cond_timedwait(&cond->cond, &mut->mutex, &ts) != 0) { @@ -470,7 +682,7 @@ ares_status_t ares_thread_cond_wait(ares_thread_cond_t *cond, ares_status_t ares_thread_cond_timedwait(ares_thread_cond_t *cond, ares_thread_mutex_t *mut, - unsigned long timeout_ms) + size_t timeout_ms) { (void)cond; (void)mut; diff --git a/src/lib/util/ares_threads.h b/src/lib/util/ares_threads.h index 95c543e6e9..d7bf551739 100644 --- a/src/lib/util/ares_threads.h +++ b/src/lib/util/ares_threads.h @@ -44,9 +44,11 @@ void ares_thread_cond_signal(ares_thread_cond_t *cond); void ares_thread_cond_broadcast(ares_thread_cond_t *cond); ares_status_t ares_thread_cond_wait(ares_thread_cond_t *cond, ares_thread_mutex_t *mut); + +/* NOTE: value of SIZE_MAX for timeout_ms means infinite */ ares_status_t ares_thread_cond_timedwait(ares_thread_cond_t *cond, ares_thread_mutex_t *mut, - unsigned long timeout_ms); + size_t timeout_ms); struct ares_thread;