Hacktoberfest 2026:维护者为十月标记出来的 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 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

还没有人认领这个 Issue。

评估

难度
4/5
预计耗时
3-5 天
新手友好度
15/100
Issue 类型
缺陷
描述清晰度
基本清楚
活跃度
活跃

调研方向

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
平均合并
10 小时 42 分钟
30 天内合并 PR
361

环境准备

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

solidjs/solid 的其他 Issue

查看 solidjs/solid 的全部 Issue

相似的 Issue

更多 TypeScript Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。