├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── index.js ├── package.json ├── promise.js └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '10' 4 | - '12' 5 | - '14' 6 | cache: 7 | npm: false 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Mathias Buus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mutexify 2 | 3 | Bike shed mutex lock implementation in node.js 4 | 5 | ``` 6 | npm install mutexify 7 | ``` 8 | 9 | [![build status](http://img.shields.io/travis/mafintosh/mutexify.svg?style=flat)](http://travis-ci.org/mafintosh/mutexify) 10 | 11 | Hasn't this been done before? Yes, but the specific semantics of this made some of my code simpler. 12 | 13 | ## Usage 14 | 15 | 16 | ``` js 17 | var mutexify = require('mutexify') 18 | var lock = mutexify() 19 | 20 | lock(function(release) { 21 | console.log('i am now locked') 22 | setTimeout(function() { 23 | release() 24 | }, 1000) 25 | }) 26 | 27 | lock(function(release) { 28 | console.log('1 second later') 29 | release() 30 | }) 31 | ``` 32 | 33 | A common pattern is to call a callback after you release the lock. 34 | To do this in a one-liner pass the callback and the value to `release(cb, err, value)` 35 | 36 | ``` js 37 | var write = function(data, cb) { 38 | lock(function(release) { 39 | fs.writeFile('locked-file.txt', data, release.bind(null, cb)) 40 | }) 41 | } 42 | ``` 43 | 44 | `mutexify` guarantees that the order that a mutex was requested in is the order that access will be given. 45 | 46 | You can read the lock's current state on the `lock.locked` property. 47 | 48 | ### Usage with promises 49 | 50 | `mutexify` provides a Promise-based alternative. 51 | 52 | ```js 53 | const mutexify = require('mutexify/promise') 54 | 55 | ;(async () => { 56 | var lock = mutexify() 57 | 58 | var release = await lock() 59 | console.log('i am now locked') 60 | setTimeout(function () { 61 | release() 62 | }, 1000) 63 | 64 | release = await lock() 65 | console.log('1 second later') 66 | release() 67 | })() 68 | ``` 69 | 70 | ## License 71 | 72 | MIT 73 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var queueTick = require('queue-tick') 2 | 3 | var mutexify = function () { 4 | var queue = [] 5 | var used = null 6 | 7 | var call = function () { 8 | used(release) 9 | } 10 | 11 | var acquire = function (fn) { 12 | if (used) return queue.push(fn) 13 | used = fn 14 | acquire.locked = true 15 | queueTick(call) 16 | return 0 17 | } 18 | 19 | acquire.locked = false 20 | 21 | var release = function (fn, err, value) { 22 | used = null 23 | acquire.locked = false 24 | if (queue.length) acquire(queue.shift()) 25 | if (fn) fn(err, value) 26 | } 27 | 28 | return acquire 29 | } 30 | 31 | module.exports = mutexify 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mutexify", 3 | "version": "1.4.0", 4 | "description": "mutex lock for javascript", 5 | "main": "index.js", 6 | "dependencies": { 7 | "queue-tick": "^1.0.0" 8 | }, 9 | "devDependencies": { 10 | "standard": "^14.3.3", 11 | "tape": "^3.0.2" 12 | }, 13 | "scripts": { 14 | "test": "tape test.js", 15 | "posttest": "npm run lint", 16 | "lint": "standard" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/mafintosh/mutexify.git" 21 | }, 22 | "author": "Mathias Buus (@mafintosh)", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/mafintosh/mutexify/issues" 26 | }, 27 | "keywords": [ 28 | "mutex", 29 | "lock" 30 | ], 31 | "homepage": "https://github.com/mafintosh/mutexify" 32 | } 33 | -------------------------------------------------------------------------------- /promise.js: -------------------------------------------------------------------------------- 1 | var mutexify = require('.') 2 | 3 | var mutexifyPromise = function () { 4 | var lock = mutexify() 5 | 6 | var acquire = function () { 7 | return new Promise(lock) 8 | } 9 | 10 | Object.defineProperty(acquire, 'locked', { 11 | get: function () { return lock.locked }, 12 | enumerable: true 13 | }) 14 | 15 | return acquire 16 | } 17 | 18 | module.exports = mutexifyPromise 19 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape') 2 | var mutexify = require('./') 3 | var mutexifyPromise = require('./promise') 4 | 5 | tape('locks', function (t) { 6 | t.plan(21) 7 | 8 | var lock = mutexify() 9 | var used = false 10 | t.ok(!lock.locked, 'not locked') 11 | 12 | for (var i = 0; i < 10; i++) { 13 | lock(function (release) { 14 | t.ok(!used, 'one at the time') 15 | t.ok(lock.locked, 'locked') 16 | used = true 17 | setImmediate(function () { 18 | used = false 19 | release() 20 | }) 21 | }) 22 | } 23 | }) 24 | 25 | tape('calls callback', function (t) { 26 | var lock = mutexify() 27 | 28 | var cb = function (err, value) { 29 | t.same(err, null) 30 | t.same(value, 'hello world') 31 | t.end() 32 | } 33 | 34 | lock(function (release) { 35 | release(cb, null, 'hello world') 36 | }) 37 | }) 38 | 39 | tape('calls the locking callbacks in a different stack', function (t) { 40 | t.plan(2) 41 | 42 | var lock = mutexify() 43 | 44 | var topScopeFinished = false 45 | var secondScopeFinished = false 46 | 47 | lock(function (release) { 48 | t.ok(topScopeFinished, 'the test function has already finished running') 49 | release() 50 | secondScopeFinished = true 51 | }) 52 | 53 | lock(function (release) { 54 | t.ok(secondScopeFinished, 'the last lock\'s call stack is done') 55 | release() 56 | t.end() 57 | }) 58 | 59 | topScopeFinished = true 60 | }) 61 | 62 | tape('locks with promises', async function (t) { 63 | t.plan(21) 64 | 65 | var lock = mutexifyPromise() 66 | var used = false 67 | t.ok(!lock.locked, 'not locked') 68 | for (var i = 0; i < 10; i++) { 69 | var release = await lock() 70 | t.ok(!used, 'one at the time') 71 | t.ok(lock.locked, 'locked') 72 | used = true 73 | setImmediate(function () { 74 | used = false 75 | release() 76 | }) 77 | } 78 | }) 79 | --------------------------------------------------------------------------------