Web Locks API:安全协调共享浏览器工作
使用 Web Locks API 协调标签页、窗口和 Worker,同时区分浏览器锁与分布式服务器锁。
BotBrowser Team
多个标签页或 Worker 更新同一浏览器资源时,“先检查再写入”并不等于协调:两个上下文可能同时通过检查。Web Locks API 会为命名锁排队,让同源上下文一次只有一个临界区持有者。它不协调服务器或不同源,也不会自动修复被关闭的应用状态。BotBrowser 可重复授权的 profile 流程,但不会授予锁、控制其他标签页或替代应用与服务器正确性。
TL;DR
- 将锁包在最小共享操作外,回调返回时释放。
- 不适合等待时使用
ifAvailable或应用层截止时间;未获得锁不能当作已获得。 - Worker、标签页或窗口可能消失。让操作幂等,并把恢复逻辑放在回调之外。
- Web Locks 只协调同源浏览器上下文,不协调跨源页面、任意服务器进程或隐藏标签页。
- BotBrowser 可帮助重复授权的 profile 流程,但锁语义、存储正确性和取消仍由页面负责。
Contents
浏览器锁模型
navigator.locks.request(name, options, callback) 为命名锁排队。默认是独占模式;共享模式可允许多个读取者,同时排除独占写入者。浏览器授予锁后才运行回调,直到回调返回或 promise settle 才释放。另一个同源上下文会在浏览器队列中等待,而不是轮询存储标志。
队列不是后端全局互斥锁,不跨源、profile、设备或服务器实例。它也不承诺超出 API 文档的公平性。临界区应很短:读取记录、应用一次校验过的变更、写入并返回。网络请求和界面等待通常放在锁外;若决策必须在浏览器关闭后仍成立,应使用租约或服务器事务。
一个小型协调 fixture
下面的 fixture 递增本地计数,并报告是否取得锁。ifAvailable 分支明确处理:其他上下文持有锁时,lock 为 null。
async function incrementCounter(storage, report) {
if (!navigator.locks) return report('unsupported');
return navigator.locks.request('counter-write', { ifAvailable: true }, async lock => {
if (!lock) return report('busy');
const current = Number(storage.getItem('counter') || 0);
storage.setItem('counter', String(current + 1));
report('committed');
});
}
预期结果是:两个上下文并发调用时不会同时进入临界区;忙碌上下文报告 busy;不支持 API 的浏览器报告 unsupported。fixture 只展示本地行为。localStorage 不是事务数据库,重要数据仍应使用合适的持久化存储,并在服务器再次校验冲突。
Worker 安全与失败恢复
Worker 可以使用与窗口相同的锁名。Worker 在等待或回调中被终止时,浏览器会释放锁,但部分写入或外部副作用仍由应用负责。一次提交一个完整记录,使用幂等键,并区分“未开始”“已提交”和“中断后未知”。不要在等待网络响应时长期持锁。
用户操作不应形成无限队列。可选刷新适合 ifAvailable;必需工作应有应用截止时间,并在每个不可逆步骤前检查取消标志。finally 路径要返回回调,关闭 Worker 时也要关闭端口。
BotBrowser 支持用于兼容性审查的可重复授权浏览器 profile 流程,但不会授予 Web Lock、强制其他标签页释放锁、控制跨源调度,也不替代服务器事务。profile 运行只是测试浏览器旅程的证据。
证据与决策矩阵
使用两个同源窗口和一个 Worker,设置唯一锁名、计数器并强制终止 Worker。记录浏览器版本、profile、headed/headless、存储实现、锁结果、可见回退和最终值;不要记录账户数据或原始页面截图。
| 观察 | 安全解释 | 下一步 |
|---|---|---|
每个已接受请求出现一次 committed | 本次临界区已串行化 | 保持临界区短,并在更新后重测 |
ifAvailable 返回 busy | 当时锁不可用 | 保留旧状态,按需安排明确重试 |
出现 unsupported | 当前上下文没有 API | 使用设计好的回退或服务器事务 |
| Worker 在报告前结束 | 结果可能未知 | 先按幂等键对账再重试 |
| 串行后计数仍错误 | 存储或校验不是原子的 | 将不变量移到事务存储 |
fixture 证明的是可观察的浏览器行为,不是分布式协调保证。还要测试用户可见恢复:状态提示、保留表单、以及不会重复提交的重试。
明确资源边界与回调责任
锁名应描述受保护的资源,而不是某个界面。为不同不变量使用不同名称,避免用一个全局锁拖慢无关工作。回调应只读取、校验、写入并返回;promise settle 后锁才释放。网络请求、用户等待和动画应放在锁外。
如果回调中途抛错,释放锁并不代表外部副作用已经完成。AbortController.abort() 也不会撤销已经开始的锁回调,只会通知主动检查 signal 的代码。每个不可逆步骤前检查取消状态,并让下一次运行能识别重复提交。
处理 owner 消失与超时
标签页、窗口或 Worker 可能在等待或执行时消失。浏览器会释放锁,但应用需要用幂等键记录意图,再由新的 Worker 对账 outbox:区分未开始、已提交和未知状态后才重试。ifAvailable 返回 busy 时保留当前状态;等待必须有应用截止时间,不能用紧循环制造更多请求。
支持、回退与复查
支持判断应分为 API 存在、锁操作完成和用户结果可见三层。浏览器版本字符串不能替代运行时 fixture。测试 API 缺失、回调拒绝、存储失败和 Worker 终止,并保留可访问状态提示与键盘可达的重试。浏览器、存储或离线策略变化后,重新检查锁名、回退和证据保留规则。
结论
Web Locks 适合浏览器上下文之间短暂的同源协调。保持写入原子、让中断可恢复,并把持久不变量放进能跨越浏览器的存储。下一步应运行双窗口/Worker fixture,验证真实回退和最终记录,而不只是检查请求是否 resolve。
常见问题
Web Lock 能保护服务器行吗?
不能。它只协调该源的浏览器上下文;服务器仍需授权、冲突处理和事务或幂等规则。
超时后能强制释放锁吗?
不能。回调 settle 或所属上下文结束时浏览器才释放。应让回调观察取消,并避免跨越慢操作持锁。
ifAvailable 是失败吗?
这是有意的不等待选择。null 表示本次没有执行;保留状态,再决定是否显示重试。
BotBrowser 会让锁调度确定吗?
不会。它能让支持的 profile 流程更易重复,而调度、存储和恢复仍是页面与平台责任。