From ce4cdb2c1504890332469933c0cfe61fbb6bd456 Mon Sep 17 00:00:00 2001 From: Vincent Weevers Date: Sat, 12 Mar 2022 12:18:39 +0100 Subject: [PATCH 1/3] Yield `LEVEL_LOCKED` error when lock is held This allows `level-party` or `rave-level` (its `abstract-level` replacement) to catch this specific error, instead of using a catch-all that swallows other errors. --- binding.cc | 9 ++++++++ test/lock-test.js | 52 +++++++++++++++++++++++++++++++++++++++++++++++ test/lock.js | 10 +++++++++ 3 files changed, 71 insertions(+) create mode 100644 test/lock-test.js create mode 100644 test/lock.js diff --git a/binding.cc b/binding.cc index d4cff53..bd6b474 100644 --- a/binding.cc +++ b/binding.cc @@ -444,6 +444,15 @@ struct BaseWorker { argv = CreateCodeError(env, "LEVEL_NOT_FOUND", errMsg_); } else if (status_.IsCorruption()) { argv = CreateCodeError(env, "LEVEL_CORRUPTION", errMsg_); + } else if (status_.IsIOError()) { + // TODO: add codes to abstract-level readme + if (strlen(errMsg_) > 15 && strncmp("IO error: lock ", errMsg_, 15) == 0) { // env_posix.cc + argv = CreateCodeError(env, "LEVEL_LOCKED", errMsg_); + } else if (strlen(errMsg_) > 19 && strncmp("IO error: LockFile ", errMsg_, 19) == 0) { // env_win.cc + argv = CreateCodeError(env, "LEVEL_LOCKED", errMsg_); + } else { + argv = CreateCodeError(env, "LEVEL_IO_ERROR", errMsg_); + } } else { argv = CreateError(env, errMsg_); } diff --git a/test/lock-test.js b/test/lock-test.js new file mode 100644 index 0000000..cbc0aeb --- /dev/null +++ b/test/lock-test.js @@ -0,0 +1,52 @@ +'use strict' + +const test = require('tape') +const tempy = require('tempy') +const fork = require('child_process').fork +const path = require('path') +const { ClassicLevel } = require('..') + +test('lock held by same process', async function (t) { + t.plan(2) + + const location = tempy.directory() + const db1 = new ClassicLevel(location) + await db1.open() + const db2 = new ClassicLevel(location) + + try { + await db2.open() + } catch (err) { + t.is(err.code, 'LEVEL_DATABASE_NOT_OPEN', 'second instance failed to open') + t.is(err.cause.code, 'LEVEL_LOCKED', 'second instance got lock error') + } + + return db1.close() +}) + +test('lock held by other process', function (t) { + t.plan(6) + + const location = tempy.directory() + const db = new ClassicLevel(location) + + db.open(function (err) { + t.ifError(err, 'no open error') + + const child = fork(path.join(__dirname, 'lock.js'), [location]) + + child.on('message', function (err) { + t.is(err.code, 'LEVEL_DATABASE_NOT_OPEN', 'second process failed to open') + t.is(err.cause.code, 'LEVEL_LOCKED', 'second process got lock error') + + child.disconnect() + }) + + child.on('exit', function (code, sig) { + t.is(code, 0, 'child exited normally') + t.is(sig, null, 'not terminated due to signal') + + db.close(t.ifError.bind(t)) + }) + }) +}) diff --git a/test/lock.js b/test/lock.js new file mode 100644 index 0000000..d74f6ac --- /dev/null +++ b/test/lock.js @@ -0,0 +1,10 @@ +'use strict' + +const { ClassicLevel } = require('..') + +const location = process.argv[2] +const db = new ClassicLevel(location) + +db.open(function (err) { + process.send(err) +}) From 25056a492759b09516034348fb01196f687b300f Mon Sep 17 00:00:00 2001 From: Vincent Weevers Date: Sun, 20 Mar 2022 13:08:23 +0100 Subject: [PATCH 2/3] Remove done TODO comment --- binding.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/binding.cc b/binding.cc index bd6b474..01507c4 100644 --- a/binding.cc +++ b/binding.cc @@ -445,7 +445,6 @@ struct BaseWorker { } else if (status_.IsCorruption()) { argv = CreateCodeError(env, "LEVEL_CORRUPTION", errMsg_); } else if (status_.IsIOError()) { - // TODO: add codes to abstract-level readme if (strlen(errMsg_) > 15 && strncmp("IO error: lock ", errMsg_, 15) == 0) { // env_posix.cc argv = CreateCodeError(env, "LEVEL_LOCKED", errMsg_); } else if (strlen(errMsg_) > 19 && strncmp("IO error: LockFile ", errMsg_, 19) == 0) { // env_win.cc From 69ab1d4d4b3b6ec3fc436c6a038134fc3331901a Mon Sep 17 00:00:00 2001 From: Vincent Weevers Date: Mon, 21 Mar 2022 00:41:23 +0100 Subject: [PATCH 3/3] Describe `LEVEL_LOCKED` in README too --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8636406..252aad0 100644 --- a/README.md +++ b/README.md @@ -194,7 +194,7 @@ Read-only getter that returns a string reflecting the current state of the datab ### `db.open([options][, callback])` -Open the database. The `callback` function will be called with no arguments when successfully opened, or with a single error argument if opening failed. If no callback is provided, a promise is returned. Options passed to `open()` take precedence over options passed to the database constructor. +Open the database. The `callback` function will be called with no arguments when successfully opened, or with a single error argument if opening failed. The database has an exclusive lock (on disk): if another process or instance has already opened the underlying LevelDB store at the given `location` then opening will fail with error code [`LEVEL_LOCKED`](https://github.com/Level/abstract-level#errors). If no callback is provided, a promise is returned. Options passed to `open()` take precedence over options passed to the database constructor. The optional `options` object may contain: