В методе acquireUrlLock() присутствует race condition (состояние гонки):
Сценарий deadlock:
- Вкладка A читает URL со статусом
pending - Вкладка B читает тот же URL со статусом
pending(до того, как A успела записать) - Обе вкладки устанавливают статус
processingи свойtabId - Последняя запись побеждает, но обе вкладки считают, что захватили блокировку
Используем версионность с оптимистичной блокировкой (как в updateHeartbeat):
-
Оптимистичная блокировка через версионность:
- Каждая запись имеет поле
version - При изменении версия инкрементируется
- После записи проверяется, что версия соответствует ожидаемой
- Каждая запись имеет поле
-
Атомарность на уровне транзакции:
- IndexedDB гарантирует, что
put()атомарен - Если две вкладки попытаются записать одновременно, одна из них перезапишет другую
- Проверка версии после записи выявляет конфликт
- IndexedDB гарантирует, что
-
Retry механизм:
- При конфликте делается до 3 попыток с экспоненциальной задержкой
- Это увеличивает шансы на успешный захват при высокой конкуренции
Оптимистичная блокировка через версионность - это лучший компромисс для IndexedDB, так как:
- ✅ Работает во всех браузерах
- ✅ Не блокирует UI
- ✅ Простая реализация
- ✅ Хорошая производительность
Проблема в том, что библиотека sabberworm/php-css-parser версии 9.x изменила возвращаемый тип методов селекторов. В версии 9.x метод getSelectors() возвращает массив объектов Selector, которые нельзя напрямую привести к строке через (string).
В версии 8.x: объект Selector можно было привести к строке через (string)$selectorObject
В версии 9.x: нужно явно вызывать метод getSelector(), который возвращает строковое представление селектора.
Update добавляет проверку наличия метода getSelector() и использует его, обеспечивая обратную совместимость с версией 8.x.
Метод updateHeartbeat() в классе TabSyncManager не защищен от параллельной записи. При мультивкладочной работе несколько вкладок могут одновременно читать и записывать данные в TAB_STORE_NAME, что приводит к перезаписи данных других вкладок.
-
Версионность записей: Каждая запись в
TAB_STORE_NAMEтеперь содержит полеversion, которое инкрементируется при каждом обновлении. -
Оптимистичная блокировка:
- Читаем текущую версию
- Инкрементируем версию
- Записываем с новой версией
- Проверяем, что версия не изменилась после записи
-
Retry механизм: При обнаружении конфликта версии делаем до 3 попыток с экспоненциальной задержкой (50ms, 100ms, 200ms).
-
Безопасная очистка: Метод
cleanupDeadTabs()также проверяет версию перед удалением, чтобы не удалить запись, которая была только что обновлена другой вкладкой.
- ✅ Нет блокировок - работает быстро
- ✅ Обнаруживает конфликты записи
- ✅ Автоматически повторяет попытку при конфликте
- ✅ Не требует изменений в схеме IndexedDB (добавляется только поле
version) - ✅ Совместимо с существующим кодом