Question

AFAIK CFHost offers the only public API on OS X (and iOS) for DNS resolution that is asynchronous and/or can be canceled (so it is possible to implement a custom timeout). All other APIs are synchronous and cannot be canceled, so one would have to waste one thread per DNS lookup to make the operation asynchronous or stoppable (even Grand Central Dispatch would waste one thread per lookup, you just don't have to create the threads yourself). Having one blocking thread per DNS resolution call (and such a call can block rather long, on my system the timeout is 30 seconds before the call will ultimately timeout) is really not the way to go if you need to resolve a huge number of DNS hostnames.

CFHost seemed like a good work-a-round for me. It can be be used synchronously, in which case the documentation says the blocking request can be canceled from another thread, and it can be used asynchronously, in which case request is running in the background and also can be canceled, if desired, yet it will not block any thread until it succeeds or times out naturally. Internally CFHost uses getaddrinfo_async_* functions, yet this is not public API, to my knowledge these functions are private and one shouldn't use them directly.

So here is a simple piece of code I wrote to test CFHost lookups with cancel, but it is not working as expected and I have no idea why.

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <dispatch/dispatch.h>
#include <CoreServices/CoreServices.h>

int main (int argc, char ** argv ) {
    CFHostRef host;
    dispatch_time_t timeout;
    CFAbsoluteTime startTime;
    dispatch_queue_t timeoutQueue;

    startTime = CFAbsoluteTimeGetCurrent();

    host = CFHostCreateWithName(kCFAllocatorDefault, CFSTR("www.apple.com"));
    assert(host);

    timeout = dispatch_time(DISPATCH_TIME_NOW, 5 * 1000 * 1000 * 1000ull);
    timeoutQueue = dispatch_get_global_queue(
        DISPATCH_QUEUE_PRIORITY_DEFAULT, 0
    );
    assert(timeoutQueue);

    dispatch_after(timeout, timeoutQueue,
        ^{
            printf("%u: Timeout limit reached, canceling.\n",
                (unsigned)(CFAbsoluteTimeGetCurrent() - startTime)
            );
            CFHostCancelInfoResolution(host, kCFHostAddresses);
        }
    );

    printf("%u: Starting name resolution.\n",
        (unsigned)(CFAbsoluteTimeGetCurrent() - startTime)
    );
    CFHostStartInfoResolution(host, kCFHostAddresses, NULL);
    printf("%u: Name resolution terminated.\n",
        (unsigned)(CFAbsoluteTimeGetCurrent() - startTime)
    );

    return 0;
}

If the DNS servers are configured correctly, this code will resolve the name pretty fast:

0: Starting name resolution.
0: Name resolution terminated.

This is expected. However, if I "misconfigure" DNS in my system, so all DNS queries will timeout, here is what I get:

0: Starting name resolution.
5: Timeout limit reached, canceling.
30: Name resolution terminated.

Cancel timer is hit after 5 seconds and I cancel the request, yet the request will not stop, it will block for another 25 seconds. Actually if I don't cancel the request, it will also block for 30 seconds, since, as I said above, this is the natural DNS timeout of my system. So the call to CFHostCancelInfoResolution does exactly NOTHING.

To quote from Apple's CFHost documentation:

CFHostStartInfoResolution

[...]

In synchronous mode, this function blocks until the resolution has completed, in which case this function returns TRUE, until the resolution is stopped by calling CFHostCancelInfoResolution from another thread, in which case this function returns FALSE, or until an error occurs.

Okay, I am calling CFHostCancelInfoResolution from another thread, yet the function keeps blocking. Either this is a bug in the API, an error in the documentation, or I'm too stupid to use this API correctly and there is something very fundamental I'm overlooking here.

Update

This could actually really be a bug. I just tested the code above on 10.6 and it works exactly as expected, lookup is canceled after 5 seconds. On 10.7 and 10.8 the cancel call doesn't do anything, the code blocks until normal DNS timeout has been reached.

No correct solution

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top