#TypeScript#DevOps

Web Locks 跨标签页 Leader:所有权代际、迟到结果围栏与降级路径

Web Locks 能让同源标签依次进入临界区,却不会自动给服务器写入提供 fencing token。本文把浏览器选主、持久 generation 和副作用提交分层,阻止旧标签迟到覆盖。

本文要点

  • 主题聚焦:TypeScript、DevOps
  • 适合人群:独立开发者、全栈工程师、AI 应用开发者
  • 核心价值:快速理解背景、实践路径、常见误区和工程建议
作者 @admin·发布于 2025年12月29日·更新于 2026年7月20日

多个标签页同时刷新 session、排空离线 outbox 或维护一条实时订阅时,localStorage 心跳很容易选出两个 leader。Web Locks API 可以在同一浏览器存储分区的同源上下文之间排队授予命名锁;锁在 navigator.locks.request() 的 callback promise 结束后释放。但它既不协调另一台设备,也不产生服务器认识的 fencing token。Web Worker 并发基础区分 Worker 类型,本篇只讨论页面之间的所有权协议。

锁授予和业务所有权是两层事实

发布 crossTabLeader.v1,固定 origin/storage-key、workstream、锁名、协议版本、能力、最大持有时间、是否允许 ifAvailable/steal、服务端或本地 canonical ledger 及 fallback。每个页面启动时生成仅存于本次 document 的 tabInstanceId,不能复用 sessionStorage 中上一页面的 owner 身份。

yaml
schema: crossTabLeader.v1
workstream: account-outbox
lockName: app:leader:account-outbox:v3
protocol: leader-worker@3
canonicalFence: server-lease-ledger
maxHold: 30s
steal: incident-only
fallback: SERVER_COORDINATED

状态为 FOLLOWER -> REQUESTING -> LEADER -> DRAINING -> RELEASED,等待被取消进入 ABORTED。获得 exclusive Web Lock 只允许页面申请 canonical claim;ledger 原子递增 leaderGeneration,绑定 account、workstream、tab instance、lease 和 payload protocol。真正的任务领取、服务端写入与结果提交都必须携带该 generation,并由接收端比较当前值。乐观锁与悲观锁提供 CAS 思路,Web Lock 本身不能替代接收端条件写。

只涉及本机数据时,generation 可放在一个短小的 IndexedDB 元事务中,所有提交事务重验;跨设备或产生服务端副作用时,服务端 ledger 才是 authority。IndexedDB 离线草稿恢复处理 schema、outbox 与数据库迁移,这里不重复其存储流程,只要求它消费当前 leader fence。

callback 活着不代表结果仍有资格提交

LockOptions.signal 只取消尚在 REQUESTING 的等待;锁一旦授予,该 signal 不再终止持锁 callback。页面进入 LEADER 后应创建独立的应用 AbortController,让 callback 内部停止领取、排水并尽快 settle promise 释放锁。页面隐藏或即将冻结时可触发它,但冻结可能让 handler 根本来不及运行,所以这只是减载,不是正确性围栏。BroadcastChannel 只用于通知,navigator.locks.query() 也只能诊断。

强制 steal 或运行环境终止可能让新 owner 获得锁,而旧 callback 与网络请求仍继续执行。新 leader 取得更高 generation 后,旧请求可以完成计算,却只能得到 FENCED,不能提交 cursor、删除新任务或确认外部效果。外部提供方已接受而响应未知的工作进入按 operation ID 对账,不能因 owner 被替换就换键重发。HTTP 幂等键正确性封闭网络副作用,leader fence 只决定谁能更新 canonical 状态。

不支持 Web Locks 的浏览器不能退回 localStorage 先读后写的自制互斥。可降级为每个标签只做只读订阅,把排空交给服务端队列,或让用户显式选择单页面模式。后台任务队列比一个无法证明唯一性的客户端 leader 更适合关键写任务。

制造两个“看起来仍是 Leader”的页面

故障套件同时启动 20 个标签,随机终止等待者和 holder,冻结当前 leader、延迟 heartbeat、在 fetch 返回前 steal、让旧 tab 在 BFCache 恢复、清除本地 ledger、断开服务器后再恢复,并从另一设备提交同一 workstream。断言同一 ledger 时刻最多一个 active generation,旧代 canonical commit 为 0,相同 operation 的真实副作用最多一次,锁服务不可用时关键写 fail-open 为 0。BFCache 生命周期恢复进一步处理恢复页面的旧异步结果。

发布门要求十万轮随机选主中双 active generation 为 0、旧 fence 写入成功为 0、leader 切换 p99 小于 2 秒、每次切换未对账 operation 都有 owner、后台 holder 超过 SLO 的比例低于 0.1%。先只记录本应成为 leader 的页面,再启用只读刷新,最后开放幂等 outbox。异常时关闭客户端领取并提升 server workstream epoch,由服务端接管;旧版本页面继续受到 server fence,不能因前端回滚重新获得写权。

延伸阅读

Related

相关推荐