UNPKG

@luminati-io/locker

Version:

lock server written in node.js

236 lines (208 loc) 7.58 kB
(function(module) { var net = require("net"), fs = require("fs"), Lock = require("./lib/Lock"), LockQueueManager = require("./lib/LockQueueManager"), LockAction = require("./lib/LockAction"); function releaseLocks(locksRegistry) { Object.keys(locksRegistry).forEach(function(key) { locksRegistry[key].lock.release(); }); }; function connName(conn) { return conn.remoteAddress+':'+conn.remotePort; } function Locker(opt) { var manager = new LockQueueManager(); this.conns = {}; this.opt = opt||{}; var _this = this; if (opt.dbfile) { try { this.db = JSON.parse(fs.readFileSync(opt.dbfile).toString()); } catch(e) { this.db = undefined; } } this.server = net.createServer(function(connection) { var locksRegistry = {}, conn_name = connName(connection), data = new Buffer(0), temp = new Buffer(0); _this.conns[conn_name] = { locksRegistry, ip: connection.remoteAddress, pid: 0, } var closing = false; connection.on("error", function(error) { connection.end(); closing = true; releaseLocks(locksRegistry); delete _this.conns[conn_name]; _this.save(); }); connection.on("end", function() { closing = true; releaseLocks(locksRegistry); delete _this.conns[conn_name]; _this.save(); }); connection.on("timeout", function() { closing = true; releaseLocks(locksRegistry); delete _this.conns[conn_name]; _this.save(); }); connection.on("data", function(part) { var sequence, wait, timeout, action, length, name; temp = new Buffer(data.length + part.length); data.copy(temp, 0); part.copy(temp, data.length); data = new Buffer(temp); while (data.length && ((data[0] + 14) <= data.length)) { length = data[0]; sequence = data.readUInt32LE(1); wait = data.readUInt32LE(5); timeout = data.readUInt32LE(9); action = data[13]; name = data.slice(14, length + 14).toString(); switch (action) { case LockAction.ACTION_INIT: _this.conns[conn_name].pid = sequence; break; case LockAction.ACTION_CONT: _this.conns[conn_name].pid = sequence; var db = _this.load(connection.remoteAddress, sequence); if (db) { let acquired = db.filter(l=>!!l.acquired); let waiting = db.filter(l=>!l.acquired); for (let l of acquired) lock(l.name, l.sequence, l.wait, l.timeout, {skip_respond: true}); for (let l of waiting) lock(l.name, l.sequence, l.wait, l.timeout); } break; case LockAction.ACTION_LOCK: lock(name, sequence, wait, timeout); break; case LockAction.ACTION_UNLOCK: unlock(sequence); break; } _this.save(); data = data.slice(length + 14); } }); function lock(name, sequence, wait, timeout, opt) { var lock = new Lock(name, manager); var opt = opt||{}; locksRegistry[sequence] = { lock: lock, name: name, sequence: sequence, wait: wait, timeout: timeout, request: Date.now(), }; lock.acquire(wait, timeout, function(error) { if (error) locksRegistry[sequence].error = error; else locksRegistry[sequence].acquired = Date.now(); if (!opt.skip_respond) respond(sequence, LockAction.ACTION_LOCK, error ? 0 : 1); }); }; function unlock(sequence) { var lock = locksRegistry[sequence]; if (lock) { delete locksRegistry[sequence]; } respond(sequence, LockAction.ACTION_UNLOCK, lock && lock.lock.release()); }; function respond(sequence, action, result) { if (closing) return; /* socket already closed */ var response = new Buffer(6); response.writeUInt32LE(sequence, 0); response[4] = action; response[5] = result ? 1 : 0; connection.write(response, "binary"); }; }); }; Locker.prototype.listen = function() { this.server.listen.apply(this.server, arguments); }; Locker.prototype.lockStatus = function() { var now = new Date(); var stat = {}; var _this = this; Object.keys(this.conns).forEach(function(c){ var registry = _this.conns[c].locksRegistry; stat[c] = Object.keys(registry).map(function(key){ var _lock = registry[key]; var res = {lock: _lock.lock.getName(), request: now - _lock.request}; if (_lock.acquired) res.acquired = now - _lock.acquired; if (_lock.error) res.error = _lock.error; return res; }); }); return stat; }; Locker.prototype.close = function() { this.server.close.apply(this.server, arguments); }; Locker.prototype.load = function(ip, pid) { if (!this.db || !pid) return; var locks = this.db[ip+'/'+pid]; if (!locks) return; var now = Date.now(); return locks.map(l=>{ l.wait = (l.request+l.wait)-now; l.timeout = (l.request+l.timeout)-now; if (l.wait<0) l.wait = 0; if (l.timeout<0) l.timeout = 0; return l; }); } Locker.prototype.save = function() { if (!this.opt.dbfile) return; var db = {}; var _this = this; Object.keys(this.conns).forEach(function(c){ var key = _this.conns[c].ip+'/'+_this.conns[c].pid; var registry = _this.conns[c].locksRegistry; db[key] = Object.keys(registry).filter(k=>!registry[k].error).map(k=>{ var _lock = registry[k]; var res = { name: _lock.name, sequence: _lock.sequence, wait: _lock.wait, timeout: _lock.timeout, request: _lock.request, acquired: _lock.acquired, }; return res; }); }); fs.writeFileSync(this.opt.dbfile, JSON.stringify(db)); }; module.exports = Locker; })(module);