从多个选项卡同步写入 localStorage 时出现不一致

Spa*_*cus 6 javascript firefox local-storage

tl;dr我注意到在完全相同的时间写入 localStorage 时浏览器之间的行为不一致。

要求:即使打开多个选项卡,特定操作(刷新 OAuth 会话的 POST 请求)也应仅执行一次。哪个选项卡执行操作并不重要。进行刷新的时间点源自会话的到期时间,并且在所有选项卡中都完全相同。

方法:所有选项卡生成一个随机数,将其存储并写入 localStorage。然后他们读取 localStorage,如果两者相同,则允许选项卡执行操作。

let tab = Math.random();
localStorage.setItem('tab',tab);
if(JSON.parse(localStorage.getItem('tab')) === tab) {
    console.log('aquired lock');
} else {
    console.log('did not aquire lock');
}
Run Code Online (Sandbox Code Playgroud)

JSFiddle -为了测试行为,您需要在两个选项卡中打开 fiddle,然后在两个选项卡中按 Run。超时被计算为在下一个完整的 10 秒执行。(第二个 0, 10, 20, 30, 40, 50)

期望:选项卡 A 和 B 将 localStorage['tab'] 设置为随机值,在检索该值时,只有一个选项卡检索与其随机生成的值相同的值,因此允许执行该操作。

结果: A 和 B 仍然检索自己生成的值。

我添加了一些超时让内存尘埃落定

let tab = Math.random();
localStorage.setItem('tab',tab);
setTimeout(function(){
    if(JSON.parse(localStorage.getItem('tab')) === tab) {
        console.log('aquired lock');
    } else {
        console.log('did not aquire lock');
    }
}, 1000);
Run Code Online (Sandbox Code Playgroud)

JSFiddle

结果 (Firefox):选项卡 A 检索选项卡 B 生成的值,反之亦然。因此不允许选项卡执行该操作。

这是我有点害怕的地方。我检查了控制台时间戳,它们完全相同,以及开发工具中的 localStorage,它在不同的选项卡中显示了不同的值。(即使重新加载选项卡也确实在不同的选项卡中显示了不同的值。)

如果稍后写入值(例如通过控制台),所有选项卡都会相应地更新值。

结果(Chrome、Edge):只有一个选项卡aquired lock按预期记录。


有没有解释为什么 Firefox 可以在每个选项卡的 localStorage 中有不同的值?


我已经通过订阅StorageEvent解决了这个问题。具有最小随机数的选项卡将执行操作。

使用的浏览器:

  • Firefox 68.0 和 60.8.0esr(均为 64 位)
  • Chrome 75.0.3770.142(64 位)
  • 边缘 44.18362.1.0

T.J*_*der 5

这是处理共享内存时相当标准的情况。出于性能原因,允许每个访问共享内存的线程保留自己的副本(“缓存”),直到/除非发生某些同步,此时本地副本必须与共享副本协调。

旧的存储规范谈到在每个存储操作上获取存储互斥锁:

每当要检查、返回、设置或删除localStorage属性Storage对象的属性时,无论是作为直接属性访问的一部分、检查属性是否存在、在属性枚举期间、确定存在的属性数量时,或作为Storage接口上定义的任何方法或属性的执行的一部分,用户代理必须首先获得存储互斥锁。

但该规范已被归入 WHAT-WG“HTML”规范(比 HTML 多得多)的第 11 节(“Web 存储”)中,并且取消了每个操作都必须获取存储互斥锁的要求。(我不知道为什么,但我猜是出于性能原因。)当前的规范

警告:localStorage属性提供对共享状态的访问。本规范没有定义多进程用户代理中与其他浏览上下文的交互,鼓励作者假设没有锁定机制。

该规范也没有讨论跨浏览上下文的存储同步。这意味着实现可以自由优化。

使用修改后的脚本版本查看它,看起来 Firefox 通过为每个浏览上下文(选项卡)创建本地存储的本地副本进行优化,它似乎根据storage来自其他上下文(选项卡)的事件进行更新。但是,如果两个选项卡在处理来自另一个选项卡storagestorage事件之前都设置了值(为另一个选项卡生成事件),则它们都从另一个选项卡获取并处理storage事件,并使用该值(另一个选项卡的值)进行更新,导致你描述的行为。

旁注:写入持久存储的操作(如果您在完成所有这些操作后打开它,第三个选项卡会看到什么)也似乎是异步的,并且两个选项卡正在竞争查看哪个最后写入(竞争是最后一个写入其本地副本并不总是获胜!)。

当线程之间只有松散的同步并且没有锁定语义时,这实际上是线程之间共享内存发生的情况的大规模版本,规范不再需要。

Chrome 似乎正在执行锁定或类似操作。