Tom*_*ica 21 javascript multithreading local-storage
我很好奇localStorage通过在两个浏览器标签中同时覆盖它来破坏输入的可能性.我应该为本地存储创建互斥吗?
我已经在考虑这样的伪类:
LocalStorageMan.prototype.v = LocalStorageMan.prototype.value = function(name, val) {
//Set inner value
this.data[name] = val;
//Delay any changes if the local storage is being changed
if(localStorage[this.name+"__mutex"]==1) {
setTimeout(function() {this.v(name, val);}, 1);
return null; //Very good point @Lightness Races in Orbit
}
//Lock the mutext to prevent overwriting
localStorage[this.name+"__mutex"] = 1;
//Save serialized data
localStorage[this.name] = this.serializeData;
//Allow usage from another tabs
localStorage[this.name+"__mutex"] = 0;
}
Run Code Online (Sandbox Code Playgroud)
上述功能意味着本地存储管理器正在管理本地存储的一个特定密钥 - localStorage["test"]例如.我想将此用于greasomonkey用户脚本,其中避免冲突是一个优先事项.
Ben*_*aum 31
是的,它是线程安全的.但是,你的代码不是原子的,那就是你的问题.我会localStorage先解决安全问题,但首先是如何解决问题.
两个选项卡都可以将if检查一起传递并写入相互覆盖的项目.处理这个问题的正确方法是使用StorageEvents.
这些允许您在localStorage中更改密钥时通知其他窗口,在内置消息中以安全的方式有效地解决问题.这是一个关于他们的好读物.我们举个例子:
// tab 1
localStorage.setItem("Foo","Bar");
// tab 2
window.addEventListener("storage",function(e){
alert("StorageChanged!"); // this will run when the localStorage is changed
});
Run Code Online (Sandbox Code Playgroud)
现在,我承诺的线程安全:)
我喜欢 - 让我们从两个角度来看 - 从规范和使用实现.
让我们通过规范显示它的线程安全.
由于使用了存储互斥锁,多个浏览上下文将能够以脚本无法检测到任何并发脚本执行的方式同时访问本地存储区域.
因此,存储对象的length属性以及该对象的各种属性的值在脚本执行时不能更改,而不是以脚本本身可预测的方式.
它甚至进一步阐述:
每当要检查,返回,设置或删除
localStorage属性Storage对象的属性时,无论是作为直接属性访问的一部分,还是在检查属性的存在时,在属性枚举期间,在确定存在的属性数时,或者作为执行Storage接口上定义的任何方法或属性的一部分,用户代理必须首先获取存储互斥锁.
强调我的.它还指出,一些实现者并不喜欢这样的说明.
让我们在实现中展示它的线程安全性.
选择一个随机浏览器,我选择了WebKit(因为我之前不知道那个代码在哪里).如果我们查看WebKit的Storage实现,我们可以看到它有互斥量的票价.
让我们从一开始就采取它.当你打电话setItem或分配时,会发生这种情况:
void Storage::setItem(const String& key, const String& value, ExceptionCode& ec)
{
if (!m_storageArea->canAccessStorage(m_frame)) {
ec = SECURITY_ERR;
return;
}
if (isDisabledByPrivateBrowsing()) {
ec = QUOTA_EXCEEDED_ERR;
return;
}
bool quotaException = false;
m_storageArea->setItem(m_frame, key, value, quotaException);
if (quotaException)
ec = QUOTA_EXCEEDED_ERR;
}
Run Code Online (Sandbox Code Playgroud)
接下来,发生在StorageArea:
void StorageAreaImpl::setItem(Frame* sourceFrame, const String& key, const String& value, bool& quotaException)
{
ASSERT(!m_isShutdown);
ASSERT(!value.isNull());
blockUntilImportComplete();
String oldValue;
RefPtr<StorageMap> newMap = m_storageMap->setItem(key, value, oldValue, quotaException);
if (newMap)
m_storageMap = newMap.release();
if (quotaException)
return;
if (oldValue == value)
return;
if (m_storageAreaSync)
m_storageAreaSync->scheduleItemForSync(key, value);
dispatchStorageEvent(key, oldValue, value, sourceFrame);
}
Run Code Online (Sandbox Code Playgroud)
请注意blockUntilImportComplete这里.我们来看看:
void StorageAreaSync::blockUntilImportComplete()
{
ASSERT(isMainThread());
// Fast path. We set m_storageArea to 0 only after m_importComplete being true.
if (!m_storageArea)
return;
MutexLocker locker(m_importLock);
while (!m_importComplete)
m_importCondition.wait(m_importLock);
m_storageArea = 0;
}
Run Code Online (Sandbox Code Playgroud)
他们还添加了一个很好的说明:
// FIXME: In the future, we should allow use of StorageAreas while it's importing (when safe to do so).
// Blocking everything until the import is complete is by far the simplest and safest thing to do, but
// there is certainly room for safe optimization: Key/length will never be able to make use of such an
// optimization (since the order of iteration can change as items are being added). Get can return any
// item currently in the map. Get/remove can work whether or not it's in the map, but we'll need a list
// of items the import should not overwrite. Clear can also work, but it'll need to kill the import
// job first.
Run Code Online (Sandbox Code Playgroud)
解释这个工作,但它可以更有效.