diff --git a/ChangeLog.md b/ChangeLog.md index 9d8786a4a20fb..20c7b174288ef 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -41,6 +41,10 @@ See docs/process.md for more on how version tagging works. `emscripten_clear_immediate` are no-ops for ids that already fired. (#27720) - The `NODE_CODE_CACHING` setting was removed, as node's support for serializing WebAssembly modules has been missing for over 5 years now. +- Added `emscripten_dns_lookup_async`/`emscripten_dns_lookup_result`, an + asynchronous `getaddrinfo` that completes through a pollable fd, so a real + DNS lookup under `-sNODERAWSOCKETS` can be awaited without blocking from any + stack. 6.0.9 - 09/01/26 ---------------- diff --git a/site/source/docs/api_reference/emscripten.h.rst b/site/source/docs/api_reference/emscripten.h.rst index 87a680a412647..9778771c29ab9 100644 --- a/site/source/docs/api_reference/emscripten.h.rst +++ b/site/source/docs/api_reference/emscripten.h.rst @@ -1605,6 +1605,35 @@ Functions arbitrary ``userData`` passed to this function. + +.. c:function:: int emscripten_dns_lookup_async(const char *name, const char *service, const struct addrinfo *hints) + + Asynchronous ``getaddrinfo()``. Takes the same inputs and starts the lookup, + returning a file descriptor that becomes readable (``poll()``, ``select()``, + ``epoll``) once it completes; it may already be readable on return. Read the + outcome with :c:func:`emscripten_dns_lookup_result`, then ``close()`` the fd. + Nothing is allocated until a result is read. + + Unlike ``getaddrinfo()``, which under ``-sNODERAWSOCKETS`` can only wait for + a real DNS lookup from a pthread or with ``ASYNCIFY``/JSPI, this never blocks + and so works from the main thread of any build. + + The descriptor is a file-system stream, so the file system must be linked + in (any use of ``poll()`` or ``close()`` already does this; otherwise pass + ``-sFORCE_FILESYSTEM``). + + :returns: The file descriptor, or -1 if no descriptor is available. + +.. c:function:: int emscripten_dns_lookup_result(int fd, struct addrinfo **res) + + The outcome of an :c:func:`emscripten_dns_lookup_async` lookup. + + :returns: 0 on success, with a newly allocated ``addrinfo`` list in ``*res`` + (free it with ``freeaddrinfo()``; each successful call returns its own + list); an ``EAI_*`` error code; or ``EAI_AGAIN`` while the lookup is still + pending. ``EAI_SYSTEM`` if ``fd`` is not a lookup fd. + + Unaligned types =============== diff --git a/src/lib/libcore.js b/src/lib/libcore.js index 928777d174877..f33076d063447 100644 --- a/src/lib/libcore.js +++ b/src/lib/libcore.js @@ -1007,34 +1007,15 @@ addToLibrary({ return inetPton4(DNS.lookup_name(nameString)); }, - getaddrinfo__deps: ['$DNS', '$inetPton4', '$inetNtop4', '$inetPton6', '$inetNtop6', '$writeSockaddr', 'malloc', 'htonl', -#if NODERAWSOCKETS - '$nodeSockHelpers', -#endif -#if NODERAWSOCKETS && ASYNCIFY - '$Asyncify', -#endif - ], - getaddrinfo__proxy: 'sync', -#if NODERAWSOCKETS && (PTHREADS || ASYNCIFY) - // Returns an EAI_* code synchronously, or - for a hostname needing a real - // DNS lookup - a Promise of one, which a sync-proxied pthread awaits and - // ASYNCIFY/JSPI suspend on. - getaddrinfo__async: true, -#endif - getaddrinfo: (node, service, hint, out) => { - // Note getaddrinfo currently only returns a single addrinfo with ai_next defaulting to NULL. When NULL - // hints are specified or ai_family set to AF_UNSPEC or ai_socktype or ai_protocol set to 0 then we - // really should provide a linked list of suitable addrinfo values. - var addrs = []; + // Complete a getAddrInfo result: an EAI_* code is returned as is; a resolved + // descriptor is written to *out as an addrinfo list (freed by freeaddrinfo) + // and 0 returned. + $writeAddrInfo__internal: true, + $writeAddrInfo__deps: ['$inetNtop4', '$inetNtop6', '$writeSockaddr', 'malloc'], + $writeAddrInfo: (desc, out) => { + if (typeof desc == 'number') return desc; + var {type, proto, port} = desc; var canon = null; - var addr = 0; - var port = 0; - var flags = 0; - var family = {{{ cDefs.AF_UNSPEC }}}; - var type = 0; - var proto = 0; - var ai, last; function allocaddrinfo(family, type, proto, canon, addr, port) { var sa, salen, ai; @@ -1068,26 +1049,41 @@ addToLibrary({ return ai; } -#if NODERAWSOCKETS - // Resolve via node:dns (which honors the host's /etc/hosts), chaining one - // addrinfo per {family, addr} result into *out. - async function lookupHostname() { - var entries = await nodeSockHelpers.lookupHost(node, family); - if (typeof entries == 'number') return entries; - var head = 0, prev = 0; - for (var entry of entries) { - var ai = allocaddrinfo(entry.family, type, proto, null, entry.addr, port); - if (prev) { - {{{ makeSetValue('prev', C_STRUCTS.addrinfo.ai_next, 'ai', '*') }}}; - } else { - head = ai; - } - prev = ai; + var head = 0, prev = 0; + for (var {family, addr} of desc.entries) { + var ai = allocaddrinfo(family, type, proto, canon, addr, port); + if (prev) { + {{{ makeSetValue('prev', C_STRUCTS.addrinfo.ai_next, 'ai', '*') }}}; + } else { + head = ai; } - {{{ makeSetValue('out', '0', 'head', '*') }}}; - return 0; + prev = ai; } + {{{ makeSetValue('out', '0', 'head', '*') }}}; + return 0; + }, + + // The getaddrinfo body, allocating nothing. Returns an EAI_* code or a + // descriptor {type, proto, port, entries: [{family, addr}]}. Under + // NODERAWSOCKETS a hostname needs an asynchronous node:dns lookup: the + // descriptor then has `lookup` instead of `entries`, an async function + // resolving to a code or entries, for the caller to wait on. + $getAddrInfo__internal: true, + $getAddrInfo__deps: ['$DNS', '$inetPton4', '$inetPton6', 'htonl', +#if NODERAWSOCKETS + '$nodeSockHelpers', #endif + ], + $getAddrInfo: (node, service, hint) => { + // Note getaddrinfo currently only returns a single addrinfo with ai_next defaulting to NULL. When NULL + // hints are specified or ai_family set to AF_UNSPEC or ai_socktype or ai_protocol set to 0 then we + // really should provide a linked list of suitable addrinfo values. + var addr = 0; + var port = 0; + var flags = 0; + var family = {{{ cDefs.AF_UNSPEC }}}; + var type = 0; + var proto = 0; if (hint) { flags = {{{ makeGetValue('hint', C_STRUCTS.addrinfo.ai_flags, 'i32') }}}; @@ -1157,9 +1153,7 @@ addToLibrary({ addr = [0, 0, 0, _htonl(1)]; } } - ai = allocaddrinfo(family, type, proto, null, addr, port); - {{{ makeSetValue('out', '0', 'ai', '*') }}}; - return 0; + return {type, proto, port, entries: [{family, addr}]}; } // @@ -1190,9 +1184,7 @@ addToLibrary({ } } if (addr != null) { - ai = allocaddrinfo(family, type, proto, node, addr, port); - {{{ makeSetValue('out', '0', 'ai', '*') }}}; - return 0; + return {type, proto, port, entries: [{family, addr}]}; } if (flags & {{{ cDefs.AI_NUMERICHOST }}}) { return {{{ cDefs.EAI_NONAME }}}; @@ -1202,23 +1194,11 @@ addToLibrary({ // try as a hostname // #if NODERAWSOCKETS - // The lookup is asynchronous, so only start it where the calling stack can - // wait on the Promise: a sync-proxied pthread (PROXY_SYNC_ASYNC) awaits it, - // ASYNCIFY/JSPI suspend on it. Otherwise (the event-loop thread itself) it - // must not start at all, since it would write to *out after we have - // returned. -#if PTHREADS - if (PThread.currentProxiedOperationCallerThread) return lookupHostname(); -#endif -#if ASYNCIFY - // handleAsync holds a runtime keepalive across the suspension, so a user - // callback completing meanwhile does not exit the runtime under main(). - // Everything above this point is pure, so the ASYNCIFY rewind re-running - // this body reaches handleAsync again and takes the stored result. - return Asyncify.handleAsync(lookupHostname); -#else - return {{{ cDefs.EAI_AGAIN }}}; -#endif + // node:dns honors the host's /etc/hosts. + return {type, proto, port, lookup: async () => { + var entries = await nodeSockHelpers.lookupHost(node, family); + return typeof entries == 'number' ? entries : {type, proto, port, entries}; + }}; #else // resolve the hostname to a temporary fake address node = DNS.lookup_name(node); @@ -1228,10 +1208,46 @@ addToLibrary({ } else if (family === {{{ cDefs.AF_INET6 }}}) { addr = [0, 0, _htonl(0xffff), addr]; } - ai = allocaddrinfo(family, type, proto, null, addr, port); - {{{ makeSetValue('out', '0', 'ai', '*') }}}; - return 0; + return {type, proto, port, entries: [{family, addr}]}; +#endif + }, + + getaddrinfo__deps: ['$getAddrInfo', '$writeAddrInfo', +#if NODERAWSOCKETS && ASYNCIFY + '$Asyncify', +#endif + ], + getaddrinfo__proxy: 'sync', +#if NODERAWSOCKETS && (PTHREADS || ASYNCIFY) + // Returns an EAI_* code synchronously, or - for a hostname needing a real + // DNS lookup - a Promise of one, which a sync-proxied pthread awaits and + // ASYNCIFY/JSPI suspend on. + getaddrinfo__async: true, +#endif + getaddrinfo: (node, service, hint, out) => { + var desc = getAddrInfo(node, service, hint); +#if NODERAWSOCKETS + if (desc.lookup) { + var lookup = async () => writeAddrInfo(await desc.lookup(), out); + // Only start the lookup where the calling stack can wait on it: a + // sync-proxied pthread (PROXY_SYNC_ASYNC) awaits the Promise, ASYNCIFY/JSPI + // suspend on it. Otherwise (the event-loop thread itself) it must not + // start at all, since it would write to *out after we have returned. +#if PTHREADS + if (PThread.currentProxiedOperationCallerThread) return lookup(); +#endif +#if ASYNCIFY + // handleAsync holds a runtime keepalive across the suspension, so a user + // callback completing meanwhile does not exit the runtime under main(). + // Everything before the lookup is pure, so the ASYNCIFY rewind re-running + // the body reaches handleAsync again and takes the stored result. + return Asyncify.handleAsync(lookup); +#else + return {{{ cDefs.EAI_AGAIN }}}; +#endif + } #endif + return writeAddrInfo(desc, out); }, getnameinfo__deps: ['$DNS', '$readSockaddr', '$stringToUTF8'], diff --git a/src/lib/libsigs.js b/src/lib/libsigs.js index 601f26850933a..181e2034074c6 100644 --- a/src/lib/libsigs.js +++ b/src/lib/libsigs.js @@ -642,6 +642,8 @@ sigs = { emscripten_destroy_audio_context__sig: 'vi', emscripten_destroy_web_audio_node__sig: 'vi', emscripten_destroy_worker__sig: 'vi', + emscripten_dns_lookup_async__sig: 'ippp', + emscripten_dns_lookup_result__sig: 'iip', emscripten_enter_soft_fullscreen__sig: 'ipp', emscripten_err__sig: 'vp', emscripten_errn__sig: 'vpp', diff --git a/src/lib/libsockfs.js b/src/lib/libsockfs.js index 44f9a934e5439..3ab88565c5fc3 100644 --- a/src/lib/libsockfs.js +++ b/src/lib/libsockfs.js @@ -850,4 +850,55 @@ addToLibrary({ emscripten_set_socket_close_callback__deps: ['$_setNetworkCallback'], emscripten_set_socket_close_callback: (userData, callback) => _setNetworkCallback('close', userData, callback), + +#if PROXY_POSIX_SOCKETS == 0 + // Asynchronous getaddrinfo: same inputs, returns an fd that becomes readable + // (poll/select/epoll) once the lookup completes; it may already be readable on + // return. Read the outcome with emscripten_dns_lookup_result, then close the + // fd. Nothing is allocated until a result is read. Returns -1 if no fd is + // available. + emscripten_dns_lookup_async__deps: ['$FS', '$getAddrInfo', '$callUserCallback'], + emscripten_dns_lookup_async__proxy: 'sync', + emscripten_dns_lookup_async: (node, service, hint) => { + var stream; + try { + stream = FS.createStream({ + node: new FS.FSNode(0, '', 0, 0), + stream_ops: {poll: () => 'result' in dns ? {{{ cDefs.POLLRDNORM | cDefs.POLLIN }}} : 0}, + }); + } catch (e) { + return -1; + } + // On the open file description, so dup'd fds share the lookup. + var dns = stream.shared.dns = {}; + var desc = getAddrInfo(node, service, hint); +#if NODERAWSOCKETS + if (desc.lookup) { + // Pending: the lookup holds the runtime until it lands, like a timer. + {{{ runtimeKeepalivePush() }}} + desc.lookup().then((result) => { + {{{ runtimeKeepalivePop() }}} + dns.result = result; + callUserCallback(() => stream.node.notifyListeners({{{ cDefs.POLLRDNORM | cDefs.POLLIN }}})); + }); + return stream.fd; + } +#endif + dns.result = desc; + return stream.fd; + }, + + // The outcome of an emscripten_dns_lookup_async fd: 0 with a newly allocated + // addrinfo list in *res (the caller frees it with freeaddrinfo), an EAI_* code, + // or EAI_AGAIN while still pending. EAI_SYSTEM if fd is not a lookup fd (the + // one use here of an EAI_* code outside its getaddrinfo meaning). + emscripten_dns_lookup_result__deps: ['$FS', '$writeAddrInfo'], + emscripten_dns_lookup_result__proxy: 'sync', + emscripten_dns_lookup_result: (fd, res) => { + var dns = FS.getStream(fd)?.shared.dns; + if (!dns) return {{{ cDefs.EAI_SYSTEM }}}; + if (!('result' in dns)) return {{{ cDefs.EAI_AGAIN }}}; + return writeAddrInfo(dns.result, res); + }, +#endif }); diff --git a/src/struct_info.json b/src/struct_info.json index 9d68f3a0f7659..4ebafd31987ff 100644 --- a/src/struct_info.json +++ b/src/struct_info.json @@ -207,7 +207,8 @@ "EAI_NONAME", "EAI_SOCKTYPE", "EAI_BADFLAGS", - "EAI_AGAIN" + "EAI_AGAIN", + "EAI_SYSTEM" ], "structs": { "addrinfo": [ diff --git a/src/struct_info_generated.json b/src/struct_info_generated.json index e8cad551d543c..8e8b13775c93c 100644 --- a/src/struct_info_generated.json +++ b/src/struct_info_generated.json @@ -71,6 +71,7 @@ "EAI_OVERFLOW": -12, "EAI_SERVICE": -8, "EAI_SOCKTYPE": -7, + "EAI_SYSTEM": -11, "EALREADY": 7, "EBADE": 113, "EBADF": 8, diff --git a/src/struct_info_generated_wasm64.json b/src/struct_info_generated_wasm64.json index c08719f390c1f..1b641a4a63a30 100644 --- a/src/struct_info_generated_wasm64.json +++ b/src/struct_info_generated_wasm64.json @@ -71,6 +71,7 @@ "EAI_OVERFLOW": -12, "EAI_SERVICE": -8, "EAI_SOCKTYPE": -7, + "EAI_SYSTEM": -11, "EALREADY": 7, "EBADE": 113, "EBADF": 8, diff --git a/system/include/emscripten/emscripten.h b/system/include/emscripten/emscripten.h index 43d2f2899dd0e..81f4a9e03314c 100644 --- a/system/include/emscripten/emscripten.h +++ b/system/include/emscripten/emscripten.h @@ -68,6 +68,15 @@ void emscripten_set_socket_connection_callback(void *userData, em_socket_callbac void emscripten_set_socket_message_callback(void *userData, em_socket_callback callback); void emscripten_set_socket_close_callback(void *userData, em_socket_callback callback); +// Asynchronous getaddrinfo(): same inputs, returns an fd that becomes readable +// (poll/select/epoll) once the lookup completes, or -1 if no fd is available. +// Read the outcome with emscripten_dns_lookup_result(), then close() the fd. +struct addrinfo; +int emscripten_dns_lookup_async(const char *name, const char *service, const struct addrinfo *hints); +// 0 with a newly allocated addrinfo list in *res (free with freeaddrinfo()), +// an EAI_* code, or EAI_AGAIN while the lookup is still pending. +int emscripten_dns_lookup_result(int fd, struct addrinfo **res); + void _emscripten_push_main_loop_blocker(em_arg_callback_func func, void *arg, const char *name); void _emscripten_push_uncounted_main_loop_blocker(em_arg_callback_func func, void *arg, const char *name); #define emscripten_push_main_loop_blocker(func, arg) \ diff --git a/test/codesize/test_codesize_hello_dylink_all.json b/test/codesize/test_codesize_hello_dylink_all.json index ecc5f257dc567..c61ce14c2bd74 100644 --- a/test/codesize/test_codesize_hello_dylink_all.json +++ b/test/codesize/test_codesize_hello_dylink_all.json @@ -1,7 +1,7 @@ { - "a.out.js": 270695, + "a.out.js": 271271, "a.out.nodebug.wasm": 588098, - "total": 858793, + "total": 859369, "sent": [ "IMG_Init", "IMG_Load", @@ -467,6 +467,8 @@ "emscripten_date_now", "emscripten_debugger", "emscripten_destroy_worker", + "emscripten_dns_lookup_async", + "emscripten_dns_lookup_result", "emscripten_enter_soft_fullscreen", "emscripten_err", "emscripten_errn", diff --git a/test/sockets/test_dns_async.c b/test/sockets/test_dns_async.c new file mode 100644 index 0000000000000..dc2cd190bf1f2 --- /dev/null +++ b/test/sockets/test_dns_async.c @@ -0,0 +1,148 @@ +/* + * Copyright 2026 The Emscripten Authors. All rights reserved. + * Emscripten is available under two separate licenses, the MIT license and the + * University of Illinois/NCSA Open Source License. Both these licenses can be + * found in the LICENSE file. + * + * emscripten_dns_lookup_async(): a getaddrinfo() that never blocks. It returns + * an fd that is readable once the lookup completes, so the result can be waited + * for with poll() from any stack, including the main thread of a plain build + * where getaddrinfo() itself cannot wait. Numeric addresses and errors are + * readable at once; a hostname completes after a real node:dns lookup (or + * immediately with a fake address without -sNODERAWSOCKETS). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int fd; +int polls; + +int start(const char* name, int family, const char* service) { + struct addrinfo hints = {0}; + hints.ai_family = family; + hints.ai_socktype = SOCK_STREAM; + int fd = emscripten_dns_lookup_async(name, service, &hints); + assert(fd >= 0); + return fd; +} + +int readable(int fd) { + struct pollfd p = { .fd = fd, .events = POLLIN }; + int n = poll(&p, 1, 0); + assert(n == 0 || (n == 1 && (p.revents & POLLIN))); + return n; +} + +void check_v4(struct addrinfo* res, const char* addr) { + int n = 0; + for (struct addrinfo* ai = res; ai; ai = ai->ai_next) { + assert(ai->ai_socktype == SOCK_STREAM); + assert(ai->ai_protocol == IPPROTO_TCP); + assert(ai->ai_family == AF_INET); + struct sockaddr_in* sin = (struct sockaddr_in*)ai->ai_addr; + assert(ai->ai_addrlen == sizeof(*sin)); + assert(ntohs(sin->sin_port) == 80); + if (!addr || sin->sin_addr.s_addr == inet_addr(addr)) n++; + } + assert(n >= 1); +} + +void finish(void* arg) { + if (!readable(fd)) { + polls++; +#ifdef __EMSCRIPTEN_PTHREADS__ + // A pthread can simply block on the fd. + struct pollfd p = { .fd = fd, .events = POLLIN }; + assert(poll(&p, 1, -1) == 1 && (p.revents & POLLIN)); +#else + // The main thread cannot: retry on the next turn of the event loop. + emscripten_set_timeout(finish, 0, NULL); + return; +#endif + } + struct addrinfo* res = NULL; + assert(emscripten_dns_lookup_result(fd, &res) == 0); +#ifdef REAL_DNS +#ifndef __EMSCRIPTEN_PTHREADS__ + assert(polls > 0); +#endif + check_v4(res, "127.0.0.1"); +#else + check_v4(res, NULL); +#endif + freeaddrinfo(res); + assert(close(fd) == 0); + printf("done\n"); +} + +int main(void) { + // A numeric address needs no lookup: readable on return. + fd = start("10.9.8.7", AF_UNSPEC, "80"); + assert(readable(fd)); + struct addrinfo* res = NULL; + assert(emscripten_dns_lookup_result(fd, &res) == 0); + check_v4(res, "10.9.8.7"); + assert(!res->ai_next); + freeaddrinfo(res); + assert(close(fd) == 0); + + // So does an error. + fd = start("10.9.8.7", AF_UNSPEC, "http"); + assert(readable(fd)); + assert(emscripten_dns_lookup_result(fd, &res) == EAI_SERVICE); + assert(close(fd) == 0); + + // Nothing is allocated until a result is read: each read mints its own list, + // and closing without reading has nothing to free. + fd = start("10.9.8.7", AF_UNSPEC, "80"); + struct addrinfo* res2 = NULL; + assert(emscripten_dns_lookup_result(fd, &res) == 0); + assert(emscripten_dns_lookup_result(fd, &res2) == 0); + assert(res != res2 && res->ai_addr != res2->ai_addr); + freeaddrinfo(res); + freeaddrinfo(res2); + assert(close(fd) == 0); + fd = start("10.9.8.7", AF_UNSPEC, "80"); + assert(close(fd) == 0); + + // Not a lookup fd. + assert(emscripten_dns_lookup_result(0, &res) == EAI_SYSTEM); + + // dup() shares the one lookup; the result is readable through either fd. + fd = start("10.9.8.7", AF_UNSPEC, "80"); + int d = dup(fd); + assert(d >= 0); + assert(close(fd) == 0); + assert(readable(d)); + assert(emscripten_dns_lookup_result(d, &res) == 0); + check_v4(res, "10.9.8.7"); + freeaddrinfo(res); + assert(close(d) == 0); + + // Closing while a real lookup is still pending is fine: its result is just + // dropped. + fd = start("localhost", AF_INET, "80"); + assert(close(fd) == 0); + + // A hostname: pending until node:dns answers, EAI_AGAIN until then. (On a + // pthread the proxied calls give the main thread's loop turns in between, so + // it may already have completed.) + fd = start("localhost", AF_INET, "80"); +#if defined(REAL_DNS) && !defined(__EMSCRIPTEN_PTHREADS__) + assert(!readable(fd)); + assert(emscripten_dns_lookup_result(fd, &res) == EAI_AGAIN); +#endif + finish(NULL); + return 0; +} diff --git a/test/test_other.py b/test/test_other.py index 5c8eee6d6038c..5d45423054cb2 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -13709,6 +13709,11 @@ def test_emscripten_set_timeout(self): def test_emscripten_set_timeout_loop(self): self.do_runf('emscripten_set_timeout_loop.c', args=['-pthread', '-sPROXY_TO_PTHREAD']) + def test_dns_lookup_async(self): + # emscripten_dns_lookup_async() without NODERAWSOCKETS: every lookup + # resolves synchronously (fake addresses), readable on return. + self.do_runf('sockets/test_dns_async.c', 'done\n', cflags=['-sEXIT_RUNTIME']) + @parameterized({ 'fires': ([], 0, 'fired\ndone\n'), 'cleared': (['-DMODE_CLEARED'], 42, 'done\n'), diff --git a/test/test_sockets_node.py b/test/test_sockets_node.py index 414d27cd37d63..f000473bebd05 100644 --- a/test/test_sockets_node.py +++ b/test/test_sockets_node.py @@ -229,6 +229,12 @@ def test_noderawsockets_dns_blocking_jspi(self): # Same, but getaddrinfo() suspends the wasm stack under JSPI. self.do_runf('sockets/test_dns.c', 'done\n', cflags=['-sNODERAWSOCKETS', '-sEXIT_RUNTIME']) + @also_with_proxy_to_pthread + def test_noderawsockets_dns_async(self): + # emscripten_dns_lookup_async(): a real node:dns lookup completes through a + # pollable fd, so it can be waited for without blocking from any stack. + self.do_runf('sockets/test_dns_async.c', 'done\n', cflags=['-sNODERAWSOCKETS', '-sEXIT_RUNTIME', '-DREAL_DNS']) + def test_noderawsockets_dns_blocking_asyncify(self): # Same, unwinding the wasm stack under ASYNCIFY. self.do_runf('sockets/test_dns.c', 'done\n', cflags=['-sNODERAWSOCKETS', '-sASYNCIFY', '-sEXIT_RUNTIME'])