0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Cloudflare Workers】"A stalled HTTP response was canceled to prevent deadlock. ..."エラーの発生と対処

Posted at

あるRemixアプリをCloudflare Workersで動かしていたところ、"A stalled HTTP response was canceled to prevent deadlock. This can happen when a Worker calls fetch() or cache.match() several times without reading the bodies of the returned Response objects. There is a limit on the number of concurrent HTTP requests that can be in-flight at one time. Normally, additional requests made beyond that limit are delayed until previous responses complete. However, because the Worker did not read the responses, they would never complete. Therefore, to prevent deadlock, the oldest response was canceled. To avoid this warning, make sure to either read the body of every HTTP Response or call response.body.cancel() to cancel a response that you don't plan to read from."というログが出力された。その際のリクエスト(画面表示)にはDev Consoleで見る限り30~40秒ほどの時間がかかっており、体感的にもめっちゃ遅くて、なにか異常が起きていることは明らかだった。が、このメッセージでググったりDiscord検索してもこれといった事例が見つからず、実際画面表示は遅いけど最終的には表示されるので、支障をきたすレベルではないため放置しようと思ってたりもした。

改めてメッセージを読んでCloudflare Workersのドキュメントを見てみると、「一度のリクエストで同時に実行できる外部コネクションは6つまで」という制限があることがわかった。
https://developers.cloudflare.com/workers/platform/limits/#simultaneous-open-connections

このドキュメントはfetchで外部にHTTPリクエスト投げる際の例を主に挙げているが、その他TCPコネクションの確立も対象に入っているため、Workersから外部のDBに接続しているような場合はその対象になる。で、自分のケースだと、WorkersからSupabaseに接続しており、この制限に触れたのだった。


このRemixアプリでは、すごい簡単にいうと、loaderの中でDBにクエリ投げる箇所が調べてみると合計で7つあって、これが上記の制限に触れたらしい、ということが想像できた。ざっくり以下のような実装だった:

app/routes/hoge.tsx
import {getHogehoge} from '~/services/hogehoge';
export async function loader({request}:LoaderFunctionArgs) {
    const hoge = await getHogeHoge();
}
services/hogehoge.ts
function getSubHogehoge1() {
    const db = getConnection();
    const result = await db.select().from(hogehoge).where(eq(hogehoge.id,1));
    return result;
}
function getSubHogehoge2() {
    const db = getConnection();
    const result = await db.select().from(hogehoge).where(eq(hogehoge.id,2));
    return result;
}
// ... みたいなfunctionが合計7個つらつらと続く

export async function getHogehoge() {
    const hoge1 = await getSubHogehoge1(); 
    const hoge2 = await getSubHogehoge2();
    // ... みたいなfunction読んで結果を変数につめるコードが合計7行つらつらと続く

    return {hoge1,hoge2,hoge3,hoge4,hoge5,hoge6,hoge7};
}

Workersでは仕様上リクエストのたびにDBコネクションを生成する必要がある。(参考)というのを、「HTTPリクエストの単位」でやればいいのに「DBクエリを発行する単位」で無駄に細かくやっていたのが根本的な原因である。要するにDBコネクションを生成する箇所がおかしかった。RemixにおいてHTTPリクエストを処理する一番表面的な層はloaderactionになるわけだが、この中でDBコネクションを生成するという発想が(なぜか)浮かばなかった。ただの仕様の理解ミスで、振り返って見ると単純な話だった。

ちなみにちょっと厄介なのが、これはローカルでwranglerで動かしてる分には再現しないことだ。Cloudflare Workersに乗せて初めて発生するエラーで、当初は何が原因かわからなかった(最初はWorkersプラットフォームの障害か不具合を疑ってしまった)。Wranglerで問題が起きるようならWorkersでも起きると判断できると思っていたので、逆に言えばWranglerで問題起きなければWorkersでも起きないと思い込んでいた。そんなことはないようなので気を付けようと思いました。(小並感)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?