Является ли localStorage потоком безопасным? - программирование
Подтвердить что ты не робот

Является ли localStorage потоком безопасным?

Мне любопытно возможность повредить запись 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;
}

В приведенной выше функции подразумевается менеджер локального хранилища, который управляет одним конкретным ключом локального хранилища - localStorage["test"], например. Я хочу использовать это для greysomonkey userscripts, где избегать конфликтов является приоритетом.

4b9b3361

Ответ 1

Да, это поточно-безопасный. Однако ваш код не является атомарным, и это ваша проблема. Я доберусь до уровня безопасности localStorage, но сначала, как исправить вашу проблему.

Обе вкладки могут проходить проверку if вместе и записывать в элемент, переписывающий друг друга. Правильный способ решения этой проблемы - использовать StorageEvent s.

Они позволяют вам уведомлять другие окна, когда ключ изменился в localStorage, эффективно решая проблему для вас во встроенном сообщении, передающем безопасный путь. Вот о них приятно читать. Приведем пример:

// tab 1
localStorage.setItem("Foo","Bar");

// tab 2
window.addEventListener("storage",function(e){
    alert("StorageChanged!"); // this will run when the localStorage is changed
});

Теперь, что я обещал о безопасности потоков:)

Как мне нравится - давайте наблюдать это с двух сторон - из спецификации и использования реализации.

Спецификация

Покажите это потокобезопасным по спецификации.

Если мы проверим спецификацию Web Storage, мы увидим, что она особенно примечания:

Из-за использования мьютекса хранения несколько контекстов просмотра смогут одновременно обращаться к локальным областям хранения таким образом, что скрипты не могут обнаружить какое-либо одновременное выполнение script.

Таким образом, атрибут length объекта Storage и значение различных свойств этого объекта не могут измениться, пока выполняется script, отличным от того, что предсказуемо самой script.

Он еще более подробно описывает:

Всякий раз, когда свойства объекта localStorage атрибут 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;
}

Далее, это происходит в 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);
}

Обратите внимание, что 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;
}

Они также дошли до замечательной заметки:

// FIXME: In the future, we should allow use of StorageAreas while it 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 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.

Объяснение этого работает, но оно может быть более эффективным.