[Pkg-javascript-commits] [node-leveldown] 71/492: cleanup & complete & add tests for deferred ops

Andrew Kelley andrewrk-guest at moszumanska.debian.org
Sun Jul 6 17:13:46 UTC 2014


This is an automated email from the git hooks/post-receive script.

andrewrk-guest pushed a commit to annotated tag rocksdb-0.10.1
in repository node-leveldown.

commit a56a380ad177ada9dc8ec8d6c40a676e1b2844ea
Author: Rod Vagg <rod at vagg.org>
Date:   Sun Nov 18 12:13:10 2012 +1100

    cleanup & complete & add tests for deferred ops
---
 lib/levelup.js             | 111 ++++++++++++++++++++++++++++++++-------------
 test/common.js             |   1 +
 test/deferred-open-test.js |  98 +++++++++++++++++++++++++++++++++++++++
 test/read-stream-test.js   |  36 +++++++++++++++
 test/simple-test.js        |   2 +-
 5 files changed, 216 insertions(+), 32 deletions(-)

diff --git a/lib/levelup.js b/lib/levelup.js
index 6216f9e..dd418d6 100644
--- a/lib/levelup.js
+++ b/lib/levelup.js
@@ -36,17 +36,25 @@ var bridge       = require('bindings')('levelup.node')
       return callback_
     }
 
+// Possible this._status values:
+//  - 'new'     - newly created, not opened or closed
+//  - 'opening' - waiting for the database to be opened, post open()
+//  - 'open'    - successfully opened the database, available for use
+//  - 'closing' - waiting for the database to be closed, post close()
+//  - 'closed'  - database has been successfully closed, should not be used
+//                except for another open() operation
+
 function LevelUP (location, options) {
   this.__proto__.__proto__ = EventEmitter.prototype
   EventEmitter.call(this)
   this._options = extend(extend({}, defaultOptions), options)
   this._location = location
-  this.status = "new"
+  this._status = 'new'
 }
 
 LevelUP.prototype = {
     open: function (callback) {
-      this.status = "opening"
+      this._status = 'opening'
       var execute = function () {
         var db = bridge.createDatabase()
         db.open(this._location, this._options, function (err) {
@@ -57,7 +65,7 @@ LevelUP.prototype = {
             this.emit('error', err)
           } else {
             this._db = db
-            this.status = "open"
+            this._status = 'open'
             callback && callback(null, this)
             this.emit('ready')
           }
@@ -73,19 +81,19 @@ LevelUP.prototype = {
     //TODO: we can crash Node by submitting an operation between close() and the actual closing of the database
   , close: function (callback) {
       if (this.isOpen()) {
-        this.status = "closing"
+        this._status = 'closing'
         this._db.close(function () {
-          this.status = "closed"
+          this._status = 'closed'
           this.emit('closed')
           callback.apply(null, arguments)
         }.bind(this))
         this._db = null
-      } else if (this.status === "closed") {
+      } else if (this._status == 'closed') {
         callback()
-      } else if (this.status === "closing") {
-        this.on("closed", callback)
-      } else if (this.status === "opening") {
-        this.on("ready", function () {
+      } else if (this._status == 'closing') {
+        this.on('closed', callback)
+      } else if (this._status == 'opening') {
+        this.on('ready', function () {
           this.close(callback)
         })
       } else {
@@ -97,12 +105,27 @@ LevelUP.prototype = {
     }
 
   , isOpen: function () {
-      return this.status === "open"
+      return this._status == 'open'
+    }
+
+    // in between these two there is 'new' and 'opening'
+
+  , isClosed: function () {
+      // covers 'closing' and 'closed'
+      return (/^clos/).test(this._status)
     }
 
   , get: function (key_, options_, callback_) {
-      var callback = getCallback(options_, callback_)
-        , options, key, err
+      var callback, options, key, err
+
+      if (!this.isOpen() && !this.isClosed()) {
+        // limbo, defer the operation
+        return this.once('ready', function () {
+          this.get(key_, options_, callback_)
+        })
+      }
+
+      callback = getCallback(options_, callback_)
 
       if (this.isOpen()) {
         options  = getOptions(options_, this._options)
@@ -117,15 +140,24 @@ LevelUP.prototype = {
           callback && callback(null, toEncoding(value, options.valueEncoding || options.encoding), key_)
         })
       } else {
-        this.once("ready", function () {
-          this.get(key_, options_, callback_)
-        })
+        err = new errors.ReadError('Database is not open')
+        if (callback)
+          return callback(err)
+        throw err
       }
     }
 
   , put: function (key, value, options_, callback_) {
-      var callback = getCallback(options_, callback_)
-        , options, err
+      var callback, options, err
+
+      if (!this.isOpen() && !this.isClosed()) {
+        // limbo, defer the operation
+        return this.once('ready', function () {
+          this.put(key, value, options_, callback_)
+        })
+      }
+
+      callback = getCallback(options_, callback_)
 
       if (this.isOpen()) {
         options  = getOptions(options_, this._options)
@@ -144,15 +176,24 @@ LevelUP.prototype = {
           }
         }.bind(this))
       } else {
-        this.once("ready", function () {
-          this.put(key, value, options_, callback_)
-        })
+        err = new errors.WriteError('Database is not open')
+        if (callback)
+          return callback(err)
+        throw err
       }
     }
 
   , del: function (key, options_, callback_) {
-      var callback = getCallback(options_, callback_)
-        , options, err
+      var callback, options, err
+
+      if (!this.isOpen() && !this.isClosed()) {
+        // limbo, defer the operation
+        return this.once('ready', function () {
+          this.del(key, options_, callback_)
+        })
+      }
+
+      callback = getCallback(options_, callback_)
 
       if (this.isOpen()) {
         options  = getOptions(options_, this._options)
@@ -169,23 +210,31 @@ LevelUP.prototype = {
           }
         }.bind(this))
       } else {
-        this.once("ready", function () {
-          this.del(key, options_, callback_)
-        })
+        err = new errors.WriteError('Database is not open')
+        if (callback)
+          return callback(err)
+        throw err
       }
     }
 
   , batch: function (arr, options_, callback_) {
-      var callback = getCallback(options_, callback_)
-        , empty    = {}
-        , options, keyEncoding, valueEncoding, err
+      var callback, options, keyEncoding, valueEncoding, err
 
       if (!this.isOpen()) {
-        return this.once("ready", function () {
+        return this.once('ready', function () {
           this.batch(arr, options_, callback_)
         })
       }
 
+      callback = getCallback(options_, callback_)
+
+      if (this.isClosed()) {
+        err = new errors.WriteError('Database is not open')
+        if (callback)
+          return callback(err)
+        throw err
+      }
+
       options       = getOptions(options_, this._options)
       keyEncoding   = options.keyEncoding   || options.encoding
       valueEncoding = options.valueEncoding || options.encoding
@@ -199,7 +248,7 @@ LevelUP.prototype = {
             o.value = toBuffer(e.value, valueEncoding)
           return o
         }
-        return empty
+        return {}
       })
       this._db.batch(arr, options, function (err) {
         if (err) {
diff --git a/test/common.js b/test/common.js
index 527609a..d75b098 100644
--- a/test/common.js
+++ b/test/common.js
@@ -39,6 +39,7 @@ module.exports.openTestDatabase = function () {
   var options = typeof arguments[0] == 'object' ? arguments[0] : { createIfMissing: true, errorIfExists: true }
     , callback = typeof arguments[0] == 'function' ? arguments[0] : arguments[1]
     , location = typeof arguments[0] == 'string' ? arguments[0] : module.exports.nextLocation()
+
   rimraf(location, function (err) {
     refute(err)
     this.cleanupDirs.push(location)
diff --git a/test/deferred-open-test.js b/test/deferred-open-test.js
new file mode 100644
index 0000000..98f05c5
--- /dev/null
+++ b/test/deferred-open-test.js
@@ -0,0 +1,98 @@
+/* Copyright (c) 2012 Rod Vagg <@rvagg> */
+
+var buster  = require('buster')
+  , assert  = buster.assert
+  , levelup = require('../lib/levelup.js')
+  , async   = require('async')
+  , common  = require('./common')
+
+buster.testCase('Deferred open()', {
+    'setUp': common.commonSetUp
+  , 'tearDown': common.commonTearDown
+
+  , 'put() and get() on pre-opened database': function (done) {
+      var location = common.nextLocation()
+      // 1) open database without callback, opens in worker thread
+        , db       = levelup(location, { createIfMissing: true, errorIfExists: true, encoding: 'utf8' })
+
+      this.closeableDatabases.push(db)
+      this.cleanupDirs.push(location)
+      assert.isObject(db)
+      assert.equals(db._location, location)
+
+      async.parallel([
+      // 2) insert 3 values with put(), these should be deferred until the database is actually open
+          db.put.bind(db, 'k1', 'v1')
+        , db.put.bind(db, 'k2', 'v2')
+        , db.put.bind(db, 'k3', 'v3')
+      ], function () {
+      // 3) when the callbacks have returned, the database should be open and those values should be in
+      //    verify that the values are there
+        async.forEach(
+            [1,2,3]
+          , function (k, cb) {
+              db.get('k' + k, function (err, v) {
+                refute(err)
+                assert.equals(v, 'v' + k)
+                cb()
+              })
+            }
+            // sanity, this shouldn't exist
+          , function () {
+              db.get('k4', function (err) {
+                assert(err)
+                // DONE
+                done()
+              })
+            }
+        )
+      })
+
+      // we should still be in a state of limbo down here, not opened or closed, but 'new'
+      refute(db.isOpen())
+      refute(db.isClosed())
+    }
+
+  , 'batch() on pre-opened database': function (done) {
+      var location = common.nextLocation()
+      // 1) open database without callback, opens in worker thread
+        , db       = levelup(location, { createIfMissing: true, errorIfExists: true, encoding: 'utf8' })
+
+      this.closeableDatabases.push(db)
+      this.cleanupDirs.push(location)
+      assert.isObject(db)
+      assert.equals(db._location, location)
+
+      // 2) insert 3 values with batch(), these should be deferred until the database is actually open
+      db.batch([
+          { type: 'put', key: 'k1', value: 'v1' }
+        , { type: 'put', key: 'k2', value: 'v2' }
+        , { type: 'put', key: 'k3', value: 'v3' }
+      ], function () {
+      // 3) when the callbacks have returned, the database should be open and those values should be in
+      //    verify that the values are there
+        async.forEach(
+            [1,2,3]
+          , function (k, cb) {
+              db.get('k' + k, function (err, v) {
+                refute(err)
+                assert.equals(v, 'v' + k)
+                cb()
+              })
+            }
+            // sanity, this shouldn't exist
+          , function () {
+              db.get('k4', function (err) {
+                assert(err)
+                // DONE
+                done()
+              })
+            }
+        )
+      })
+
+      // we should still be in a state of limbo down here, not opened or closed, but 'new'
+      refute(db.isOpen())
+      refute(db.isClosed())
+    }
+})
\ No newline at end of file
diff --git a/test/read-stream-test.js b/test/read-stream-test.js
index 3b06d6c..87a671d 100644
--- a/test/read-stream-test.js
+++ b/test/read-stream-test.js
@@ -560,4 +560,40 @@ buster.testCase('ReadStream', {
 
       setup(delayed.delayed(reopen, 0.05))
     }
+
+
+    // this is just a fancy way of testing levelup('/path').readStream()
+    // i.e. not waiting for 'open' to complete
+    // the logic for this is inside the ReadStream constructor which waits for 'ready'
+  , 'test ReadStream on pre-opened db': function (done) {
+      var execute = function (db) {
+            // is in limbo
+            refute(db.isOpen())
+            refute(db.isClosed())
+
+            var rs = db.readStream()
+            assert.isFalse(rs.writable)
+            assert.isTrue(rs.readable)
+            rs.on('ready', this.readySpy)
+            rs.on('data' , this.dataSpy)
+            rs.on('data' , function () { console.log('data', arguments) })
+            rs.on('end'  , this.endSpy)
+            rs.on('close', this.verify.bind(this, rs, done))
+          }.bind(this)
+        , setup = function (db) {
+          console.log('opened')
+            db.batch(this.sourceData.slice(), function (err) {
+              console.log('batching',err)
+              refute(err)
+              db.close(function (err) {
+                console.log('closed', err)
+                refute(err)
+                var db2 = levelup(db._location, { createIfMissing: false, errorIfExists: false, encoding: 'utf8' })
+                execute(db2)
+              })
+            }.bind(this))
+          }.bind(this)
+
+      this.openTestDatabase(setup)
+    }
 })
\ No newline at end of file
diff --git a/test/simple-test.js b/test/simple-test.js
index 2b33d2d..a371ec3 100644
--- a/test/simple-test.js
+++ b/test/simple-test.js
@@ -20,7 +20,7 @@ buster.testCase('Basic API', {
 
   , 'default options': function (done) {
       var location = common.nextLocation()
-      var db = levelup(location, { createIfMissing: true, errorIfExists: true }, function (err, db) {
+      levelup(location, { createIfMissing: true, errorIfExists: true }, function (err, db) {
         assert.isTrue(db.isOpen())
         this.closeableDatabases.push(db)
         this.cleanupDirs.push(location)

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-leveldown.git



More information about the Pkg-javascript-commits mailing list