|
|
@@ -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() {
|