Async DNS

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()
を呼び出すことです。
遅いリクエストがあっても他の処理をブロックしません;複数スレッドを使えばシステム全体の応答性を保てます。

メリット – シンプルで、ほぼすべての環境に存在します。
デメリット – バックグラウンドスレッド/プロセスを自前で管理する必要があります。


2.
getaddrinfo_a
(glibc)

glibc は

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 専用)。

自分の「イベント制御」や「ポータビリティ」のニーズに合わせて選択してください。

同じ日のほかのニュース

一覧に戻る →

2025/12/13 5:57

GNU Unifont

2025/12/13 7:02

Show HN: Tiny VM sandbox in C with apps in Rust, C and Zig

## Japanese Translation: uvm32 は、単一の C ファイルで書かれたミニマリストで依存関係を持たない仮想マシンサンドボックスです。 STM32L0 のような非常に小型のマイクロコントローラ上で動作し、4 KB 未満のフラッシュと 1 KB の RAM を使用します。静的割り当てのみで非同期設計となっています。 この VM は RISC‑V のサブセットを実装しており、軽量な管理インタフェースを公開しています。「if‑this‑then‑that」ロジックのために Lua、Duktape、MicroPython などの軽量スクリプトエンジンを置き換え、信頼できないコンポーネントや不安定な部品をサンドボックス化し、ターゲットコンパイラなしでモダン言語の「一度書けばどこでも実行できる」スクリプトを書けるようにすることが目的です。 主な特徴: - バイトコードアプリは C、Zig、Rust、またはアセンブリで記述可能。 - 非ブロッキングでシンプルな実行モデル。安全かつ最小限の型付けを備えた FFI を使用し、ホスト IO(stdio/ネットワーク)は想定していません。 - デザインは高速よりも安全性を優先しています。 - すべてのソースコードは `uvm32/` ディレクトリにあります。最小限のホスト例は `host‑mini` にあり、より高度なホストは `host/`、`host-parallel`、`host-arduino` にあります。 サンプルアプリケーションは VM の機能を示しています(C: helloworld, heap, conio, lissajous, maze, fib, sketch; Zig: zig‑mandel, zigtris, zigalloc, zigdoom; Rust: rust‑hello; アセンブリ: hello‑asm)。 ビルドとテスト用の Dockerfile が提供されており、`make dockerbuild`、`make dockershell` で構築・起動し、その後 `make` を実行してサンプルをコンパイル・実行します。ドキュメントはヘッダファイル `uvm32/uvm32.h` と `doc/README.md` にあります。本プロジェクトは MIT ライセンスで公開されています。

2025/12/13 5:15

Rats Play DOOM

## Japanese Translation: > **概要:** > 著者らは、ラットがDOOMをプレイできる完全にオープンソースの仮想現実装置をリリースしました。ゼロから構築されたこのシステムには、ハードウェア設計・ファームウェア・ソフトウェアがGitHubに掲載されており、他研究室でも簡単に再現または改良できます。バージョン 1(v1)はニューヨークのヴィクトール・トー氏によって開発され、ラットにDOOMコリドーを走行させるよう訓練しました。この実装はViceとPC Gamerで紹介されました。 > > 改良版(v2)はよりモジュラー化され、180°×80°の視野を持つ折りたたみ可能なAMOLEDスクリーン、新しいボールドライバー、強化された給餌器、ゲームイベントに同期した正確な10 µLの砂糖水投与が可能な改良リワード回路を備えています。追加センサーとランニングマシンのボール周囲に設置された光学フロー運動捕捉システムでトラッキング精度が向上しています。 > > ソフトウェアはPythonベースのモジュラースタック(arena_scenario.py)で、PC上で実行され、Raspberry PiとTCP経由で通信します。Piはリアルタイムセンサー読み取り・ボール駆動・リワード制御を担当します。すべてのコンポーネントはGitHubに文書化されており、3Dプリント可能なパーツや回路図も公開されています。 > > チーム(ヴィクトール・トー=ゲーマーラットコーチ、サンドル・マクラ=電気技師、アコス・ブラシュェク=ドキュメントリード)は、ラットが約2週間で慣れることを示しましたが、完全な訓練はまだ完了していません。 > > 現在の制限として、自動キャリブレーションスイートが無いため、ユーザーはセンサーの整列とリワードタイミングを手動で検証する必要があります。 > > 今後の課題は完全な訓練プロトコルの完成、キャリブレーションツールの改良、および他の行動タスクや種へ装置を拡張することです。低コストで完全にオープンなプラットフォームを提供することで、このプロジェクトは世界中の神経科学研究室の参入障壁を下げ、動物VR実験に依存する研究のスピードアップに寄与できる可能性があります。