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

时间:2019-07-18 07:28:53

标签: javascript firefox local-storage

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

要求:即使打开了多个标签,特定操作(POST请求刷新OAuth会话)也应只执行一次。哪个选项卡执行操作无关紧要。刷新的时间点源自会话的过期时间,并且在所有选项卡中都是完全相同的。

方法::所有标签页都会生成一个随机数,将其存储并写入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');
}

JSFiddle-为了测试行为,您需要在两个选项卡中打开小提琴,然后在两个选项卡中均按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);

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

1 个答案:

答案 0 :(得分:3)

在处理共享内存时,这是相当标准的情况。出于性能原因,每个访问共享内存的线程都可以保留自己的副本(“缓存”),直到/除非发生一些同步为止,这时本地副本必须与共享副本保持一致。

旧的存储规范talked about在每个存储操作上获取一个存储互斥量:

  

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

但是,该规范已包含在§11(“ Web存储”)中的WHAT-WG“ HTML”规范中(比HTML大很多),并且要求每个操作都必须获取存储互斥量掉了。 (我不知道为什么,但是出于性能原因我会猜测。)当前规范says

  

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

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

使用modified version of your script进行浏览,看起来Firefox通过为每个浏览上下文(选项卡)具有本地存储的本地副本进行了优化,它似乎是根据其他storage事件而更新的上下文(标签)。但是,如果两个选项卡都在处理另一个选项卡的storage事件之前设置了值(为另一个选项卡生成storage事件),则它们都会从该选项卡获取并处理storage事件其他,使用该值(另一个标签的值)进行更新,导致您描述的行为。

侧面说明:写入持久性存储的操作(执行完所有这些操作后,第三个选项卡会显示是否打开了它)也似乎是异步的,并且这两个选项卡正在竞争最后写入哪个选项(一个最后一次写入本地副本并不总是赢的比赛!)

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

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