GitXplorerGitXplorer
A

level-lock

public
0 stars
0 forks
0 issues

Commits

List of commits on branch master.
Unverified
b260ba5d538c807ed3e560f696893826f877ffc4

passing test

iinvalid-email-address committed 10 years ago
Unverified
84abd2bc3a99c114b828c67b76fdbe8d549b1c51

docs

iinvalid-email-address committed 10 years ago
Unverified
700c5fa4a99e30f73d0189fe4d5398179a73e3e1

race condition example

iinvalid-email-address committed 10 years ago
Unverified
4c685742656404c761cbbc77b3eec8f58a698375

working example

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