Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ See docs/process.md for more on how version tagging works.
- `MEMORY64=2` was marked as deprecated. (#27952)
- The undocumented `FS.filesystems` object was removed. Replace things like
`FS.filesystems['MEMFS']` with plain `MEMFS`. (#27940)
- Added `emscripten_dns_lookup_promise` and `emscripten_dns_lookup_callback`,
asynchronous variants of `getaddrinfo` completing through an `em_promise_t`
or a callback, so a real DNS lookup under `-sNODERAWSOCKETS` can be awaited
without blocking from any stack.

6.0.12 - 10/08/26
-----------------
Expand Down
21 changes: 21 additions & 0 deletions site/source/docs/api_reference/emscripten.h.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1605,6 +1605,27 @@ Functions
arbitrary ``userData`` passed to this function.


.. c:function:: intptr_t emscripten_dns_lookup(const char *name, const char *service, const struct addrinfo *hints)
em_promise_t emscripten_dns_lookup_promise(const char *name, const char *service, const struct addrinfo *hints)
void emscripten_dns_lookup_callback(const char *name, const char *service, const struct addrinfo *hints, void (*callback)(bool err, intptr_t result, void *arg), void *arg)

``getaddrinfo()`` as one asynchronous lookup in three variants. Each
resolves to the head of a newly allocated ``addrinfo`` list (free it with
``freeaddrinfo()``), or a negative ``EAI_*`` code.

``emscripten_dns_lookup()`` is synchronous and is what ``getaddrinfo()``
calls: where the stack cannot wait for a real lookup (the main thread of a
build without ``ASYNCIFY``/JSPI under ``-sNODERAWSOCKETS``) it returns
``EAI_AGAIN``. ``emscripten_dns_lookup_promise()`` returns an
``em_promise_t`` (see ``<emscripten/promise.h>``) fulfilled with the result.
``emscripten_dns_lookup_callback()`` calls ``callback(err, result, arg)``
on the calling thread with it once the lookup completes, always after the
call has returned; a pending callback keeps the runtime alive, like a timer.
(``err`` is never set: a failed lookup is an ``EAI_*`` ``result``.)

Without ``-sNODERAWSOCKETS`` every lookup completes synchronously.


Unaligned types
===============

Expand Down
141 changes: 69 additions & 72 deletions src/lib/libcore.js
Original file line number Diff line number Diff line change
Expand Up @@ -1002,36 +1002,15 @@ addToLibrary({
}
},

getaddrinfo__deps: ['$inetPton4', '$inetNtop4', '$inetPton6', '$inetNtop6', '$writeSockaddr', 'malloc', 'htonl',
#if NODERAWSOCKETS
'$nodeSockHelpers',
#else
'$DNS',
#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 (negative) is returned as is;
// a resolved descriptor is allocated as an addrinfo list (freed by
// freeaddrinfo) and its head returned.
$allocAddrInfo__internal: true,
$allocAddrInfo__deps: ['$inetNtop4', '$inetNtop6', '$writeSockaddr', 'malloc'],
$allocAddrInfo: (desc) => {
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;
Expand Down Expand Up @@ -1065,26 +1044,42 @@ 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;
}
return head;
},

// 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: ['$inetPton4', '$inetPton6', 'htonl',
#if NODERAWSOCKETS
'$nodeSockHelpers',
#else
'$DNS',
#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') }}};
Expand Down Expand Up @@ -1154,9 +1149,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}]};
}

//
Expand Down Expand Up @@ -1187,9 +1180,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 }}};
Expand All @@ -1199,23 +1190,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);
Expand All @@ -1225,10 +1204,28 @@ 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() as one asynchronous body with variants: resolves to the
// head of a newly allocated addrinfo list (freed with freeaddrinfo), or an
// EAI_* code (negative). Numeric addresses and errors complete
// synchronously; under NODERAWSOCKETS a hostname awaits node:dns, or is
// EAI_AGAIN where the caller cannot wait. getaddrinfo() itself
// (system/lib/libc/getaddrinfo.c) calls the synchronous form.
emscripten_dns_lookup__deps: ['$getAddrInfo', '$allocAddrInfo'],
emscripten_dns_lookup__async: ['promise', 'callback'],
emscripten_dns_lookup__proxy: 'sync',
emscripten_dns_lookup: (node, service, hint, sync) => {
var desc = getAddrInfo(node, service, hint);
#if NODERAWSOCKETS
if (desc.lookup) {
if (sync) return {{{ cDefs.EAI_AGAIN }}};
return desc.lookup().then(allocAddrInfo);
}
#endif
return allocAddrInfo(desc);
},

getnameinfo__deps: ['$readSockaddr', '$stringToUTF8',
Expand Down
4 changes: 3 additions & 1 deletion src/lib/libsigs.js
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,9 @@ sigs = {
emscripten_destroy_audio_context__sig: 'vi',
emscripten_destroy_web_audio_node__sig: 'vi',
emscripten_destroy_worker__sig: 'vi',
emscripten_dns_lookup__sig: 'pppp',
emscripten_dns_lookup_callback__sig: 'vppppp',
emscripten_dns_lookup_promise__sig: 'pppp',
emscripten_enter_soft_fullscreen__sig: 'ipp',
emscripten_epoll_listener_add__sig: 'iipp',
emscripten_epoll_listener_remove__sig: 'iipp',
Expand Down Expand Up @@ -942,7 +945,6 @@ sigs = {
fd_write__sig: 'iippp',
filledEllipseColor__sig: 'ipiiiii',
filledEllipseRGBA__sig: 'ipiiiiiiii',
getaddrinfo__sig: 'ipppp',
getnameinfo__sig: 'ipipipii',
getprotobyname__sig: 'pp',
getprotobynumber__sig: 'pi',
Expand Down
1 change: 1 addition & 0 deletions system/include/emscripten/em_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#pragma once

#include <stdbool.h>
#include <stdint.h>

/* Typedefs */

Expand Down
14 changes: 14 additions & 0 deletions system/include/emscripten/emscripten.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,20 @@ 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);

// getaddrinfo() as one asynchronous lookup in three variants. Each resolves
// to the head of a newly allocated addrinfo list (free with freeaddrinfo()),
// or a (negative) EAI_* code. emscripten_dns_lookup() is synchronous (it is
// what getaddrinfo() calls): where the stack cannot wait for a real lookup -
// the main thread without ASYNCIFY/JSPI - it returns EAI_AGAIN.
// emscripten_dns_lookup_promise() returns an em_promise_t fulfilled with the
// result; emscripten_dns_lookup_callback() calls callback(err, result, arg) on
// the calling thread with it, after returning (err is never set: a failed
// lookup is an EAI_* result).
struct addrinfo;
intptr_t emscripten_dns_lookup(const char *name, const char *service, const struct addrinfo *hints);
em_promise_t emscripten_dns_lookup_promise(const char *name, const char *service, const struct addrinfo *hints);
void emscripten_dns_lookup_callback(const char *name, const char *service, const struct addrinfo *hints, void (*callback)(bool err, intptr_t result, void *arg), void *arg);

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) \
Expand Down
14 changes: 14 additions & 0 deletions system/lib/libc/getaddrinfo.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Emscripten-specific version of musl/src/network/getaddrinfo.c: the
// synchronous call variant of emscripten_dns_lookup (see libcore.js).

#include <netdb.h>
#include <stdint.h>
#include <emscripten/emscripten.h>

int getaddrinfo(const char *restrict host, const char *restrict serv, const struct addrinfo *restrict hint, struct addrinfo **restrict res)
{
intptr_t r = emscripten_dns_lookup(host, serv, hint);
if (r < 0) return r;
*res = (struct addrinfo*)r;
return 0;
}
12 changes: 7 additions & 5 deletions test/codesize/test_codesize_hello_dylink_all.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"a.out.js": 272568,
"a.out.nodebug.wasm": 588100,
"total": 860668,
"a.out.js": 272803,
"a.out.nodebug.wasm": 588158,
"total": 860961,
"sent": [
"IMG_Init",
"IMG_Load",
Expand Down Expand Up @@ -466,6 +466,7 @@
"emscripten_date_now",
"emscripten_debugger",
"emscripten_destroy_worker",
"emscripten_dns_lookup",
"emscripten_enter_soft_fullscreen",
"emscripten_epoll_listener_add",
"emscripten_epoll_listener_remove",
Expand Down Expand Up @@ -1004,7 +1005,6 @@
"fd_write",
"filledEllipseColor",
"filledEllipseRGBA",
"getaddrinfo",
"getnameinfo",
"getprotobyname",
"getprotobynumber",
Expand Down Expand Up @@ -1838,6 +1838,7 @@
"env._tzset_js",
"env.emscripten_asm_const_int",
"env.emscripten_date_now",
"env.emscripten_dns_lookup",
"env.emscripten_get_heap_max",
"env.emscripten_get_now",
"env.emscripten_promise_create",
Expand All @@ -1847,7 +1848,6 @@
"env.emscripten_set_canvas_element_size",
"env.emscripten_wget_data",
"env.exit",
"env.getaddrinfo",
"env.getnameinfo",
"wasi_snapshot_preview1.clock_res_get",
"wasi_snapshot_preview1.clock_time_get",
Expand Down Expand Up @@ -2784,6 +2784,7 @@
"get_nprocs",
"get_nprocs_conf",
"get_phys_pages",
"getaddrinfo",
"getc",
"getc_unlocked",
"getchar",
Expand Down Expand Up @@ -4635,6 +4636,7 @@
"$get_nprocs",
"$get_nprocs_conf",
"$get_phys_pages",
"$getaddrinfo",
"$getc_unlocked",
"$getchar",
"$getchar_unlocked",
Expand Down
Loading
Loading