From f2ab1611e4327fc38a3c39088c23f52d68e4dc67 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 5 Mar 2017 14:32:36 -0800 Subject: [PATCH] db: rewrite lowlevelup without asyncobject. --- lib/db/lowlevelup.js | 204 +++++++++++++++++++++++++++++++++---------- 1 file changed, 157 insertions(+), 47 deletions(-) diff --git a/lib/db/lowlevelup.js b/lib/db/lowlevelup.js index dccf00cc..56df6c42 100644 --- a/lib/db/lowlevelup.js +++ b/lib/db/lowlevelup.js @@ -7,18 +7,16 @@ 'use strict'; -var util = require('../utils/util'); var assert = require('assert'); -var AsyncObject = require('../utils/asyncobject'); +var util = require('../utils/util'); +var Lock = require('../utils/lock'); var co = require('../utils/co'); var VERSION_ERROR; /** * Extremely low-level version of levelup. * The only levelup feature it provides is - * error-wrapping. It gives a nice recallable - * `open()` method and event. It assumes ascii - * keys and binary values. + * error-wrapping. * * This avoids pulling in extra deps and * lowers memory usage. @@ -33,12 +31,19 @@ function LowlevelUp(file, options) { if (!(this instanceof LowlevelUp)) return new LowlevelUp(file, options); - AsyncObject.call(this); + assert(typeof file === 'string', 'Filename is required.'); + assert(options, 'Options are required.'); + assert(options.db, 'Database backend is required.'); this.options = options; this.backend = options.db; this.location = file; this.bufferKeys = options.bufferKeys === true; + this.locker = new Lock(); + + this.loading = false; + this.closing = false; + this.loaded = false; this.db = new options.db(file); @@ -53,15 +58,94 @@ function LowlevelUp(file, options) { this.binding = this.db.binding; } -util.inherits(LowlevelUp, AsyncObject); - /** - * Open the database (recallable). - * @alias LowlevelUp#open + * Open the database. * @returns {Promise} */ -LowlevelUp.prototype._open = function open() { +LowlevelUp.prototype.open = co(function* open() { + var unlock = yield this.locker.lock(); + try { + return yield this._open(); + } finally { + unlock(); + } +}); + +/** + * Close the database. + * @returns {Promise} + */ + +LowlevelUp.prototype.close = co(function* close() { + var unlock = yield this.locker.lock(); + try { + return yield this._close(); + } finally { + unlock(); + } +}); + +/** + * Open the database (without a lock). + * @private + * @returns {Promise} + */ + +LowlevelUp.prototype._open = co(function* open() { + if (this.loaded) + throw new Error('Database is already open.'); + + assert(!this.loading); + assert(!this.closing); + + this.loading = true; + + try { + yield this.load(); + } catch (e) { + this.loading = false; + throw e; + } + + this.loading = false; + this.loaded = true; +}); + +/** + * Close the database (without a lock). + * @private + * @returns {Promise} + */ + +LowlevelUp.prototype._close = co(function* close() { + if (!this.loaded) + throw new Error('Database is already closed.'); + + assert(!this.loading); + assert(!this.closing); + + this.loaded = false; + this.closing = true; + + try { + yield this.unload(); + } catch (e) { + this.loaded = true; + this.closing = false; + throw e; + } + + this.closing = false; +}); + +/** + * Open the database. + * @private + * @returns {Promise} + */ + +LowlevelUp.prototype.load = function load() { var self = this; return new Promise(function(resolve, reject) { self.binding.open(self.options, co.wrap(resolve, reject)); @@ -69,12 +153,12 @@ LowlevelUp.prototype._open = function open() { }; /** - * Close the database (recallable). - * @alias LowlevelUp#close + * Close the database. + * @private * @returns {Promise} */ -LowlevelUp.prototype._close = function close() { +LowlevelUp.prototype.unload = function unload() { var self = this; return new Promise(function(resolve, reject) { self.binding.close(co.wrap(resolve, reject)); @@ -89,13 +173,17 @@ LowlevelUp.prototype._close = function close() { LowlevelUp.prototype.destroy = function destroy() { var self = this; - assert(!this.loading); - assert(!this.closing); - assert(!this.loaded); - return new Promise(function(resolve, reject) { - if (!self.backend.destroy) - return reject(new Error('Cannot destroy.')); + if (self.loaded || self.closing) { + reject(new Error('Cannot destroy open database.')); + return; + } + + if (!self.backend.destroy) { + reject(new Error('Cannot destroy (method not available).')); + return; + } + self.backend.destroy(self.location, co.wrap(resolve, reject)); }); }; @@ -108,13 +196,17 @@ LowlevelUp.prototype.destroy = function destroy() { LowlevelUp.prototype.repair = function repair() { var self = this; - assert(!this.loading); - assert(!this.closing); - assert(!this.loaded); - return new Promise(function(resolve, reject) { - if (!self.backend.repair) - return reject(new Error('Cannot repair.')); + if (self.loaded || self.closing) { + reject(new Error('Cannot repair open database.')); + return; + } + + if (!self.backend.repair) { + reject(new Error('Cannot repair (method not available).')); + return; + } + self.backend.repair(self.location, co.wrap(resolve, reject)); }); }; @@ -128,14 +220,14 @@ LowlevelUp.prototype.repair = function repair() { LowlevelUp.prototype.backup = function backup(path) { var self = this; - assert(!this.loading); - assert(!this.closing); - assert(this.loaded); - if (!this.binding.backup) return this.clone(path); return new Promise(function(resolve, reject) { + if (!self.loaded) { + reject(new Error('Database is closed.')); + return; + } self.binding.backup(path, co.wrap(resolve, reject)); }); }; @@ -148,10 +240,11 @@ LowlevelUp.prototype.backup = function backup(path) { LowlevelUp.prototype.get = function get(key) { var self = this; - - assert(this.loaded, 'Cannot use database before it is loaded.'); - return new Promise(function(resolve, reject) { + if (!self.loaded) { + reject(new Error('Database is closed.')); + return; + } self.binding.get(key, function(err, result) { if (err) { if (isNotFound(err)) @@ -172,8 +265,11 @@ LowlevelUp.prototype.get = function get(key) { LowlevelUp.prototype.put = function put(key, value) { var self = this; - assert(this.loaded, 'Cannot use database before it is loaded.'); return new Promise(function(resolve, reject) { + if (!self.loaded) { + reject(new Error('Database is closed.')); + return; + } self.binding.put(key, value, co.wrap(resolve, reject)); }); }; @@ -186,8 +282,11 @@ LowlevelUp.prototype.put = function put(key, value) { LowlevelUp.prototype.del = function del(key) { var self = this; - assert(this.loaded, 'Cannot use database before it is loaded.'); return new Promise(function(resolve, reject) { + if (!self.loaded) { + reject(new Error('Database is closed.')); + return; + } self.binding.del(key, co.wrap(resolve, reject)); }); }; @@ -201,12 +300,17 @@ LowlevelUp.prototype.del = function del(key) { LowlevelUp.prototype.batch = function batch(ops) { var self = this; - assert(this.loaded, 'Cannot use database before it is loaded.'); - - if (!ops) + if (!ops) { + if (!this.loaded) + throw new Error('Database is closed.'); return new Batch(this); + } return new Promise(function(resolve, reject) { + if (!self.loaded) { + reject(new Error('Database is closed.')); + return; + } self.binding.batch(ops, co.wrap(resolve, reject)); }); }; @@ -220,7 +324,8 @@ LowlevelUp.prototype.batch = function batch(ops) { LowlevelUp.prototype.iterator = function iterator(options) { var opt; - assert(this.loaded, 'Cannot use database before it is loaded.'); + if (!this.loaded) + throw new Error('Database is closed.'); opt = { gte: options.gte, @@ -254,7 +359,8 @@ LowlevelUp.prototype.iterator = function iterator(options) { */ LowlevelUp.prototype.getProperty = function getProperty(name) { - assert(this.loaded, 'Cannot use database before it is loaded.'); + if (!this.loaded) + throw new Error('Database is closed.'); if (!this.binding.getProperty) return ''; @@ -272,11 +378,16 @@ LowlevelUp.prototype.getProperty = function getProperty(name) { LowlevelUp.prototype.approximateSize = function approximateSize(start, end) { var self = this; - assert(this.loaded, 'Cannot use database before it is loaded.'); - return new Promise(function(resolve, reject) { - if (!self.binding.approximateSize) - return reject(new Error('Cannot get size.')); + if (!self.loaded) { + reject(new Error('Database is closed.')); + return; + } + + if (!self.binding.approximateSize) { + reject(new Error('Cannot get size.')); + return; + } self.binding.approximateSize(start, end, co.wrap(resolve, reject)); }); @@ -483,9 +594,8 @@ LowlevelUp.prototype.clone = co(function* clone(path) { var total = 0; var tmp, batch, iter, item; - assert(!this.loading); - assert(!this.closing); - assert(this.loaded); + if (!this.loaded) + throw new Error('Database is closed.'); options.createIfMissing = true; options.errorIfExists = true;