diff --git a/httplib.h b/httplib.h index d69c007..c19c210 100644 --- a/httplib.h +++ b/httplib.h @@ -5906,17 +5906,52 @@ inline int getaddrinfo_with_timeout(const char *node, const char *service, *res = result_addrinfo; return 0; +#elif defined(_GNU_SOURCE) && defined(__GLIBC__) && \ + (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 2)) + // #2431: gai_cancel() is non-blocking and may return EAI_NOTCANCELED while + // the resolver worker still references the stack-local gaicb. The cancel + // path therefore waits (gai_suspend with no timeout) for the worker to + // actually finish before letting the stack frame go. The trade-off is that + // a wedged DNS server can hold this thread for the system resolver timeout + // (~30s by default) past the caller's connection timeout. + struct gaicb request {}; + struct gaicb *requests[1] = {&request}; + struct sigevent sevp {}; + struct timespec timeout { + timeout_sec, 0 + }; + + request.ar_name = node; + request.ar_service = service; + request.ar_request = hints; + sevp.sigev_notify = SIGEV_NONE; + + int rc = getaddrinfo_a(GAI_NOWAIT, requests, 1, &sevp); + if (rc != 0) { return rc; } + + auto cleanup = scope_exit([&] { + if (request.ar_result) { freeaddrinfo(request.ar_result); } + }); + + int wait_result = gai_suspend(requests, 1, &timeout); + + if (wait_result == 0 || wait_result == EAI_ALLDONE) { + int gai_result = gai_error(&request); + if (gai_result == 0) { + *res = request.ar_result; + request.ar_result = nullptr; + return 0; + } + return gai_result; + } + + gai_cancel(&request); + while (gai_error(&request) == EAI_INPROGRESS) { + gai_suspend(requests, 1, nullptr); + } + return wait_result; #else // Fallback implementation using thread-based timeout for other Unix systems. - // - // The previous Linux/glibc path used getaddrinfo_a(GAI_NOWAIT) with a - // stack-local gaicb. On timeout it called gai_cancel(), which is non- - // blocking and may return EAI_NOTCANCELED -- the resolver worker would - // then write back to the destroyed stack frame after this function had - // already returned (#2431). The std::thread + shared_ptr fallback below - // is correct for the same reason it works on other platforms: the - // worker captures shared ownership of the state, so it stays alive as - // long as anyone (caller or worker) still holds a reference. struct GetAddrInfoState { ~GetAddrInfoState() {