GitXplorerGitXplorer
A

level-lock

public
0 stars
0 forks
0 issues

Commits

List of commits on branch master.
Unverified
4905af02218fec1ce7129738d4faa1fa225afb6f

added a test for rvagg/level-spaces

AArtskydJ committed 10 years ago
Unverified
f7855261ea24ddd742a2ba4e001adcd36fe28214

1.0.1

iinvalid-email-address committed 10 years ago
Unverified
030edca225782387ba6983986e3d7d3b85e87061

unlock on exists

iinvalid-email-address committed 10 years ago
Unverified
c56373cad8c85a74109c301ae9fed38afc4f681c

badge

iinvalid-email-address committed 10 years ago
Unverified
79ff7c6c563e9c5487a8202d86482843c48943c4

package.json etc

iinvalid-email-address committed 10 years ago
Unverified
6d78096453e4b022c4cffd30c30f104b49d94f4c

passing bytewise test

iinvalid-email-address committed 10 years ago

README

The README file for this repository.

level-lock

in-memory advisory read/write locks for leveldb keys

build status

example

A very common use-case for locking is to prevent race conditions when checking to see if a username has been taken.

In a naive solution, a get() followed by a put() runs the risk that 2 requests might come in at roughly the same time and that both calls to get() could finish before either call to put(), resulting in 2 calls to put() and leaving the database in an inconsistent state.

However, if we obtain a write lock on a key before checking for the existence of that key with get() and then only release the lock after the put() has completed, the sequence of operations can be safely performed.

Here is an example:

var level = require('level');
var db = level('/tmp/users.db', { valueEncoding: 'json' });
var lock = require('level-lock');

var username = process.argv[2];
var key = 'users!' + username;
var userdata = { bio: 'beep boop' };

var unlock = lock(db, key, 'w');
if (!unlock) return exit(1, 'locked');
 
db.get(key, function (err, value) {
    if (value) {
        unlock();
        return exit(1, 'that username already exists');
    }
    
    db.put('users!substack', userdata, function (err) {
        unlock();
        if (err) return exit(1, err);
        console.log('created user ' + username);
    });
});

function exit (code, err) {
    console.error(err);
    process.exit(code);
}

To drive the point further home, here is code that concretely demonstrates the problem:

var level = require('level');
var db = level('/tmp/race.db', { valueEncoding: 'json' });
var lock = require('level-lock');

var name = process.argv[2];
var data = { bio: 'beep boop' };

for (var i = 0; i < 3; i++) (function (i) {
    create(name, data, function (err) {
        console.error(i + ' create: ' + (err || 'ok'));
    });
})(i);

function create (name, data, cb) {
    var key = 'users!' + name;
    
    //var unlock = lock(db, key, 'w');
    //if (!unlock) return cb(new Error('locked'));
    
    db.get(key, function (err, value) {
        if (value) return cb(new Error('that username already exists'));
        
        db.put('users!substack', data, function (err) {
            //unlock();
            cb(err);
        });
    });
}

If we run this program, then the user substack is created 3 separate times, subverting our check to see if a username already exists:

$ node race.js substack
0 create: ok
1 create: ok
2 create: ok

However, if the locking code is un-commented, then a user is only created once:

$ node race.js substack
1 create: Error: locked
2 create: Error: locked
0 create: ok

Note however that just like the unix system call flock(2), these locks are merely advisory so code that does not check for locks can still cause consistency problems.

methods

var lock = require('level-lock')

lock(db, key, mode='w')

Create a lock on a key with a mode.

mode can be 'r', 'w', or 'rw'.

The keyEncoding of db will be respected when setting a lock on a key.

Locks are stored in-memory on the db object under db._locks.

install

With npm do:

npm install level-lock

license

MIT