From aa975ded9d63a53492bf12c8817325af045f2983 Mon Sep 17 00:00:00 2001 From: Vincent Weevers Date: Fri, 25 Mar 2022 11:42:03 +0100 Subject: [PATCH] Yield `LEVEL_LOCKED` error when lock is held (#8) 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. --- README.md | 2 +- binding.cc | 8 ++++++++ test/lock-test.js | 52 +++++++++++++++++++++++++++++++++++++++++++++++ test/lock.js | 10 +++++++++ 4 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 test/lock-test.js create mode 100644 test/lock.js diff --git a/README.md b/README.md index 86364068..252aad03 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: diff --git a/binding.cc b/binding.cc index d4cff53a..01507c42 100644 --- a/binding.cc +++ b/binding.cc @@ -444,6 +444,14 @@ struct BaseWorker { argv = CreateCodeError(env, "LEVEL_NOT_FOUND", errMsg_); } else if (status_.IsCorruption()) { argv = CreateCodeError(env, "LEVEL_CORRUPTION", errMsg_); + } else if (status_.IsIOError()) { + 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 00000000..cbc0aeb8 --- /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 00000000..d74f6acb --- /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) +})