Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

With serverFunctions.components on, data server functions called during hydration never reach the network (frames intercept waits forever)

クローズ
#4,033 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
15/100
issue の種類
バグ
明瞭さ
おおむね明確
活発さ
活発
技術スタック
javascript, typescript

調査の方向性

Start in packages/web/frames/src/client.ts, where the intercept passed to createServerComponentHandler (around line 1683) calls boundaryMayArrive() and awaitBoundary(id), then read the arrivals cache. Also read subFetch in packages/solid/src/client/hydration.ts and the tracing check from #4013. Reproduce by loading examples/room /live with components: true. Done means data server functions called during hydration reach the network and the summary leaves "summarizing…".

索引モデルが issue の本文から書いたものです。

説明

bug

In an app with serverFunctions: { components: true }, a data server function (one that returns data, not a component) called during hydration never sends its request. The frames client's intercept parks the call waiting for a <solid-frame> boundary that a data function never has, and in two ways that wait never ends.

examples/room's /live page shows it on next (4937f0a):

  • Never connects: there are no /_server/live/* or /_server/data/summary requests at all, and no console errors.
  • UI stuck: the status pills stay at "connecting", presence shows "0 here" and "Not in the room yet", and the summary stays at "summarizing…".
  • Writes go one way: a message posted from one tab reaches the server but never shows in another tab.
  • Everywhere: it happens in dev over HTTP/1.1, in dev over HTTPS/HTTP/2, and in the production build, while _$HY.done === true.
  • Controls: room's / page, a live server component, works on the same server. The same /live source served with components: true removed connects, with both tabs in presence and posts crossing. examples/board doesn't set components: true, which is why it connects on the same build.

This is related to #3819, which uses the same page as its motivating case, but that issue is about the connection opening late. Here it never opens.

Cause

In packages/web/frames/src/client.ts, the intercept passed to createServerComponentHandler (around line 1683) runs for every server-function call, data functions included. While boundaryMayArrive() is true, meaning _$HY.done is unset or a fragment is pending, it treats a function with no boundary element as "a boundary that may still arrive" and returns awaitBoundary(id). That is a promise cached per function id in arrivals, and the call doesn't go to the network. A data function never gets a boundary, so it can only be released by the reveal hook. Two defects keep that release from ever happening.

1. The cached arrival promise can be a MockPromise that never resolves (confirmed)
  • The first call happens inside the hydration trace (subFetch in packages/solid/src/client/hydration.ts), which swaps the global Promise for MockPromise.
  • So new Promise(r => (resolve = r)) in awaitBoundary creates a MockPromise and resolve stays undefined.
  • In the browser, the arrivals entries for presence, transcript, roomCard and archive all had typeof resolve === "undefined", created exactly when Promise !== native.
  • The takeover calls after the root pass hit the same cached dead entry and hang.
  • If a reveal ever tries to release them, it throws TypeError: arrival.resolve is not a function. That throw was reproduced.

This is the same class of bug as #4013 (a lazy() loader promise made under hydration tracing). That fix added a tracing check, and frames doesn't consult it.

2. Waiters registered after the page's last reveal are never released (confirmed)

The summary source hangs even though its arrival was created with a real Promise:

  • Waiters are released only from a reveal notification, and only if hy.done && !hy.fr.pending() holds at that moment.
  • Timeline: reveals at about 520 ms and 720 ms, then the last one (the 4 s Archive panel) at about 4015 ms, with done still unset and pending() false.
  • _$HY.done = true is set about 5 ms later, from a setTimeout in drainHydrationCallbacks. So the last reveal kept the waiters, and no reveal comes after it.
  • Calls made by hydration-end recomputes (summary uses ssrSource: "client") register in that gap and wait forever.
  • Firing a synthetic reveal after done (with the dead MockPromise entries dropped first) immediately sent GET /_server/data/summary, and the summary started streaming.

A fresh call after hydration goes straight to the network, because boundaryMayArrive() is false once done is set. Only calls made in the hydration window are lost, and any components: true app that calls data server functions during hydration should hit this.

Possible fixes (untested)

  • Don't defer non-component functions in the intercept: a data function can never have a boundary.
  • Don't create or cache arrival promises while hydration tracing is on, as #4013's fix does for lazy().
  • Release outstanding waiters at hydration end (onHydrationEnd) as well as on reveals.

Regression?

Unknown. The intercept, awaitBoundary and boundaryMayArrive code predates the held-swap policy note (#2964) in the comments, and an older build (around 06c7717dd) behaves the same.

主要言語
TypeScript
スター
36.1k
フォーク
1.1k
平均マージ
11時間 32分
マージ済み PR(30日)
356

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

solidjs/solid のほかの issue

solidjs/solid の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。