2.5 The Event Loop

2.5.1 One thread and one poll #

resolvd runs on one thread. Every input is a descriptor in one poll call, and the engine sees time only when the loop hands it the current instant.

2.5.2 What is polled #

Each iteration builds the poll set afresh:

  1. the native listener, except during its accept-error backoff;
  2. the stub UDP socket;
  3. the stub TCP listener, except during its accept-error backoff;
  4. the netd channel, while connected;
  5. the registry watch, while armed;
  6. every native connection still sending its request;
  7. every stub TCP connection still sending its query;
  8. every upstream socket — for reading, or for writing while a TCP connection is still being established or its query is still being sent;
  9. native and stub TCP replies waiting to write, for writability;
  10. native and stub TCP streams awaiting engine answers, for hangup and error indications only. Readability and read-half-close are not requested, preserving supported half-closes without spinning on EOF.

2.5.2.1 The poll timeout #

The poll timeout is the earliest of: the next upstream transaction deadline, the next netd reconnection time while disconnected, the moment the oldest stub TCP connection reaches its 10-second bound, the moment the oldest native connection reaches its 5-second bound, the earliest queued reply write deadline, and the end of either listener's 250 ms accept-error backoff. With none of these pending, the loop sleeps until a descriptor is ready.

The timeout is the time to that moment in whole milliseconds, rounded down. A moment less than a millisecond away gives a timeout of zero, so the loop polls again without sleeping until the moment has passed.

2.5.3 Service order #

After poll returns, ready descriptors are serviced in this order:

  1. waiting native and stub TCP streams with definite hangup/error, cancelling their questions and upstream transactions;
  2. upstream sockets — replies that have arrived are answers already owed;
  3. queued native replies, then queued stub TCP replies, whose sockets are writable;
  4. the stub UDP socket, reading at most 64 datagrams;
  5. the stub TCP listener, accepting at most 64 connections;
  6. stub TCP connections still sending queries;
  7. the native listener, accepting at most 64 connections;
  8. native connections still sending requests;
  9. the netd channel;
  10. the registry watch.

The fixed datagram/accept batches yield to the other doors and timers even while one listener remains continuously ready.

2.5.4 Timers after descriptors #

Once the ready descriptors have been serviced, the timers run, in this order: transactions at or past their deadline fail over to their next attempt (§4.6), a due netd reconnection is attempted (§3.2), stub TCP connections 10 seconds old or older are closed, expired reply writes are closed, and native connections 5 seconds old or older are closed.

2.5.5 Answers within an iteration #

A question that can be answered without the network — synthetic, from the cache, or refused — has its answer prepared and its first write attempted within the iteration that read it.

A question that needs the network sends its first transaction within that iteration too, and has its answer prepared in the iteration that sees its deciding reply or its last deadline. Delivery may continue over later writable events for a slow native or TCP reader.

2.5.6 Nonblocking reply writes #

Native and stub TCP replies get one nonblocking write attempt immediately. If the socket cannot accept the whole frame, the unwritten suffix is queued and resumed when that socket is writable. Each ready event makes one write attempt; partial progress is retained without duplicating bytes. The connection closes after the whole frame is written, on a write error, or when its fixed one-second write deadline expires. Progress does not extend that deadline. Other inputs and timers continue to be serviced.

At most 256 unfinished native replies and 256 unfinished stub TCP replies are queued. Each frame is bounded by its protocol ceiling (§5.1, §6.2). Even when a queue is full, a new reply gets its immediate write attempt; if it too needs queue space, that connection closes. Ready readers can therefore still receive replies while stalled readers occupy the queue.

Stub UDP replies and upstream queries are not written this way. The netd subscription request is written with a two-second timeout when the channel is opened (§3.1).

Edit this page