Skip to content

-sNODERAWSOCKETS DNS resolution, with general readiness callback#27182

Open
guybedford wants to merge 3 commits into
emscripten-core:mainfrom
guybedford:async-dns
Open

-sNODERAWSOCKETS DNS resolution, with general readiness callback#27182
guybedford wants to merge 3 commits into
emscripten-core:mainfrom
guybedford:async-dns

Conversation

@guybedford

@guybedford guybedford commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Alternative to #27162, based to #27181 for async readiness. Implementation diff is in 0019242.

This adds real DNS resolution under -sNODERAWSOCKETS via node:dns, along with an async syscall variant of getaddrinfo for non-JSPI environments.

getaddrinfo() resolves numeric addresses and /etc/hosts entries (read fresh through emscripten's FS) synchronously, and returns a full addrinfo list. For a real hostname: without JSPI it returns EAI_AGAIN (resolve via the async API); under JSPI it suspends the wasm stack on the node:dns lookup and returns the addresses directly.

Async API (available in all builds):

  • emscripten_dns_lookup_async(node, service, hint) — same inputs as getaddrinfo(), returns a pollable fd that becomes readable when resolution completes.
  • emscripten_dns_lookup_result(fd, struct addrinfo **res) — reads the outcome: 0 on success (writing the addrinfo list to *res, freed with freeaddrinfo), or an EAI_* code.

Differences with #27162: That PR allowed the async callback for DNS lookup to be registered via emscripten_set_socket_message_callback. Instead of "hijacking" that mechanism, this attempts to build on a more general file descriptor readiness callback mechanism provided in #27181. This could take many shapes, so happy to iterate on this further based on feedback. The core concept is that generic callback readiness avoids needing new callback listeners defined for future APIs like this in future. On readiness, the actual emscripten_dns_lookup_result() just gets called again.

freeaddrinfo now frees the whole ai_next chain; EAI_AGAIN is added to the generated struct info.

Tested with test_dns_async, test_dns_callback (completion via emscripten_poll_with_callback), test_dns_async_net, test_dns_async_default, and test_dns_jspi, including PROXY_TO_PTHREAD variants.

@guybedford guybedford force-pushed the async-dns branch 2 times, most recently from 5bae5d9 to 917c563 Compare June 25, 2026 19:03
@guybedford guybedford changed the title Add asynchronous DNS resolution (emscripten_dns_lookup_async) Support proper DNS resolution, with readiness Jun 25, 2026
@guybedford guybedford changed the title Support proper DNS resolution, with readiness -sNODERAWSOCKETS DNS resolution, with shared readiness Jun 25, 2026
@guybedford guybedford changed the title -sNODERAWSOCKETS DNS resolution, with shared readiness -sNODERAWSOCKETS DNS resolution, with shared readiness callback Jun 25, 2026
@guybedford guybedford changed the title -sNODERAWSOCKETS DNS resolution, with shared readiness callback -sNODERAWSOCKETS DNS resolution, with general readiness callback Jun 25, 2026
…_callback

Adds emscripten_poll_with_callback(fd, events, timeout, cb): a non-blocking
single-fd poll that invokes cb(fd, revents) when the fd is ready or the timeout
elapses. revents is passed by value. It does not suspend the caller, so it works
without ASYNCIFY/JSPI. Returns -EBADF for a bad fd and -EPERM if the descriptor
type can't deliver readiness callbacks (checked before arming, even when ready);
closing an fd wakes its waiters with POLLNVAL.

It is meant as an integration point for async runtimes and event loops that need
to await I/O readiness without a blocking call or a stack switch: e.g. waiting
for a socket to become readable/writable, or for an async-completion fd, and
dispatching when ready. In ASYNCIFY/JSPI builds it complements blocking
poll()/select(); in plain synchronous builds it is the only way to wait on an fd
without spinning a poll loop.

To support it, fd readiness now uses a single wait-queue on the file node,
replacing three separate mechanisms (the socket event callbacks, the pipe
readable handlers, and the blocking-poll notifier):

- stream_ops.poll(stream) is now pure derivation; it no longer registers.
- producers wake waiters via $notifyPollCallback(node, flags): SOCKFS.emit
  bridges socket events, PIPEFS writes wake the read end.
- consumers register via $addPollCallback(node, cb): the async __syscall_poll
  registers one waiter per fd (re-deriving the set on wake), and
  emscripten_poll_with_callback registers a single-fd waiter.

Sockets now feed the same seam, so blocking poll()/select() on a socket is woken
by incoming data; previously sock_ops.poll() ignored the notifier.

Tests: test_poll_callback (callback readiness, -EPERM/-EBADF gate, POLLNVAL
close), test_poll_socket_blocking (blocking poll() woken by a delayed send;
hangs before this change, passes after), and the core poll/ppoll/select/pipe
blocking suites, including PROXY_TO_PTHREAD variants.
Under -sNODERAWSOCKETS, getaddrinfo() previously fabricated fake addresses via
DNS.lookup_name. This adds real resolution backed by node:dns, plus an async
getaddrinfo so names can be resolved without blocking.

getaddrinfo() now resolves numeric addresses and /etc/hosts entries (read fresh
through emscripten's FS) synchronously, and returns a full addrinfo list (one
node per address). For a real hostname:

- without JSPI it returns EAI_AGAIN (no synchronous DNS); resolve it via the
  async API below.
- under JSPI it suspends the wasm stack on the node:dns lookup and returns the
  addresses directly (gated on ASYNCIFY == 2).

The async API (available in all builds):

- emscripten_dns_lookup_async(node, service, hint) takes the same inputs as
  getaddrinfo() and returns a pollable fd that becomes readable when resolution
  completes.
- emscripten_dns_lookup_result(fd, struct addrinfo **res) reads the outcome: 0
  on success (writing the addrinfo list to *res, freed with freeaddrinfo), or an
  EAI_* code on failure.

The completion fd is a plain pollable descriptor: wait on it with poll/select,
or without blocking via emscripten_poll_with_callback. It is a regular client of
that readiness wait-queue - DNS completion does not special-case the socket
message callback.

freeaddrinfo now frees the whole ai_next chain; EAI_AGAIN is added to the
generated struct info.

Tested with test_dns_async, test_dns_callback (completion via
emscripten_poll_with_callback), test_dns_async_net, test_dns_async_default, and
test_dns_jspi, including PROXY_TO_PTHREAD variants.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant