MEDIUM: dns: bind the nameserver sockets to the initiating thread

There's still a big architectural limitation in the dns/resolvers code
regarding threads: resolvers run as a task that is scheduled to run
anywhere, and each NS dgram socket is bound to any thread of the same
thread group as the initiating thread. This becomes a big problem when
dealing with multiple nameservers because responses arrive on any thread,
start by locking the resolvers section, and other threads dealing with
responses are just stuck waiting for the lock to disappear. This means
that most of the time is exclusively spent causing contention. The
process_resolvers() function also also suffers from this contention
but apparently less often.

It turns out that the nameserver sockets are created during emission
of the first packet, triggered from the resolvers task. The present
patch exploits this to stick all sockets to the calling thread instead
of any thread. This way there is no longer any contention between
multiple nameservers of a same resolvers section. Tests with a section
having 10 name servers showed that the CPU usage dropped from 38 to
about 10%, or almost by a factor of 4.

Note that TCP resolvers do not offer this possibility because the
tasks that manage the applets are created earlier to run anywhere
during config parsing. This might possibly be refined later, e.g.
by changing the task's affinity when it first runs.

The change was kept fairly minimal to permit a backport once enough
testing is conducted on it. It could address a significant part of
the trouble reported by Felipe in GH issue #3101.
This commit is contained in:
Willy Tarreau 2025-09-10 16:37:40 +02:00
parent 07c10ec2f1
commit d624aceaef

View File

@ -121,7 +121,13 @@ static int dns_connect_nameserver(struct dns_nameserver *ns)
/* Add the fd in the fd list and update its parameters */ /* Add the fd in the fd list and update its parameters */
dgram->t.sock.fd = fd; dgram->t.sock.fd = fd;
fd_insert(fd, dgram, dgram_fd_handler, tgid, tg->threads_enabled);
/* let's stick the FD to the initiator thread, this will ensure that
* most of the time, a resolver will not try to access its structure
* at the same time as a response is processed, and will eliminate
* locking contention.
*/
fd_insert(fd, dgram, dgram_fd_handler, tgid, ti->ltid_bit);
fd_want_recv(fd); fd_want_recv(fd);
return 0; return 0;
} }