
2025/12/13 1:52
Async DNS
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
この記事では、バックグラウンドスレッドを生成したりシグナルを使用せずにノンブロッキング DNS クエリを実行する方法について説明しています。著者はまず
pthread_cancel を使って非同期リクエストのタイムアウト処理を試みましたが、失敗しました。他にも検討したオプションとして、別スレッドで getaddrinfo() を実行する(古典的な解決策)、glibc の getaddrinfo_a(非同期だがポータブルではなくイベントループに組み込むのが難しい)、そして c‑ares(スレッドバックエンドまたはイベント駆動システムを使用できるが、コールバックでチャンネル停止を防ぐために追加の後処理が必要)があります。著者は c‑ares デモを OpenBSD の asr ライブラリへ切り替えて書き直しました。asr は単一呼び出しの非同期 API を提供し、OpenBSD のイベントループ機構(OpenSMTPD や libc で使用)とシームレスに統合できます。新しいコードは元の c‑ares サンプルよりも短く、明瞭です。この投稿(2025年9月25日、ユーザー tedu が執筆)は、将来のネットワークアプリケーションが asr を採用することで DNS 処理を簡素化し、バグを減らし、メモリ使用量を削減し、イベント駆動型プログラムのパフォーマンスを向上させることができると示唆しています。本文
CURL – 非同期 DNS の代替手段
Curl は
pthread_cancel を使って非同期 DNS クエリのタイムアウトを試みましたが、失敗に終わりました。他にどんな方法がありますか?
1. getaddrinfo
getaddrinfo古典的なアプローチは、別スレッド(あるいはプロセス)で
getaddrinfo() を呼び出すことです。遅いリクエストがあっても他の処理をブロックしません;複数スレッドを使えばシステム全体の応答性を保てます。
メリット – シンプルで、ほぼすべての環境に存在します。
デメリット – バックグラウンドスレッド/プロセスを自前で管理する必要があります。
2. getaddrinfo_a
(glibc)
getaddrinfo_aglibc は
getaddrinfo_a を提供し、内部でスレッド処理を行います。非ポータブルで、イベントループときれいに統合できない点が欠点ですが、システムに入っている場合は便利です。
3. c‑ares
独立した DNS ライブラリで、スレッドベースとイベント駆動の両方をサポートしています。
- スレッドバックエンド:
と同じコールバックスタイル。getaddrinfo_a - イベント駆動バックエンド:チャネルロックを保持しながらコールバックが呼ばれるため、処理は最小限に抑える必要があります。
デモ(スレッドベース)
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <poll.h> #include <arpa/inet.h> #include <ares.h> struct server { char name[32]; char ip[16]; int status; }; struct everything { struct server servers[1]; int nservers; struct pollfd pfds[4]; int npfds; }; static void addrinfo_cb(void *arg, int status, int timeouts, struct ares_addrinfo *result) { struct server *s = arg; s->status = 3; if (!result) return; for (struct ares_addrinfo_node *node = result->nodes; node; node = node->ai_next) { if (node->ai_family == AF_INET) { struct sockaddr_in *in = (void *)node->ai_addr; inet_ntop(node->ai_family, &in->sin_addr, s->ip, sizeof(s->ip)); } } } static void socket_cb(void *arg, ares_socket_t fd, int readable, int writable) { struct everything *state = arg; printf("socket: %d r/w: %d %d\n", fd, readable, writable); int idx = -1; for (int i = 0; i < 4; ++i) { if (state->pfds[i].fd == fd) { idx = i; break; } } if (idx == -1) { for (int i = 0; i < 4; ++i) if (state->pfds[i].fd == -1) { idx = i; state->pfds[idx].fd = fd; state->npfds++; break; } } if (idx == -1) abort(); if (!readable && !writable) { state->pfds[idx].fd = -1; state->npfds--; return; } state->pfds[idx].fd = fd; state->pfds[idx].events = 0; if (readable) state->pfds[idx].events |= POLLIN; if (writable) state->pfds[idx].events |= POLLOUT; } int main(int argc, char **argv) { struct everything s = { .servers[0] = {.name = "", .status = 1}, .nservers = 1 }; strlcpy(s.servers[0].name, argv[1], sizeof s.servers[0].name); for (int i = 0; i < 4; ++i) s.pfds[i].fd = -1; ares_library_init(ARES_LIB_INIT_ALL); struct ares_options opts = { .flags = ARES_FLAG_EDNS | ARES_FLAG_DNS0x20, .sock_state_cb = socket_cb, .sock_state_cb_data = &s }; int optmask = ARES_OPT_FLAGS | ARES_OPT_SOCK_STATE_CB; ares_channel_t *ch; ares_init_options(&ch, &opts, optmask); while (1) { printf("top of loop\n"); for (int i = 0; i < s.nservers; ++i) { struct server *srv = &s.servers[i]; switch (srv->status) { case 1: { struct ares_addrinfo_hints h = { .ai_family = AF_UNSPEC, .ai_flags = ARES_AI_CANONNAME }; ares_getaddrinfo(ch, argv[1], NULL, &h, addrinfo_cb, srv); srv->status = 2; } break; case 2: printf("woke up while working\n"); break; case 3: printf("got it, done: %s -> %s\n", srv->name, srv.ip); return 0; } } if (s.npfds == 0) { puts("confused. nothing to poll"); return 1; } int r = poll(s.pfds, s.npfds, 2000); printf("poll results: %d\n", r); if (r > 0) { ares_fd_events_t ev[4]; int nev = 0; for (int i = 0; i < 4; ++i) if (s.pfds[i].revents) { ev[nev].fd = s.pfds[i].fd; ev[nev].events = 0; if (s.pfds[i].revents & (POLLERR|POLLHUP|POLLIN)) ev[nev].events |= ARES_FD_EVENT_READ; if (s.pfds[i].revents & POLLOUT) ev[nev].events |= ARES_FD_EVENT_WRITE; ++nev; } ares_process_fds(ch, ev, nev, 0); } } }
コールバックはやや煩雑です ― イベントループをライブラリに密接に結びつける必要があります。
4. dns.c
c‑ares に同梱されている代替実装で、デモコードと実装が混在しており、イベントループ用のクリーンな API は存在しません。
5. asr(OpenBSD)
OpenBSD の libc と OpenSMTPD が使用する軽量 DNS リゾルバです。
スレッドは使わず、呼び出し側がイベントをプッシュします。
メリット – 最小構成でスレッド不要、イベントループに自然に組み込めます。
デメリット – OpenBSD 専用(OpenSMTPD にはポータブル版があります)。
デモ
#include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <poll.h> #include <netdb.h> #include <asr.h> #include <arpa/inet.h> struct server { char name[32]; char ip[16]; int status; struct asr_query *aq; int ar_fd; }; int main(int argc, char **argv) { struct server srv = { .name = "", .status = 1 }; strlcpy(srv.name, argv[1], sizeof srv.name); while (1) { struct pollfd pfds[4]; int npfds = 0; printf("top of loop\n"); switch (srv.status) { case 1: { struct addrinfo hints = { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM }; srv.aq = getaddrinfo_async(srv.name, "80", &hints, NULL); srv.status = 2; } /* fallthrough */ case 2: { printf("ready to run\n"); struct asr_result ar; int rv = asr_run(srv.aq, &ar); switch (rv) { case 0: pfds[npfds].fd = ar.ar_fd; pfds[npfds].events = 0; if (ar.ar_cond == ASR_WANT_READ) pfds[npfds].events |= POLLIN; else pfds[npfds].events |= POLLOUT; ++npfds; srv.ar_fd = ar.ar_fd; srv.status = 3; break; case 1: for (struct addrinfo *r = ar.ar_addrinfo; r; r = r->ai_next) if (r->ai_family == AF_INET) { struct sockaddr_in *in = (void *)r->ai_addr; inet_ntop(r->ai_family, &in->sin_addr, srv.ip, sizeof srv.ip); } srv.status = 4; } } break; case 3: printf("woke up while working\n"); break; case 4: printf("got it, done: %s -> %s\n", srv.name, srv.ip); return 0; } if (npfds == 0) continue; int r = poll(pfds, npfds, 2000); printf("poll results: %d\n", r); if (r > 0) for (int i = 0; i < npfds; ++i) for (int j = 0; j < 1; ++j) if (pfds[i].fd == srv.ar_fd) srv.status = 2; } }
この API は典型的な
read/write に近く、応答が返ってこない場合は「あとでやり直し」と通知されます。
結論
- スレッド/プロセス – 最も簡単ですが、バックグラウンドワーカーを増やす必要があります。
- glibc の
– 便利だが非ポータブル。getaddrinfo_a - c‑ares – 強力だがコールバックが多く、イベント統合は手動で行う必要があります。
- asr(OpenBSD)– クリーンでスレッド不要、イベントループに適しています(OpenBSD 専用)。
自分の「イベント制御」や「ポータビリティ」のニーズに合わせて選択してください。