diff --git a/dependencies/lmdb/libraries/liblmdb/lmdb.h b/dependencies/lmdb/libraries/liblmdb/lmdb.h index f3db0d8f5..b258aac06 100644 --- a/dependencies/lmdb/libraries/liblmdb/lmdb.h +++ b/dependencies/lmdb/libraries/liblmdb/lmdb.h @@ -339,6 +339,9 @@ typedef int (MDB_enc_func)(const MDB_val *src, MDB_val *dst, const MDB_val *key, */ typedef void (MDB_sum_func)(const MDB_val *src, MDB_val *dst, const MDB_val *key); #endif +// +typedef int (MDB_check_fd)(const mdb_filehandle_t fd, MDB_env* env); +// /** @defgroup mdb_env Environment Flags * @{ diff --git a/dependencies/lmdb/libraries/liblmdb/mdb.c b/dependencies/lmdb/libraries/liblmdb/mdb.c index daf87cdca..c148f132f 100644 --- a/dependencies/lmdb/libraries/liblmdb/mdb.c +++ b/dependencies/lmdb/libraries/liblmdb/mdb.c @@ -5872,6 +5872,8 @@ mdb_env_setup_locks(MDB_env *env, MDB_name *fname, int mode, int *excl) MDB_OFF_T size, rsize; rc = mdb_fopen(env, fname, MDB_O_LOCKS, mode, &env->me_lfd); + if (!rc) + rc = ((MDB_check_fd*) env->me_userctx)(env->me_lfd, env); if (rc) { /* Omit lockfile if read-only env on read-only filesystem */ if (rc == MDB_ERRCODE_ROFS && (env->me_flags & MDB_RDONLY)) { @@ -6300,7 +6302,7 @@ mdb_env_open(MDB_env *env, const char *path, unsigned int flags, mdb_mode_t mode env->boot_id = strtoll(boot_uuid, &endptr, 16); } #endif - /**/ + /**/ env->me_path = strdup(path); env->me_dbxs = calloc(env->me_maxdbs, sizeof(MDB_dbx)); env->me_dbflags = calloc(env->me_maxdbs, sizeof(uint16_t)); diff --git a/src/env.cpp b/src/env.cpp index a97033279..0389153f9 100644 --- a/src/env.cpp +++ b/src/env.cpp @@ -34,6 +34,39 @@ EnvWrap::EnvWrap(const CallbackInfo& info) : ObjectWrap(info) { pthread_mutex_init(this->writingLock, nullptr); pthread_cond_init(this->writingCond, nullptr); } +MDB_env* foundEnv; +const int EXISTING_ENV_FOUND = 10; +int checkExistingEnvs(mdb_filehandle_t fd, MDB_env* env) { + uint64_t inode, dev; + #ifdef _WIN32 + BY_HANDLE_FILE_INFORMATION fileInformation; + GetFileInformationByHandle(fd, &fileInformation); + dev = fileInformation.dwVolumeSerialNumber; + inode = ((uint64_t) fileInformation.nFileIndexHigh << 32) | fileInformation.nFileIndexLow; + #else + auto st = stat(path); + dev = st.st_dev; + inode = st.st_ino; + #endif + fprintf(stderr, "check existing env at dev %llu inode %llu\n", dev, inode); + for (auto envRef = EnvWrap::envTracking->envs.begin(); envRef != EnvWrap::envTracking->envs.end();) { + if (envRef->dev == dev && envRef->inode == inode) { + fprintf(stderr, "found existing env at dev %llu inode %llu\n", dev, inode); + envRef->count++; + foundEnv = envRef->env; + return EXISTING_ENV_FOUND; + } + ++envRef; + } + fprintf(stderr, "no existing env at dev %llu inode %llu\n", dev, inode); + SharedEnv envRef; + envRef.dev = dev; + envRef.inode = inode; + envRef.env = env; + envRef.count = 1; + EnvWrap::envTracking->envs.push_back(envRef); + return 0; +} EnvWrap::~EnvWrap() { // Close if not closed already @@ -210,24 +243,10 @@ Napi::Value EnvWrap::open(const CallbackInfo& info) { } int EnvWrap::openEnv(int flags, int jsFlags, const char* path, char* keyBuffer, Compression* compression, int maxDbs, int maxReaders, mdb_size_t mapSize, int pageSize, char* encryptionKey) { - pthread_mutex_lock(envTracking->envsLock); this->keyBuffer = keyBuffer; this->compression = compression; this->jsFlags = jsFlags; - MDB_env* env = this->env; - for (auto envPath = envTracking->envs.begin(); envPath != envTracking->envs.end();) { - char* existingPath = envPath->path; - if (!strcmp(existingPath, path)) { - envPath->count++; - mdb_env_close(env); - this->env = envPath->env; - pthread_mutex_unlock(envTracking->envsLock); - if ((jsFlags & DELETE_ON_CLOSE) || (flags & MDB_OVERLAPPINGSYNC)) - openEnvWraps.push_back(this); - return 0; - } - ++envPath; - } + int rc; rc = mdb_env_set_maxdbs(env, maxDbs); if (rc) goto fail; @@ -244,7 +263,7 @@ int EnvWrap::openEnv(int flags, int jsFlags, const char* path, char* keyBuffer, enckey.mv_data = encryptionKey; enckey.mv_size = 32; #ifdef MDB_RPAGE_CACHE - rc = mdb_env_set_encrypt(this->env, encfunc, &enckey, 0); + rc = mdb_env_set_encrypt(env, encfunc, &enckey, 0); #else rc = -1; #endif @@ -258,24 +277,24 @@ int EnvWrap::openEnv(int flags, int jsFlags, const char* path, char* keyBuffer, if (flags & MDB_NOLOCK) { fprintf(stderr, "You chose to use MDB_NOLOCK which is not officially supported by node-lmdb. You have been warned!\n"); } + mdb_env_set_userctx(env, checkExistingEnvs); // Set MDB_NOTLS to enable multiple read-only transactions on the same thread (in this case, the nodejs main thread) flags |= MDB_NOTLS; // TODO: make file attributes configurable // *String::Utf8Value(Isolate::GetCurrent(), path) + pthread_mutex_lock(envTracking->envsLock); rc = mdb_env_open(env, path, flags, 0664); - mdb_env_get_flags(env, (unsigned int*) &flags); if (rc != 0) { mdb_env_close(env); - goto fail; - } - SharedEnv envPath; - envPath.path = strdup(path); - envPath.env = env; - envPath.count = 1; - envPath.deleteOnClose = jsFlags & DELETE_ON_CLOSE; - envTracking->envs.push_back(envPath); + if (rc == EXISTING_ENV_FOUND) { + fprintf(stderr, "env_open failed because of existing env %p", foundEnv); + env = foundEnv; + } else + goto fail; + } + mdb_env_get_flags(env, (unsigned int*) &flags); if ((jsFlags & DELETE_ON_CLOSE) || (flags & MDB_OVERLAPPINGSYNC)) openEnvWraps.push_back(this); pthread_mutex_unlock(envTracking->envsLock); @@ -283,7 +302,7 @@ int EnvWrap::openEnv(int flags, int jsFlags, const char* path, char* keyBuffer, fail: pthread_mutex_unlock(envTracking->envsLock); - this->env = nullptr; + env = nullptr; return rc; } Napi::Value EnvWrap::getMaxKeySize(const CallbackInfo& info) { @@ -316,18 +335,11 @@ NAPI_FUNCTION(setJSFlags) { #endif -void SharedEnv::finish(bool close) { -} NAPI_FUNCTION(EnvWrap::onExit) { // close all the environments for (auto envWrap : openEnvWraps) { envWrap->closeEnv(); } - /*pthread_mutex_lock(envTracking->envsLock); - for (auto envPath : envTracking->envs) { - envPath.finish(false); - } - pthread_mutex_unlock(envTracking->envsLock);*/ napi_value returnValue; RETURN_UNDEFINED; } @@ -370,9 +382,13 @@ void EnvWrap::closeEnv() { if (envFlags & MDB_OVERLAPPINGSYNC) { mdb_env_sync(env, 1); } + char* path; + mdb_env_get_path(env, &((const char*)path)); + path = strdup(path); mdb_env_close(env); - if (envPath->deleteOnClose) { - unlink(envPath->path); + fprintf(stderr, "Closed path %s\n", path); + if (jsFlags & DELETE_ON_CLOSE) { + unlink(path); //unlink(strcat(envPath->path, "-lock")); } envTracking->envs.erase(envPath); diff --git a/src/lmdb-js.h b/src/lmdb-js.h index 3f313645b..853c34a2f 100644 --- a/src/lmdb-js.h +++ b/src/lmdb-js.h @@ -183,10 +183,9 @@ class CursorWrap; class SharedEnv { public: MDB_env* env; - char* path; + uint64_t dev; + uint64_t inode; int count; - bool deleteOnClose; - void finish(bool close); }; const int INTERRUPT_BATCH = 9998; diff --git a/test/index.test.js b/test/index.test.js index 51761fd25..bbc851907 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -1130,7 +1130,7 @@ describe('lmdb-js', function() { }); }); }); - describe.only('Read-only Threads', function() { + describe('Read-only Threads', function() { this.timeout(1000000); it('will run a group of threads with read-only transactions', function(done) { var child = spawn('node', [fileURLToPath(new URL('./readonly-threads.cjs', import.meta.url))]); diff --git a/test/readonly-threads.cjs b/test/readonly-threads.cjs index db49d654d..ad9abb3d7 100644 --- a/test/readonly-threads.cjs +++ b/test/readonly-threads.cjs @@ -38,12 +38,8 @@ if (isMainThread) { setTimeout(() => { worker.terminate() - }, 8000); + }, 20); if (messages.length === workerCount) { - db.close(); - for (var i = 0; i < messages.length; i ++) { - assert(messages[i] === value.toString('hex')); - } console.log("done", threadId) //setTimeout(() => //process.exit(0), 200); @@ -72,19 +68,16 @@ if (isMainThread) { if (msg.key) { var value = db.get(msg.key); let lastIterate = db.getRange().iterate() - setInterval(() => { + let interval = setInterval(() => { db.get(msg.key); let iterate = db.getRange().iterate(); while(!lastIterate.next().done){} lastIterate = iterate; }, 1); setTimeout(() => { - if (value === null) { + clearInterval(interval) parentPort.postMessage(""); - } else { - parentPort.postMessage(value.toString('hex')); - } - }, 10000); + }, 100); } });