Tuesday, July 30, 2019

HTTP のプライオリティが大きく変わろうとしている話(その他 IETF 105 雑感)

先週、モントリオールで開催された IETF 105 に参加してきました。

いろんなことがあったのですが、個人的に一番大きかったのは、HTTP/3 からプライオリティ(優先度制御)まわりの仕様を落とすことが決定したこと。

HTTP/3 は、トランスポートプロトコルである QUIC の上で動作する、次世代の HTTP プロトコルです。その設計は、QUIC ワーキングググループが、HTTP ワーキンググループから委託され、HTTP/2 の機能を移植する、という形式を取っています。

ところが、5月にロンドンで開催された QUIC ワーキンググループの中間会議で、一部参加者から HTTP/3 の優先度制御に対する不満が表明されたのです注1。それを受けて、QUIC ワーキンググループでは、HTTP/3 の優先度制御にあった HTTP/2 のそれとの差異を少なくする作業を進める一方、HTTP ワーキンググループでは、IETF 105 において、プライオリティをどうするか、議論することになっていました。

HTTP/2 の優先度制御を十分にサポートしていないサーバが多いことは事実です。まともに実装したサーバを動かしている大規模事業者はFで始まる2社くらいではないでしょうか。一方で、実装しようと思えばできるものを「嫌だから変えたい」と言って変えようとするのは、信頼感のある話ではありません。HTTP/2 の仕様策定に関わった当事者が主張するのであれば、なおさらです。

変えるのであれば、皆が納得できるようなものにすべきです。

という考えを踏まえ、今月初頭、僕は Cloudflare 社の Lucas Pardue 氏とともに、HTTP ヘッダベースの優先度制御手法を提案しました(提案仕様, 発表資料)。具体的には、以下のような特徴をもつ提案です。

  • HTTP/2 よりも単純な、各リクエストが独立した優先度を保持する方式
  • たった8種類の優先度
  • サーバとクライアントによる優先度の協調制御
  • HTTP ヘッダを利用することで、優先度制御を HTTP のバージョン非依存にするとともに、将来の拡張性を確保

HTTP/2 方式の堅持を主張するプレーヤーが減ったことで、焦点は、QUIC と HTTP/3 への影響を最小限に抑えつつ、新しい優先度制御手法に如何に軟着陸するか、という点に移りました。

結果、QUIC と HTTP の両ワーキンググループで HTTP/3 から優先度制御に関する規定を取り除くことが決定しました注2QUIC WG 議事録, HTTP WG 議事録)。HTTP/2 についても、現行の優先度制御を廃止するか、「優先度制御しない」ことを表明する機能を追加するかが議論され、後者が採択されました。また、新しい優先度制御手法を議論するサイドミーティングも開かれ、我々の提案した手法を中心に議論が行われました。

今後は、HTTP ワーキンググループにおいて組織された、新しい優先度制御手法を議論する小グループにおいて、迅速な合意が得られるかが焦点になります。

その他、僕の仕事に関係しているところでいうと、共著者の一人を務めている Encrypted SNI は徐々に進展し、また、maprg(測定と解析に関するリサーチグループ)では、Fastly で開発を進めている quicly を使った、衛星ネットワークでの QUICの パフォーマンス試験や、エンドポイントが露出するパケットあたり2ビットの情報を使って、プライバシー問題を抑えつつネットワーク事業者が輻輳の分析を行えるようにする手法の検証報告がありました。

感想:つかれた。

注1: 4月に開催された HTTP Workshop でも話があったのですが、僕は欠席したので雰囲気を知りません。
注2: 優先度を伝達するプロトコルと、その関連規定を取り除くのであって、サーバ独自の優先度制御など、プロトコルによらない手段を否定するものではありません。

Monday, July 22, 2019

pthread_once が嫌いすぎて再実装した話

pthread_once が嫌いです。なぜ嫌いかって言うと、こんな感じで、ファイルレベルのグローバル変数やグローバル関数が出現し、また、値を使う場所と初期化コードの位置が離れがちで可読性が下がるから。

static volatile BIO_METHODS *biom = NULL;

static void init_biom(void)
{
    biom = BIO_meth_new(BIO_TYPE_FD, "h2o_socket");
    BIO_meth_set_write(biom, write_bio);
    BIO_meth_set_read(biom, read_bio);
    BIO_meth_set_puts(biom, puts_bio);
    BIO_meth_set_ctrl(biom, ctrl_bio);
}

static void setup_connection(...)
{
    (いろいろ省略)

    // BIOを初期化
    static pthread_once_t init_biom_once = PTHREAD_ONCE_INIT;
    pthread_once(&init_biom_once, init_biom);
    BIO *bio = BIO_new(biom);
    ...
}


一方、pthread_onceを使う煩雑さを避けようとすると、自前でダブルチェックロックを書くことになるのですが、ダブルチェックロックをちゃんと書くのは難しい(参考:LCK10-J. ダブルチェックロック手法を誤用しない)し、実際間違えるし、毎回、間違えないように書こうとするのはストレスなんです。

というわけで、一念発起して、マクロを使って自分が本当にほしかった「once」を実装しました。

こんな感じで使います。

static void setup_connection(...)
{
    (いろいろ省略)

    // BIOを初期化
    static volatile BIO_METHODS *biom = NULL;
    H2O_MULTITHREAD_ONCE({
        biom = BIO_meth_new(BIO_TYPE_FD, "h2o_socket");
        BIO_meth_set_write(biom, write_bio);
        BIO_meth_set_read(biom, read_bio);
        BIO_meth_set_puts(biom, puts_bio);
        BIO_meth_set_ctrl(biom, ctrl_bio);
    });
    BIO *bio = BIO_new(biom);
    ...
}

グローバル変数やコールバック関数はなくなったし、初期化コードと利用コードが隣同士になって可読性が上がりました。

実際のコードは https://github.com/h2o/h2o/pull/2086 にありますので、ご参照ください。これで正しくダブルチェックロック実装できてるはず。

最後に一言。Cのマクロにブロック渡すのは超便利。