diff options
Diffstat (limited to 'common/storage_lmdb.cpp')
-rw-r--r-- | common/storage_lmdb.cpp | 153 |
1 files changed, 94 insertions, 59 deletions
diff --git a/common/storage_lmdb.cpp b/common/storage_lmdb.cpp index 08eea37..f7999d1 100644 --- a/common/storage_lmdb.cpp +++ b/common/storage_lmdb.cpp | |||
@@ -35,9 +35,6 @@ | |||
35 | #include <lmdb.h> | 35 | #include <lmdb.h> |
36 | #include "log.h" | 36 | #include "log.h" |
37 | 37 | ||
38 | SINK_DEBUG_AREA("storage") | ||
39 | // SINK_DEBUG_COMPONENT(d->storageRoot.toLatin1() + '/' + d->name.toLatin1()) | ||
40 | |||
41 | namespace Sink { | 38 | namespace Sink { |
42 | namespace Storage { | 39 | namespace Storage { |
43 | 40 | ||
@@ -169,6 +166,27 @@ public: | |||
169 | if (const int rc = mdb_dbi_open(transaction, db.constData(), flags, &dbi)) { | 166 | if (const int rc = mdb_dbi_open(transaction, db.constData(), flags, &dbi)) { |
170 | //Create the db if it is not existing already | 167 | //Create the db if it is not existing already |
171 | if (rc == MDB_NOTFOUND && !readOnly) { | 168 | if (rc == MDB_NOTFOUND && !readOnly) { |
169 | //Sanity check db name | ||
170 | { | ||
171 | auto parts = db.split('.'); | ||
172 | for (const auto &p : parts) { | ||
173 | auto containsSpecialCharacter = [] (const QByteArray &p) { | ||
174 | for (int i = 0; i < p.size(); i++) { | ||
175 | const auto c = p.at(i); | ||
176 | //Between 0 and z in the ascii table. Essentially ensures that the name is printable and doesn't contain special chars | ||
177 | if (c < 0x30 || c > 0x7A) { | ||
178 | return true; | ||
179 | } | ||
180 | } | ||
181 | return false; | ||
182 | }; | ||
183 | if (p.isEmpty() || containsSpecialCharacter(p)) { | ||
184 | SinkError() << "Tried to create a db with an invalid name. Hex:" << db.toHex() << " ASCII:" << db; | ||
185 | Q_ASSERT(false); | ||
186 | throw std::runtime_error("Fatal error while creating db."); | ||
187 | } | ||
188 | } | ||
189 | } | ||
172 | if (const int rc = mdb_dbi_open(transaction, db.constData(), flags | MDB_CREATE, &dbi)) { | 190 | if (const int rc = mdb_dbi_open(transaction, db.constData(), flags | MDB_CREATE, &dbi)) { |
173 | SinkWarning() << "Failed to create db " << QByteArray(mdb_strerror(rc)); | 191 | SinkWarning() << "Failed to create db " << QByteArray(mdb_strerror(rc)); |
174 | Error error(name.toLatin1(), ErrorCodes::GenericError, "Error while creating database: " + QByteArray(mdb_strerror(rc))); | 192 | Error error(name.toLatin1(), ErrorCodes::GenericError, "Error while creating database: " + QByteArray(mdb_strerror(rc))); |
@@ -542,6 +560,7 @@ DataStore::Transaction::Transaction(Transaction &&other) : d(nullptr) | |||
542 | DataStore::Transaction &DataStore::Transaction::operator=(DataStore::Transaction &&other) | 560 | DataStore::Transaction &DataStore::Transaction::operator=(DataStore::Transaction &&other) |
543 | { | 561 | { |
544 | if (&other != this) { | 562 | if (&other != this) { |
563 | abort(); | ||
545 | delete d; | 564 | delete d; |
546 | d = other.d; | 565 | d = other.d; |
547 | other.d = nullptr; | 566 | other.d = nullptr; |
@@ -639,11 +658,6 @@ static bool ensureCorrectDb(DataStore::NamedDatabase &database, const QByteArray | |||
639 | return !openedTheWrongDatabase; | 658 | return !openedTheWrongDatabase; |
640 | } | 659 | } |
641 | 660 | ||
642 | bool DataStore::Transaction::validateNamedDatabases() | ||
643 | { | ||
644 | return true; | ||
645 | } | ||
646 | |||
647 | DataStore::NamedDatabase DataStore::Transaction::openDatabase(const QByteArray &db, const std::function<void(const DataStore::Error &error)> &errorHandler, bool allowDuplicates) const | 661 | DataStore::NamedDatabase DataStore::Transaction::openDatabase(const QByteArray &db, const std::function<void(const DataStore::Error &error)> &errorHandler, bool allowDuplicates) const |
648 | { | 662 | { |
649 | if (!d) { | 663 | if (!d) { |
@@ -694,7 +708,7 @@ QList<QByteArray> DataStore::Transaction::getDatabaseNames() const | |||
694 | class DataStore::Private | 708 | class DataStore::Private |
695 | { | 709 | { |
696 | public: | 710 | public: |
697 | Private(const QString &s, const QString &n, AccessMode m); | 711 | Private(const QString &s, const QString &n, AccessMode m, const DbLayout &layout = {}); |
698 | ~Private(); | 712 | ~Private(); |
699 | 713 | ||
700 | QString storageRoot; | 714 | QString storageRoot; |
@@ -702,68 +716,85 @@ public: | |||
702 | 716 | ||
703 | MDB_env *env; | 717 | MDB_env *env; |
704 | AccessMode mode; | 718 | AccessMode mode; |
719 | Sink::Log::Context logCtx; | ||
720 | |||
721 | void initEnvironment(const QString &fullPath, const DbLayout &layout) | ||
722 | { | ||
723 | // Ensure the environment is only created once, and that we only have one environment per process | ||
724 | if (!(env = sEnvironments.value(fullPath))) { | ||
725 | QMutexLocker locker(&sMutex); | ||
726 | if (!(env = sEnvironments.value(fullPath))) { | ||
727 | int rc = 0; | ||
728 | if ((rc = mdb_env_create(&env))) { | ||
729 | SinkWarningCtx(logCtx) << "mdb_env_create: " << rc << " " << mdb_strerror(rc); | ||
730 | qCritical() << "mdb_env_create: " << rc << " " << mdb_strerror(rc); | ||
731 | } else { | ||
732 | //Limit large enough to accomodate all our named dbs. This only starts to matter if the number gets large, otherwise it's just a bunch of extra entries in the main table. | ||
733 | mdb_env_set_maxdbs(env, 50); | ||
734 | const bool readOnly = (mode == ReadOnly); | ||
735 | unsigned int flags = MDB_NOTLS; | ||
736 | if (readOnly) { | ||
737 | flags |= MDB_RDONLY; | ||
738 | } | ||
739 | if ((rc = mdb_env_open(env, fullPath.toStdString().data(), flags, 0664))) { | ||
740 | SinkWarningCtx(logCtx) << "mdb_env_open: " << rc << ":" << mdb_strerror(rc); | ||
741 | mdb_env_close(env); | ||
742 | env = 0; | ||
743 | } else { | ||
744 | if (RUNNING_ON_VALGRIND) { | ||
745 | // In order to run valgrind this size must be smaller than half your available RAM | ||
746 | // https://github.com/BVLC/caffe/issues/2404 | ||
747 | mdb_env_set_mapsize(env, (size_t)10485760 * (size_t)1000); // 1MB * 1000 | ||
748 | } else { | ||
749 | //This is the maximum size of the db (but will not be used directly), so we make it large enough that we hopefully never run into the limit. | ||
750 | mdb_env_set_mapsize(env, (size_t)10485760 * (size_t)100000); // 1MB * 1000 | ||
751 | } | ||
752 | Q_ASSERT(env); | ||
753 | sEnvironments.insert(fullPath, env); | ||
754 | //Open all available dbi's | ||
755 | bool noLock = true; | ||
756 | auto t = Transaction(new Transaction::Private(readOnly, nullptr, name, env, noLock)); | ||
757 | if (!layout.tables.isEmpty()) { | ||
758 | |||
759 | //TODO upgrade db if the layout has changed: | ||
760 | //* read existing layout | ||
761 | //* if layout is not the same create new layout | ||
762 | //If the db is read only, abort if the db is not yet existing. | ||
763 | //If the db is not read-only but is not existing, ensure we have a layout and create all tables. | ||
764 | |||
765 | for (auto it = layout.tables.constBegin(); it != layout.tables.constEnd(); it++) { | ||
766 | bool allowDuplicates = it.value(); | ||
767 | t.openDatabase(it.key(), {}, allowDuplicates); | ||
768 | } | ||
769 | } else { | ||
770 | for (const auto &db : t.getDatabaseNames()) { | ||
771 | //Get dbi to store for future use. | ||
772 | t.openDatabase(db); | ||
773 | } | ||
774 | } | ||
775 | //To persist the dbis (this is also necessary for read-only transactions) | ||
776 | t.commit(); | ||
777 | } | ||
778 | } | ||
779 | } | ||
780 | } | ||
781 | } | ||
782 | |||
705 | }; | 783 | }; |
706 | 784 | ||
707 | DataStore::Private::Private(const QString &s, const QString &n, AccessMode m) : storageRoot(s), name(n), env(0), mode(m) | 785 | DataStore::Private::Private(const QString &s, const QString &n, AccessMode m, const DbLayout &layout) : storageRoot(s), name(n), env(0), mode(m), logCtx(n.toLatin1()) |
708 | { | 786 | { |
787 | |||
709 | const QString fullPath(storageRoot + '/' + name); | 788 | const QString fullPath(storageRoot + '/' + name); |
710 | QFileInfo dirInfo(fullPath); | 789 | QFileInfo dirInfo(fullPath); |
711 | if (!dirInfo.exists() && mode == ReadWrite) { | 790 | if (!dirInfo.exists() && mode == ReadWrite) { |
712 | QDir().mkpath(fullPath); | 791 | QDir().mkpath(fullPath); |
713 | dirInfo.refresh(); | 792 | dirInfo.refresh(); |
714 | } | 793 | } |
715 | Sink::Log::Context logCtx{n.toLatin1()}; | ||
716 | if (mode == ReadWrite && !dirInfo.permission(QFile::WriteOwner)) { | 794 | if (mode == ReadWrite && !dirInfo.permission(QFile::WriteOwner)) { |
717 | qCritical() << fullPath << "does not have write permissions. Aborting"; | 795 | qCritical() << fullPath << "does not have write permissions. Aborting"; |
718 | } else if (dirInfo.exists()) { | 796 | } else if (dirInfo.exists()) { |
719 | // Ensure the environment is only created once | 797 | initEnvironment(fullPath, layout); |
720 | QMutexLocker locker(&sMutex); | ||
721 | |||
722 | /* | ||
723 | * It seems we can only ever have one environment open in the process. | ||
724 | * Otherwise multi-threading breaks. | ||
725 | */ | ||
726 | env = sEnvironments.value(fullPath); | ||
727 | if (!env) { | ||
728 | int rc = 0; | ||
729 | if ((rc = mdb_env_create(&env))) { | ||
730 | // TODO: handle error | ||
731 | SinkWarningCtx(logCtx) << "mdb_env_create: " << rc << " " << mdb_strerror(rc); | ||
732 | } else { | ||
733 | mdb_env_set_maxdbs(env, 50); | ||
734 | unsigned int flags = MDB_NOTLS; | ||
735 | if (mode == ReadOnly) { | ||
736 | flags |= MDB_RDONLY; | ||
737 | } | ||
738 | if ((rc = mdb_env_open(env, fullPath.toStdString().data(), flags, 0664))) { | ||
739 | SinkWarningCtx(logCtx) << "mdb_env_open: " << rc << ":" << mdb_strerror(rc); | ||
740 | mdb_env_close(env); | ||
741 | env = 0; | ||
742 | } else { | ||
743 | if (RUNNING_ON_VALGRIND) { | ||
744 | // In order to run valgrind this size must be smaller than half your available RAM | ||
745 | // https://github.com/BVLC/caffe/issues/2404 | ||
746 | const size_t dbSize = (size_t)10485760 * (size_t)1000; // 1MB * 1000 | ||
747 | mdb_env_set_mapsize(env, dbSize); | ||
748 | } else { | ||
749 | // FIXME: dynamic resize | ||
750 | const size_t dbSize = (size_t)10485760 * (size_t)8000; // 1MB * 8000 | ||
751 | mdb_env_set_mapsize(env, dbSize); | ||
752 | } | ||
753 | sEnvironments.insert(fullPath, env); | ||
754 | //Open all available dbi's | ||
755 | bool noLock = true; | ||
756 | bool requestedRead = m == ReadOnly; | ||
757 | auto t = Transaction(new Transaction::Private(requestedRead, nullptr, name, env, noLock)); | ||
758 | for (const auto &db : t.getDatabaseNames()) { | ||
759 | //Get dbi to store for future use. | ||
760 | t.openDatabase(db); | ||
761 | } | ||
762 | //To persist the dbis (this is also necessary for read-only transactions) | ||
763 | t.commit(); | ||
764 | } | ||
765 | } | ||
766 | } | ||
767 | } | 798 | } |
768 | } | 799 | } |
769 | 800 | ||
@@ -777,6 +808,10 @@ DataStore::DataStore(const QString &storageRoot, const QString &name, AccessMode | |||
777 | { | 808 | { |
778 | } | 809 | } |
779 | 810 | ||
811 | DataStore::DataStore(const QString &storageRoot, const DbLayout &dbLayout, AccessMode mode) : d(new Private(storageRoot, dbLayout.name, mode, dbLayout)) | ||
812 | { | ||
813 | } | ||
814 | |||
780 | DataStore::~DataStore() | 815 | DataStore::~DataStore() |
781 | { | 816 | { |
782 | delete d; | 817 | delete d; |