Main thread-proxy localStorage oprations in native WASM QSettings
localStorage is unavailable on workers. Operations will now get proxied to the main thread instead. This - among other benefits - makes tst_QSettings::testThreadSafety pass. Task-number: QTBUG-115509 Change-Id: Iebbe5e9f9069948f8728e0a82628cc082b30de12 Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
This commit is contained in:
parent
39a5ed4bdd
commit
5c5c9dd830
@ -18,7 +18,9 @@
|
|||||||
#include <QSet>
|
#include <QSet>
|
||||||
|
|
||||||
#include <emscripten.h>
|
#include <emscripten.h>
|
||||||
#include <emscripten/val.h>
|
# include <emscripten/proxying.h>
|
||||||
|
# include <emscripten/threading.h>
|
||||||
|
# include <emscripten/val.h>
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
@ -58,7 +60,6 @@ public:
|
|||||||
QString fileName() const final;
|
QString fileName() const final;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
val m_localStorage = val::global("window")["localStorage"];
|
|
||||||
QStringList m_keyPrefixes;
|
QStringList m_keyPrefixes;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -108,89 +109,106 @@ void QWasmLocalStorageSettingsPrivate::remove(const QString &key)
|
|||||||
{
|
{
|
||||||
const std::string removed = QString(m_keyPrefixes.first() + key).toStdString();
|
const std::string removed = QString(m_keyPrefixes.first() + key).toStdString();
|
||||||
|
|
||||||
std::vector<std::string> children = { removed };
|
qstdweb::runTaskOnMainThread<void>([this, &removed, &key]() {
|
||||||
const int length = m_localStorage["length"].as<int>();
|
std::vector<std::string> children = { removed };
|
||||||
for (int i = 0; i < length; ++i) {
|
const int length = val::global("window")["localStorage"]["length"].as<int>();
|
||||||
const QString storedKeyWithPrefix =
|
for (int i = 0; i < length; ++i) {
|
||||||
QString::fromStdString(m_localStorage.call<val>("key", i).as<std::string>());
|
const QString storedKeyWithPrefix = QString::fromStdString(
|
||||||
|
val::global("window")["localStorage"].call<val>("key", i).as<std::string>());
|
||||||
|
|
||||||
const QStringView storedKey = keyNameFromPrefixedStorageName(
|
const QStringView storedKey = keyNameFromPrefixedStorageName(
|
||||||
m_keyPrefixes.first(), QStringView(storedKeyWithPrefix));
|
m_keyPrefixes.first(), QStringView(storedKeyWithPrefix));
|
||||||
if (storedKey.isEmpty() || !storedKey.startsWith(key))
|
if (storedKey.isEmpty() || !storedKey.startsWith(key))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
children.push_back(storedKeyWithPrefix.toStdString());
|
children.push_back(storedKeyWithPrefix.toStdString());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto &child : children)
|
for (const auto &child : children)
|
||||||
m_localStorage.call<val>("removeItem", child);
|
val::global("window")["localStorage"].call<val>("removeItem", child);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void QWasmLocalStorageSettingsPrivate::set(const QString &key, const QVariant &value)
|
void QWasmLocalStorageSettingsPrivate::set(const QString &key, const QVariant &value)
|
||||||
{
|
{
|
||||||
const std::string keyString = QString(m_keyPrefixes.first() + key).toStdString();
|
qstdweb::runTaskOnMainThread<void>([this, &key, &value]() {
|
||||||
const std::string valueString = QSettingsPrivate::variantToString(value).toStdString();
|
const std::string keyString = QString(m_keyPrefixes.first() + key).toStdString();
|
||||||
m_localStorage.call<void>("setItem", keyString, valueString);
|
const std::string valueString = QSettingsPrivate::variantToString(value).toStdString();
|
||||||
|
val::global("window")["localStorage"].call<void>("setItem", keyString, valueString);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<QVariant> QWasmLocalStorageSettingsPrivate::get(const QString &key) const
|
std::optional<QVariant> QWasmLocalStorageSettingsPrivate::get(const QString &key) const
|
||||||
{
|
{
|
||||||
for (const auto &prefix : m_keyPrefixes) {
|
return qstdweb::runTaskOnMainThread<std::optional<QVariant>>(
|
||||||
const std::string keyString = QString(prefix + key).toStdString();
|
[this, &key]() -> std::optional<QVariant> {
|
||||||
const emscripten::val value = m_localStorage.call<val>("getItem", keyString);
|
for (const auto &prefix : m_keyPrefixes) {
|
||||||
if (!value.isNull())
|
const std::string keyString = QString(prefix + key).toStdString();
|
||||||
return QSettingsPrivate::stringToVariant(
|
const emscripten::val value =
|
||||||
QString::fromStdString(value.as<std::string>()));
|
val::global("window")["localStorage"].call<val>("getItem", keyString);
|
||||||
if (!fallbacks)
|
if (!value.isNull()) {
|
||||||
return std::nullopt;
|
return QSettingsPrivate::stringToVariant(
|
||||||
}
|
QString::fromStdString(value.as<std::string>()));
|
||||||
return std::nullopt;
|
}
|
||||||
|
if (!fallbacks) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList QWasmLocalStorageSettingsPrivate::children(const QString &prefix, ChildSpec spec) const
|
QStringList QWasmLocalStorageSettingsPrivate::children(const QString &prefix, ChildSpec spec) const
|
||||||
{
|
{
|
||||||
QSet<QString> nodes;
|
return qstdweb::runTaskOnMainThread<QStringList>([this, &prefix, &spec]() -> QStringList {
|
||||||
// Loop through all keys on window.localStorage, return Qt keys belonging to
|
QSet<QString> nodes;
|
||||||
// this application, with the correct prefix, and according to ChildSpec.
|
// Loop through all keys on window.localStorage, return Qt keys belonging to
|
||||||
QStringList children;
|
// this application, with the correct prefix, and according to ChildSpec.
|
||||||
const int length = m_localStorage["length"].as<int>();
|
QStringList children;
|
||||||
for (int i = 0; i < length; ++i) {
|
const int length = val::global("window")["localStorage"]["length"].as<int>();
|
||||||
for (const auto &storagePrefix : m_keyPrefixes) {
|
for (int i = 0; i < length; ++i) {
|
||||||
const QString keyString =
|
for (const auto &storagePrefix : m_keyPrefixes) {
|
||||||
QString::fromStdString(m_localStorage.call<val>("key", i).as<std::string>());
|
const QString keyString =
|
||||||
|
QString::fromStdString(val::global("window")["localStorage"]
|
||||||
|
.call<val>("key", i)
|
||||||
|
.as<std::string>());
|
||||||
|
|
||||||
const QStringView key =
|
const QStringView key =
|
||||||
keyNameFromPrefixedStorageName(storagePrefix, QStringView(keyString));
|
keyNameFromPrefixedStorageName(storagePrefix, QStringView(keyString));
|
||||||
if (!key.isEmpty() && key.startsWith(prefix)) {
|
if (!key.isEmpty() && key.startsWith(prefix)) {
|
||||||
QStringList children;
|
QStringList children;
|
||||||
QSettingsPrivate::processChild(key.sliced(prefix.length()), spec, children);
|
QSettingsPrivate::processChild(key.sliced(prefix.length()), spec, children);
|
||||||
if (!children.isEmpty())
|
if (!children.isEmpty())
|
||||||
nodes.insert(children.first());
|
nodes.insert(children.first());
|
||||||
|
}
|
||||||
|
if (!fallbacks)
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (!fallbacks)
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return QStringList(nodes.begin(), nodes.end());
|
return QStringList(nodes.begin(), nodes.end());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void QWasmLocalStorageSettingsPrivate::clear()
|
void QWasmLocalStorageSettingsPrivate::clear()
|
||||||
{
|
{
|
||||||
// Get all Qt keys from window.localStorage
|
qstdweb::runTaskOnMainThread<void>([this]() {
|
||||||
const int length = m_localStorage["length"].as<int>();
|
// Get all Qt keys from window.localStorage
|
||||||
QStringList keys;
|
const int length = val::global("window")["localStorage"]["length"].as<int>();
|
||||||
keys.reserve(length);
|
QStringList keys;
|
||||||
for (int i = 0; i < length; ++i)
|
keys.reserve(length);
|
||||||
keys.append(QString::fromStdString((m_localStorage.call<val>("key", i).as<std::string>())));
|
for (int i = 0; i < length; ++i)
|
||||||
|
keys.append(QString::fromStdString(
|
||||||
|
(val::global("window")["localStorage"].call<val>("key", i).as<std::string>())));
|
||||||
|
|
||||||
// Remove all Qt keys. Note that localStorage does not guarantee a stable
|
// Remove all Qt keys. Note that localStorage does not guarantee a stable
|
||||||
// iteration order when the storage is mutated, which is why removal is done
|
// iteration order when the storage is mutated, which is why removal is done
|
||||||
// in a second step after getting all keys.
|
// in a second step after getting all keys.
|
||||||
for (const QString &key : keys) {
|
for (const QString &key : keys) {
|
||||||
if (!keyNameFromPrefixedStorageName(m_keyPrefixes.first(), key).isEmpty())
|
if (!keyNameFromPrefixedStorageName(m_keyPrefixes.first(), key).isEmpty())
|
||||||
m_localStorage.call<val>("removeItem", key.toStdString());
|
val::global("window")["localStorage"].call<val>("removeItem", key.toStdString());
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void QWasmLocalStorageSettingsPrivate::sync() { }
|
void QWasmLocalStorageSettingsPrivate::sync() { }
|
||||||
@ -361,9 +379,12 @@ QSettingsPrivate *QSettingsPrivate::create(QSettings::Format format, QSettings::
|
|||||||
format = QSettings::WebLocalStorageFormat;
|
format = QSettings::WebLocalStorageFormat;
|
||||||
|
|
||||||
// Check if cookies are enabled (required for using persistent storage)
|
// Check if cookies are enabled (required for using persistent storage)
|
||||||
const bool cookiesEnabled = val::global("navigator")["cookieEnabled"].as<bool>();
|
|
||||||
constexpr QLatin1StringView cookiesWarningMessage
|
const bool cookiesEnabled = qstdweb::runTaskOnMainThread<bool>(
|
||||||
("QSettings::%1 requires cookies, falling back to IniFormat with temporary file");
|
[]() { return val::global("navigator")["cookieEnabled"].as<bool>(); });
|
||||||
|
|
||||||
|
constexpr QLatin1StringView cookiesWarningMessage(
|
||||||
|
"QSettings::%1 requires cookies, falling back to IniFormat with temporary file");
|
||||||
if (!cookiesEnabled) {
|
if (!cookiesEnabled) {
|
||||||
if (format == QSettings::WebLocalStorageFormat) {
|
if (format == QSettings::WebLocalStorageFormat) {
|
||||||
qWarning() << cookiesWarningMessage.arg("WebLocalStorageFormat");
|
qWarning() << cookiesWarningMessage.arg("WebLocalStorageFormat");
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
#include <QtCore/QCoreApplication>
|
#include <QtCore/QCoreApplication>
|
||||||
#include <QtCore/QDir>
|
#include <QtCore/QDir>
|
||||||
|
#include <QtCore/QEventLoop>
|
||||||
#include <QtCore/QtGlobal>
|
#include <QtCore/QtGlobal>
|
||||||
#include <QtCore/QThread>
|
#include <QtCore/QThread>
|
||||||
#include <QtCore/QSysInfo>
|
#include <QtCore/QSysInfo>
|
||||||
@ -44,6 +45,7 @@
|
|||||||
#if defined(Q_OS_WASM)
|
#if defined(Q_OS_WASM)
|
||||||
#include <QtCore/private/qstdweb_p.h>
|
#include <QtCore/private/qstdweb_p.h>
|
||||||
|
|
||||||
|
#include "emscripten/threading.h"
|
||||||
#include "emscripten/val.h"
|
#include "emscripten/val.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -2136,6 +2138,12 @@ void tst_QSettings::testThreadSafety()
|
|||||||
#if !QT_CONFIG(thread)
|
#if !QT_CONFIG(thread)
|
||||||
QSKIP("This test requires threads to be enabled.");
|
QSKIP("This test requires threads to be enabled.");
|
||||||
#endif // !QT_CONFIG(thread)
|
#endif // !QT_CONFIG(thread)
|
||||||
|
#if defined(Q_OS_WASM)
|
||||||
|
if (!qstdweb::haveJspi())
|
||||||
|
QSKIP("Test needs jspi on WASM. Calls are proxied to the main thread from SettingsThreads, "
|
||||||
|
"which necessitates the use of an event loop to yield to the main loop. Event loops "
|
||||||
|
"require jspi.");
|
||||||
|
#endif
|
||||||
|
|
||||||
SettingsThread threads[NumThreads];
|
SettingsThread threads[NumThreads];
|
||||||
int i, j;
|
int i, j;
|
||||||
@ -2144,6 +2152,19 @@ void tst_QSettings::testThreadSafety()
|
|||||||
|
|
||||||
for (i = 0; i < NumThreads; ++i)
|
for (i = 0; i < NumThreads; ++i)
|
||||||
threads[i].start(i + 1);
|
threads[i].start(i + 1);
|
||||||
|
|
||||||
|
#if defined(Q_OS_WASM) && QT_CONFIG(thread)
|
||||||
|
QEventLoop loop;
|
||||||
|
int remaining = NumThreads;
|
||||||
|
for (int i = 0; i < NumThreads; ++i) {
|
||||||
|
QObject::connect(&threads[i], &QThread::finished, this, [&remaining, &loop]() {
|
||||||
|
if (!--remaining)
|
||||||
|
loop.quit();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
loop.exec();
|
||||||
|
#endif // defined(Q_OS_WASM) && QT_CONFIG(thread)
|
||||||
|
|
||||||
for (i = 0; i < NumThreads; ++i)
|
for (i = 0; i < NumThreads; ++i)
|
||||||
threads[i].wait();
|
threads[i].wait();
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user