UNPKG

semlocks

Version:

Mind-blowingly simple local resource management

520 lines (517 loc) 13.2 kB
/* * Semlocks * Copyright 2014 Tom Frost */ var should = require('should'), semlocks = require('../lib/Semlocks'), inst; describe("Semaphore", function() { beforeEach(function() { inst = new semlocks.Semlocks(); }); it("should only assign one lock at a time", function(done) { var locked = false, firstHit = false; inst.acquire('test', function(err, release) { should.not.exist(err); locked.should.equal(false); locked = true; firstHit.should.equal(false); firstHit = true; inst.acquire('test', function(err) { should.not.exist(err); locked.should.equal(false); firstHit.should.equal(true); done(); }); setTimeout(function() { release(); locked = false; }, 5); }); }); it("should allow sems to be capped higher than 1", function(done) { var locks = 0; inst.setMaxLocks('foo', 2); inst.acquire('foo', function(err, release) { should.not.exist(err); locks++; locks.should.equal(1); inst.acquire('foo', function(err) { should.not.exist(err); locks++; locks.should.equal(2); inst.acquire('foo', function(err) { should.not.exist(err); locks++; locks.should.equal(2); done(); }); }); setTimeout(function() { release(); locks--; }, 1); }); }); it("should allow caps to be cleared", function(done) { var locked = false; inst.setMaxLocks('foo', 2); inst.acquire('foo', function(err, release) { should.not.exist(err); locked = true; inst.setMaxLocks('foo', null); inst.acquire('foo', function(err) { should.not.exist(err); locked.should.equal(false); done(); }); setTimeout(function() { locked = false; release(); }, 5); }); }); it("should allow caps to be set at 0", function(done) { inst.setMaxLocks('foo', 0); inst.acquire('foo', {instant: true}, function(err) { should.exist(err); done(); }); }); it("should allow the cap to be retrieved", function() { inst.setMaxLocks('foo', 5); inst.getMaxLocks('foo').should.equal(5); inst.getMaxLocks('bar').should.equal(1); }); it("should acquire arrays of sems", function(done) { var foo = false, bar = false; inst.acquire(['foo', 'bar'], function(err, release) { should.not.exist(err); inst.acquire('foo', function(err, release) { should.not.exist(err); foo = true; release(); }); inst.acquire('bar', function(err, release) { should.not.exist(err); bar = true; release(); }); inst.acquire(['foo', 'bar'], function(err) { should.not.exist(err); foo.should.equal(true); bar.should.equal(true); done(); }); setTimeout(release, 5); }); }); it("should allow sems to be acquired instantly only", function(done) { inst.acquire('foo', {instant: true}, function(err) { should.not.exist(err); done(); }); }); it("should send error when sems can't acquire instantly", function(done) { var opts = {instant: true}; inst.acquire('foo', opts, function(err, release) { should.not.exist(err); inst.acquire('foo', opts, function(err) { should.exist(err); release(); done(); }); }); }); it("should send error for instant when in the same tick", function(done) { inst.acquire('foo', function(err) { should.not.exist(err); }); inst.acquire('foo', {instant: true}, function(err) { should.exist(err); done(); }); }); it("should return a handle to cancel the request", function(done) { var hit = false; inst.acquire('foo', function(err, release) { should.not.exist(err); var handle = inst.acquire('foo', function() { hit = true; }); should.exist(handle); inst.cancel(handle); release(); setTimeout(function() { hit.should.equal(false); done(); }, 5); }); }); it("should pass an error when canceling with an error", function(done) { var hit = false; inst.acquire('foo', function(err, release) { should.not.exist(err); var handle = inst.acquire('foo', function(err) { should.exist(err); err.should.have.property('message').equal('bar'); hit = true; }); should.exist(handle); inst.cancel(handle, new Error('bar')); release(); setTimeout(function() { hit.should.equal(true); done(); }, 5); }); }); it("should not pass a cancel error if cb was called", function(done) { var hits = 0; inst.acquire('foo', function(err, release) { should.not.exist(err); var handle = inst.acquire('foo', function(err, release) { should.not.exist(err); hits++; setTimeout(release, 10); }); should.exist(handle); release(); setTimeout(function() { inst.cancel(handle, new Error('bar')); }, 5); setTimeout(function() { hits.should.equal(1); done(); }, 15); }); }); it("should release all locks when canceling", function(done) { var hit = false; inst.acquire('foo', function(err, release) { should.not.exist(err); var handle = inst.acquire(['foo', 'bar'], function() { hit = true; }); inst.acquire(['foo', 'bar'], function(err) { should.not.exist(err); hit.should.equal(false); done(); }); inst.cancel(handle); release(); }); }); it("should work correctly with a high wait time", function(done) { var hit = false; inst.acquire('foo', function(err, release) { should.not.exist(err); inst.acquire('foo', {wait: 10}, function(err) { should.not.exist(err); hit = true; }); setTimeout(release, 5); setTimeout(function() { hit.should.equal(true); done(); }, 15); }); }); it("should fail when wait time is exceeded", function(done) { var hit = false; inst.acquire('foo', function(err, release) { should.not.exist(err); inst.acquire('foo', {wait: 5}, function(err) { should.exist(err); hit = true; }); setTimeout(function() { hit.should.equal(true); release(); done(); }, 10); }); }); it("should work correctly with a high ttl", function(done) { var released = false; inst.acquire('foo', {ttl: 1000}, function(err, release) { should.not.exist(err); inst.acquire('foo', function(err) { should.not.exist(err); released.should.equal(true); done(); }); setTimeout(function() { released = true; release(); }, 5); }); }); it("should auto-release when ttl is reached", function(done) { var hit = false; inst.acquire('foo', {ttl: 5}, function(err) { should.not.exist(err); inst.acquire('foo', function(err) { should.not.exist(err); hit = true; }); setTimeout(function() { hit.should.equal(true); done(); }, 10); }); }); it("should allow sems to be released individually", function(done) { var hitFoo = false, hitBar = false; inst.acquire(['foo', 'bar'], function(err, release) { should.not.exist(err); inst.acquire('foo', function(err) { should.not.exist(err); hitFoo = true; }); inst.acquire('bar', function() { hitBar = true; }); release('foo'); setTimeout(function() { hitFoo.should.equal(true); hitBar.should.equal(false); done(); }, 5); }); }); it("should allow sems to be released externally", function(done) { var firstHit = false; var firstHandle = inst.acquire('foo', function(err) { should.not.exist(err); firstHit.should.equal(false); firstHit = true; }); inst.acquire('foo', function(err, release) { should.not.exist(err); firstHit.should.equal(true); release(); done(); }); setTimeout(inst.release.bind(inst, firstHandle, 'foo'), 10); }); it("should emit acquire and release events appropriately", function(done) { var acquires = 0, releases = 0; inst.on('acquire:foo', function(handle) { should.exist(handle); acquires++; }); inst.on('acquire', function(sem, handle) { should.exist(sem); should.exist(handle); if (sem == 'foo') acquires++; }); inst.on('release:foo', function(handle) { should.exist(handle); releases++; }); inst.on('release', function(sem, handle) { should.exist(sem); should.exist(handle); if (sem == 'foo') releases++; }); inst.acquire(['foo', 'bar'], function(err, release) { should.not.exist(err); acquires.should.equal(2); releases.should.equal(0); release(); }); setTimeout(function() { acquires.should.equal(2); releases.should.equal(2); done(); }, 5); }); it("should emit killed event when ttl expires", function(done) { var killed = false; var handle = inst.acquire('foo', {ttl: 5}, function(err, release) { should.not.exist(err); killed.should.equal(false); setTimeout(function() { killed.should.equal(handle); release(); done(); }, 10); }); inst.on('killed', function(killHandle) { killed = killHandle; }); }); it("should award locks to earlier priorities first", function(done) { var hits = 0; inst.acquire('foo', function(err, release) { should.not.exist(err); inst.acquire('foo', {priority: 3}, function(err, release) { should.not.exist(err); hits.should.equal(3); hits++; release(); done(); }); inst.acquire('foo', {priority: 2}, function(err, release) { should.not.exist(err); hits.should.equal(1); hits++; release(); }); inst.acquire('foo', {priority: 1}, function(err, release) { should.not.exist(err); hits.should.equal(0); hits++; release(); }); inst.acquire('foo', {priority: 2}, function(err, release) { should.not.exist(err); hits.should.equal(2); hits++; release(); }); release(); }); }); it("should allow locks to be acquired without a callback", function(done) { var handle = inst.acquire('foo'), called = false; inst.acquire('foo', function(err, release) { should.not.exist(err); called = true; release(); }); setImmediate(function() { called.should.equal(false); inst.release(handle); setImmediate(function() { called.should.equal(true); done(); }); }); }); it("should wrap handle IDs to 0 when the limit is reached", function() { inst._curId = semlocks.HANDLE_LIMIT; var handle = inst.acquire('foo'); handle.should.equal(0); }); it("should forcibly release all held locks", function(done) { var locks = 0; inst.setMaxLocks('foo', 2); for (var i = 0; i < 5; i++) inst.acquire('foo', function() { locks++; }); setImmediate(function() { locks.should.equal(2); inst.forceRelease('foo'); setImmediate(function() { locks.should.equal(4); inst.forceRelease('foo'); setImmediate(function() { locks.should.equal(5); done(); }); }); }); }); it("should grant locks immediately upon raising max", function(done) { var locks = 0; inst.setMaxLocks('foo', 0); for (var i = 0; i < 3; i++) inst.acquire('foo', function() { locks++; }); setImmediate(function() { locks.should.equal(0); inst.setMaxLocks('foo', 2); setImmediate(function() { locks.should.equal(2); done(); }); }); }); it("should report currently held locks", function(done) { inst.setMaxLocks('foo', 0); inst.setMaxLocks('baz', 2); inst.acquire('foo'); inst.acquire(['bar', 'baz']); inst.acquire('baz'); inst.acquire('tek', function(err, release) { should.not.exist(err); release(); setImmediate(function() { var report = inst.getLocks(); report.should.eql({ bar: 1, baz: 2 }); done(); }); }); }); it("should allow the default max locks to be changed", function(done) { inst.setDefaultMaxLocks(2); var locks = 0; for (var i = 0; i < 3; i++) inst.acquire('foo', function() { locks++; }); setImmediate(function() { locks.should.eql(2); done(); }); }); it("should restore default max of 1 when passing null", function(done) { inst.setDefaultMaxLocks(5); inst.setDefaultMaxLocks(null); var locks = 0; for (var i = 0; i < 3; i++) inst.acquire('foo', function() { locks++; }); setImmediate(function() { locks.should.eql(1); done(); }); }); it("should grant locks upon raising default max", function(done) { var locks = 0; for (var i = 0; i < 3; i++) inst.acquire('foo', function() { locks++; }); setImmediate(function() { locks.should.eql(1); inst.setDefaultMaxLocks(2); setImmediate(function() { locks.should.eql(2); done(); }); }); }); it("should not allow default caps to affect sem caps", function(done) { var locks = []; inst.setDefaultMaxLocks(0); inst.setMaxLocks('foo', 1); inst.acquire('foo', function() { locks.push('foo'); }); inst.acquire('bar', function() { locks.push('bar'); }); setImmediate(function() { locks.should.eql(['foo']); done(); }); }); it("should switch sem out of default cap when cap is set", function(done) { var locks = 0; inst.setDefaultMaxLocks(0); inst.acquire('foo', function() { locks++; }); setImmediate(function() { locks.should.eql(0); inst.setMaxLocks('foo', 1); setImmediate(function() { locks.should.eql(1); done(); }); }); }); it("should return the default max locks in getMaxLocks()", function() { inst.getMaxLocks().should.eql(1); inst.setDefaultMaxLocks(5); inst.getMaxLocks().should.eql(5); }); });