テック技報最新号へ →
インフラ

Cloudflare Workers × better-auth: hanging promise で「特定ユーザーだけ全滅」する

リクエスト中に作った promise を isolate 寿命でキャッシュすると、クライアント切断後に永遠に pending(hanging promise)になりうる。better-auth の遅延初期化がその罠に嵌ると、同じ isolate を掴んだユーザーだけ認証が永久ハングする。

@techtalkjp · 2026-10-12
#infra#cloudflare#workers#better-auth#debugging

どんなときに使う?

  • Workers + better-auth(や類似の遅延初期化ライブラリ)で、「特定の人だけ全リクエストが応答しない/ブラウザ再起動で直る」
  • zone ログは 499、Workers invocation は canceled なのに、静的アセットは同じ接続で返っている
  • モジュール/インスタンスに初期化 promise をキャッシュしている

やり方(防御の 3 層)

原因の骨格: workerd は、リクエストの IoContext に属する promise を、クライアント切断後に resolve/reject しない。その promise を isolate 共有キャッシュに載せると、以後その isolate 上の await が全部止まる。

  1. 危険な動的 import を先回りで埋める
    例: node:async_hooks の AsyncLocalStorage をグローバルで静的 import し、better-auth が遅延解決する前に共有コンテキストへセットする(バージョン依存の内部キーなのでアップグレード時は要確認)。
  2. 残る遅延初期化を ctx.waitUntil に係留する
    waitUntil は切断後も IoContext を延命するので、初期化を担いだリクエストが abort されても完了できる。係留側にもタイムアウト(hang 検知)を付ける。
  3. API 呼び出しに hang 検知とインスタンス作り直し
    Promise.race で数秒待っても返らなければキャッシュを捨てて再生成。回復しなければ未認証扱いで応答し、ハングを伝播させない。破棄は「自分が掴んでいたインスタンス」に限定し、並行リクエストの競合に注意。

診断の近道: 段階ログで「どこまで通ったか」を見る/NetLog で h2 PING が生きているか確認する。

注意点

  • Abort や「前の fetch をキャンセルする最適化」が、初期化中断の頻度を上げる
  • 公式修正前のライブラリでは外側の多層防御が現実的(better-auth には upstream issue 報告あり)
  • 自分のコードでも「リクエスト中の promise をモジュール/isolate キャッシュに載せない」が一般則。載せるなら waitUntil か hang 検知を必須にする

出典

X の元の投稿を開く