持久频道云工适配
频道。豫 用豫言写在云工宿主库的具名广播、可读流读取器与 Promise 之上,不增加任何宿主 JavaScript。它实现[持久频道接口规范](../../../豫言操作系统接口/持久频道/规范.汉语.md) 0.1.0:订阅、保序发布、限时等待、取消、关闭与订阅数。
实现
- 宿主频道:每个逻辑频道对应宿主广播
持久频道:<名>(前缀使它与应用直接使用的云工广播新流频道互不相通)。宿主广播属于宿主对象,即同一个 Durable Object 实例:注册表随对象存续,被驱逐即丢失;每实例至多 64 个频道,每频道至多 256 个读者,单块 64 KiB,每读者积压 1 MiB。 - 帧:消息文字的 UTF-8 字节原样作为一块发布,不加头,所以文字额度就是宿主的 64 KiB;控制帧以字节
0xFF(合法 UTF-8 绝不以它起首)起首:FF 02是探测帧,FF 01加原因文字是关闭帧。发布时拒绝以0xFF起首的文字。 - 订阅数与订阅上限:宿主没有“数订阅”的原语,本适配向频道发一个探测帧,用宿主返回的送达数作订阅数(订阅端读到探测帧直接跳过,剩余超时按已用时间重算,所以探测不会拉长或缩短等待)。
订阅持久频道先探测,达 256 时抛可捕获的豫言异常;频道数超 64 无法预先得知,由宿主拒绝(宿主报错,不可捕获)。 - 等待与超时:对订阅读取器发
read(),用Promise.allSettled包装(不产生未处理拒绝),与scheduler.wait计时(自己的 AbortController,结束即撤销)做Promise.race;超时不丢弃尚未完成的读取承诺,下一次等待继续使用它,因此不会丢消息。读到的字节经云工句柄取字节取出后立即释放句柄,每次等待都释放临时句柄(宿主每事件句柄上限 4096,单订阅收发数千条已验)。 - 关闭:先向全部订阅发关闭帧(带原因),再关闭宿主频道,订阅端读完排队消息后得状态 2。积压被宿主断开时,订阅端读取以错误告终,适配映射为“订阅积压超过 1 MiB,已被断开”。
- 登记簿与订阅柄:每个 Wasm 实例内一张订阅登记簿(柄、读取器号、待读承诺号、终态、终因)。订阅柄是“实例标记:读取器句柄号”,实例标记取
crypto.randomUUID()的前 13 个字符,另一个事件拿到它得到豫言异常。取消用云工调用方法安全调cancel(即使流已因积压出错也不会让宿主异常穿出),并释放读取器句柄;从不调用releaseLock。 - 只能在持久对象事件里使用:事件种类须以
durable-起首,否则抛可捕获的豫言异常(宿主原语本身在非持久对象事件里会报不可捕获的错误)。 - 所有内部辅助名都以
持久频道内起首。
部署要求
- 全局
scheduler.wait:Workers 自带;Node 宿主用node:timers/promises的scheduler垫片经创建云工宿主({全局})传入,见一致性验证/工具.mjs的造全局。 - 事件时限:阻塞等待可以超过每事件墙钟时限(默认 30 秒),但醒来后下一次调用宿主即失败;需要更久的等待与转发循环,在应用目录放
执行配置.json(至多 15 分钟)。
本地 workerd 实测(Wrangler 4.129.0,兼容日期 2026-09-10,同一个持久对象实例上的多个并发 durable-fetch,真实 HTTP 与真实 D1)
- 跨请求订阅与发布、三订阅者按序各得 100 条、超时精度(300 毫秒得 290 至 700 毫秒内,探测不改变长短)、关闭先交后终、64 KiB 消息与零长消息(空文字)均正确;订阅上限 256 与积压断开的原因文字正确;一个订阅内收发 3000 条不耗尽句柄;先订阅再回放的窗口去重在真实 D1 上恰好各一次。
- 客户端断开之后持久对象里的事件继续运行:订阅事件在客户端走后仍被频道消息唤醒,写入前两次仍返回 0,第三次返回 1,随后应用取消订阅(订阅数归 0)。等待期间(无写入)断开不会唤醒任何东西,只有消息或超时到来才会再次运行——所以长连接转发要靠心跳与周期超时。
- 订阅不随事件结束回收:
订阅不取消试验路由订阅后直接返回,订阅数仍为 1;一个专属实例里 64 个这样的频道之后,再订阅新频道得到 HTTP 500(宿主拒绝:云工广播频道达到上限)。这能把实例的 64 个频道名额永久耗尽(直到对象被驱逐),事件被 30 秒时限或平台取消提前终止时同样发生。 - 云端(Cloudflare 边缘)行为待验证。
宿主改动请求(写给宿主壳维护者;本适配不改宿主 JS)
- 事件结束时收尾流写器与读取器:详见[网页事件流适配说明](../网页事件流/说明.汉语.md)“宿主改动请求”的
收尾流补丁;对读取器一律cancel(),订阅即被回收,本适配的订阅登记(读取器句柄)正好落在补丁扫描范围内。补丁宿主上持久频道的一致性验证全部通过,“事件结束时应自动回收订阅”那条测试由待办转为通过。 - (可选,更稳)新增两个原语,让上限成为可捕获的异常并免去探测帧:
豫言_云工_广播新流安全(名) → [爻, 流号或错误文](把订阅数与频道数超限转成返回值)与豫言_云工_广播订阅数(名) → 整数(读取广播频道里该频道现有读者数,不产生任何帧)。适配届时可改用它们,接口不变。
一致性验证
一致性验证/ 含试验应用 持久频道一致性应用(真实 Wasm)、Node 测试 持久频道一致性.test.mjs(同一宿主对象上并发多个 durable-fetch 模拟同一个持久对象的多个事件:跨事件订阅与发布、多订阅者、多发布者保序、超时与探测、零超时、关闭、取消、无效与他事订阅柄、256 与 64 上限、积压断开、上万条消息、文字规则、频道名规则、非持久对象事件、实例隔离、先订阅后回放去重范例)、组合范例 服务商连接缩影应用(用两个包复现 AI 服务商连接持久对象:/connect 事件流带心跳、/dispatch 与 /chunk 是另外的事件、新连接替换旧连接)及其测试,以及真实 workerd 复验驱动。复跑方法见该目录说明。