mirror of
https://github.com/swift-project/pilotclient.git
synced 2026-03-23 07:15:35 +08:00
refs #581 Synchronous loading of data cache value by returning a future.
CDataCacheRevision maintains a list of promises corresponding to the futures, so they can be delivered when the loading is finished. Corresponding notification slot is inhibited to avoid duplicate notification.
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
#include "blackmisc/logmessage.h"
|
||||
#include "blackmisc/identifier.h"
|
||||
#include <QStandardPaths>
|
||||
#include <utility>
|
||||
|
||||
namespace BlackMisc
|
||||
{
|
||||
@@ -83,6 +84,21 @@ namespace BlackMisc
|
||||
return enumerateFiles(persistentStore());
|
||||
}
|
||||
|
||||
std::future<CVariant> CDataCache::syncLoad(QObject *pageOwner, const QString &key)
|
||||
{
|
||||
auto future = m_revision.promiseLoadedValue(pageOwner, key);
|
||||
if (future.valid())
|
||||
{
|
||||
return future;
|
||||
}
|
||||
else // value is not currently loading, so immediately return the current value
|
||||
{
|
||||
std::promise<CVariant> p;
|
||||
p.set_value(getValueSync(key));
|
||||
return p.get_future();
|
||||
}
|
||||
}
|
||||
|
||||
QString lockFileError(const QLockFile &lock)
|
||||
{
|
||||
switch (lock.error())
|
||||
@@ -162,18 +178,47 @@ namespace BlackMisc
|
||||
{
|
||||
if (! m_deferredChanges.isEmpty())
|
||||
{
|
||||
auto promises = m_cache->m_revision.loadedValuePromises();
|
||||
for (const auto &tuple : promises)
|
||||
{
|
||||
QObject *pageOwner = nullptr;
|
||||
QString key;
|
||||
std::tie(pageOwner, key, std::ignore) = tuple;
|
||||
|
||||
m_deferredChanges.inhibit(pageOwner, key); // don't fire notification slots for objects waiting on syncLoad futures
|
||||
}
|
||||
|
||||
m_deferredChanges.setSaved();
|
||||
emit valuesLoadedFromStore(m_deferredChanges, CIdentifier::anonymous());
|
||||
deliverPromises(std::move(promises));
|
||||
m_deferredChanges.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void CDataCacheSerializer::deliverPromises(std::vector<std::tuple<QObject *, QString, std::promise<CVariant>>> i_promises)
|
||||
{
|
||||
auto changes = m_deferredChanges;
|
||||
auto promises = std::make_shared<decltype(i_promises)>(std::move(i_promises)); // \todo use C++14 lambda init-capture
|
||||
QTimer::singleShot(0, Qt::PreciseTimer, this, [this, changes, promises]
|
||||
{
|
||||
for (auto &tuple : *promises)
|
||||
{
|
||||
QString key;
|
||||
std::promise<CVariant> promise;
|
||||
std::tie(std::ignore, key, promise) = std::move(tuple);
|
||||
|
||||
promise.set_value(changes.value(key).first);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
CDataCacheRevision::LockGuard CDataCacheRevision::beginUpdate(const QMap<QString, qint64> ×tamps)
|
||||
{
|
||||
QMutexLocker lock(&m_mutex);
|
||||
|
||||
Q_ASSERT(! m_updateInProgress);
|
||||
Q_ASSERT(! m_lockFile.isLocked());
|
||||
Q_ASSERT(m_promises.empty());
|
||||
|
||||
if (! m_lockFile.lock())
|
||||
{
|
||||
@@ -301,6 +346,28 @@ namespace BlackMisc
|
||||
return m_updateInProgress && m_timestamps.contains(key);
|
||||
}
|
||||
|
||||
std::future<CVariant> CDataCacheRevision::promiseLoadedValue(QObject *pageOwner, const QString &key)
|
||||
{
|
||||
QMutexLocker lock(&m_mutex);
|
||||
|
||||
if (isNewerValueAvailable(key))
|
||||
{
|
||||
std::promise<CVariant> promise;
|
||||
auto future = promise.get_future();
|
||||
m_promises.emplace_back(pageOwner, key, std::move(promise));
|
||||
return future;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<std::tuple<QObject *, QString, std::promise<CVariant>>> CDataCacheRevision::loadedValuePromises()
|
||||
{
|
||||
QMutexLocker lock(&m_mutex);
|
||||
|
||||
Q_ASSERT(m_updateInProgress);
|
||||
return std::move(m_promises); // move into the return value, so m_promises becomes empty
|
||||
}
|
||||
|
||||
QJsonObject CDataCacheRevision::toJson(const QMap<QString, qint64> ×tamps)
|
||||
{
|
||||
QJsonObject result;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <QUuid>
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QLockFile>
|
||||
#include <future>
|
||||
|
||||
namespace BlackMisc
|
||||
{
|
||||
@@ -65,6 +66,12 @@ namespace BlackMisc
|
||||
//! During update, returns true if the on-disk timestamp of this key is newer than in-memory.
|
||||
bool isNewerValueAvailable(const QString &key) const;
|
||||
|
||||
//! Return a future which will be made ready when the value is loaded. Future is invalid if value is not loading.
|
||||
std::future<CVariant> promiseLoadedValue(QObject *pageOwner, const QString &key);
|
||||
|
||||
//! Returns (by move) the container of promises to load values.
|
||||
std::vector<std::tuple<QObject *, QString, std::promise<CVariant>>> loadedValuePromises();
|
||||
|
||||
private:
|
||||
mutable QMutex m_mutex { QMutex::Recursive };
|
||||
bool m_updateInProgress = false;
|
||||
@@ -74,6 +81,7 @@ namespace BlackMisc
|
||||
QLockFile m_lockFile { m_basename + "/.lock" };
|
||||
QUuid m_uuid;
|
||||
QMap<QString, qint64> m_timestamps;
|
||||
std::vector<std::tuple<QObject *, QString, std::promise<CVariant>>> m_promises;
|
||||
|
||||
static QJsonObject toJson(const QMap<QString, qint64> ×tamps);
|
||||
static QMap<QString, qint64> fromJson(const QJsonObject ×tamps);
|
||||
@@ -108,6 +116,7 @@ namespace BlackMisc
|
||||
private:
|
||||
const QString &persistentStore() const;
|
||||
void applyDeferredChanges();
|
||||
void deliverPromises(std::vector<std::tuple<QObject *, QString, std::promise<CVariant>>>);
|
||||
|
||||
CDataCache *const m_cache = nullptr;
|
||||
QUuid m_revision;
|
||||
@@ -137,6 +146,9 @@ namespace BlackMisc
|
||||
//! Return all files where data may be stored.
|
||||
QStringList enumerateStore() const;
|
||||
|
||||
//! Method used for implementing CData::syncLoad.
|
||||
std::future<CVariant> syncLoad(QObject *pageOwner, const QString &key);
|
||||
|
||||
private:
|
||||
CDataCache();
|
||||
|
||||
@@ -169,7 +181,8 @@ namespace BlackMisc
|
||||
//! Must be a void, non-const member function of the owner.
|
||||
template <typename T>
|
||||
CData(T *owner, NotifySlot<T> slot = nullptr) :
|
||||
CData::CCached(CDataCache::instance(), Trait::key(), Trait::isValid, Trait::defaultValue(), owner, slot)
|
||||
CData::CCached(CDataCache::instance(), Trait::key(), Trait::isValid, Trait::defaultValue(), owner, slot),
|
||||
m_owner(owner)
|
||||
{}
|
||||
|
||||
//! Reset the data to its default value.
|
||||
@@ -178,8 +191,15 @@ namespace BlackMisc
|
||||
//! Return the file that is used for persistence for this value.
|
||||
QString getFilename() const { return CDataCache::filenameForKey(this->getKey()); }
|
||||
|
||||
//! Return a future providing the value. If the value is still loading, the future will wait for it.
|
||||
//! If the value is not present, the variant is null. Bypasses async get and inhibits notification slot.
|
||||
std::future<CVariant> syncLoad() { return CDataCache::instance()->syncLoad(m_owner, this->getKey()); }
|
||||
|
||||
//! Data cache doesn't support setAndSave (because set() already causes save anyway).
|
||||
CStatusMessage setAndSave(const typename Trait::type &value, qint64 timestamp = 0) = delete;
|
||||
|
||||
private:
|
||||
QObject *m_owner = nullptr;
|
||||
};
|
||||
|
||||
/*!
|
||||
|
||||
@@ -224,6 +224,11 @@ namespace BlackMisc
|
||||
//! Mutex protecting operations which are critical on m_elements.
|
||||
mutable QMutex m_mutex { QMutex::Recursive };
|
||||
|
||||
protected:
|
||||
//! Synchronously return a current value.
|
||||
//! \threadsafe
|
||||
CVariant getValueSync(const QString &key) { return std::get<0>(getValue(key)); }
|
||||
|
||||
private:
|
||||
friend class Private::CValuePage;
|
||||
struct Element;
|
||||
|
||||
Reference in New Issue
Block a user