gridfs-locks
Advanced tools
Comparing version 1.1.1 to 1.1.2
@@ -0,1 +1,6 @@ | ||
### 1.1.2 | ||
- Revert completely to use of MongoDB 2.4.x only update modifiers to maintain compatibility with mongo 2.4.x | ||
- The more efficient MongoDB 2.6 version lives in the mongodb_2.6 branch for now until if can be cleanly included conditionally | ||
### 1.1.1 | ||
@@ -2,0 +7,0 @@ |
306
index.js
@@ -193,20 +193,19 @@ /*********************************************************************** | ||
if (self.lockType === 'r') { | ||
query = {files_id: self.fileId, read_locks: {$gt: 0}}, | ||
update = {$inc: {read_locks: -1}, $set: {meta: null}}; | ||
releaseReadLock(self); | ||
} else if (self.lockType[0] === 'w') { | ||
query = {files_id: self.fileId, write_lock: true}, | ||
update = {$set: {write_lock: false, expires: new Date(), meta: null}}; | ||
releaseWriteLock(self); | ||
} else { | ||
return emitError(self, "Lock.releaseLock invalid lockType."); | ||
} | ||
return self; // allow chaining | ||
}; | ||
var releaseWriteLock = function (self) { | ||
var query = {files_id: self.fileId, write_lock: true}, | ||
update = {$set: {write_lock: false, expires: new Date(never), meta: null}}; | ||
self.collection.findAndModify(query, [], update, {w: self.lockCollection.writeConcern, new: true}, function (err, doc) { | ||
if (err) { return emitError(self, err); } | ||
var lt = self.lockType; | ||
self.lockType = null; | ||
@@ -219,20 +218,51 @@ self.query = null; | ||
} | ||
self.emit('released', doc); | ||
}); | ||
} | ||
// This resets the expire time when there are no locks and this was a release of a read lock | ||
// This prevents a former long expire time from persisting into new read locks unnecessarily. | ||
// There is a small time window between the findAndModify above and the one below where a long expire time | ||
// may be inheritied by a new read lock, but the window should be short enough that this is tolerable | ||
// because the lock->unlock->lock order of operations could just as easily have been lock->lock->unlock | ||
if ((lt === 'r') && doc && (doc.read_locks == 0)) { | ||
query = {files_id: self.fileId, read_locks: 0, write_lock: false}; | ||
update = {$set: { expires: new Date() }}; | ||
var releaseReadLock = function (self) { | ||
var query = {files_id: self.fileId, read_locks: {$gt: 1}}, | ||
update = {$inc: {read_locks: -1}, $set: {meta: null}}; | ||
// Case for read_locks > 1 | ||
self.collection.findAndModify(query, [], update, {w: self.lockCollection.writeConcern, new: true}, function (err, doc) { | ||
if (err) { return emitError(self, err); } | ||
if (doc) { | ||
self.lockType = null; | ||
self.query = null; | ||
self.update = null; | ||
self.heldLock = null; | ||
return self.emit('released', doc); | ||
} else { | ||
query = {files_id: self.fileId, read_locks: 1}; | ||
update = {$set: {read_locks: 0, expires: new Date(never), meta: null}}; | ||
// Case for read_locks == 1 | ||
self.collection.findAndModify(query, [], update, {w: self.lockCollection.writeConcern, new: true}, function (err, doc) { | ||
if (err) { console.warn("Error returned from expiration time reset on release", err); } | ||
if (err) { return emitError(self, err); } | ||
if (doc) { | ||
self.lockType = null; | ||
self.query = null; | ||
self.update = null; | ||
self.heldLock = null; | ||
return self.emit('released', doc); | ||
} else { | ||
// If another readLock released between the above two findAndModify calls they can both fail... so keep trying. | ||
// console.log("Retrying to release read lock..."); | ||
// Avoid an infinite loop when lock document no longer exists | ||
self.collection.findOne({files_id: self.fileId, read_locks: {$gt: 0}}, function (err, doc) { | ||
if (err) { return emitError(self, err); } | ||
if (doc == null) { | ||
return emitError(self, "Lock.releaseLock Valid read Lock document not found in collection. " + JSON.stringify(self.heldLock)); | ||
} else { | ||
self.releaseLock(); | ||
} | ||
}); | ||
} | ||
}); | ||
} | ||
self.emit('released', doc); | ||
}); | ||
return self; // allow chaining | ||
}; | ||
@@ -264,13 +294,25 @@ | ||
self.collection.findAndModify({files_id: self.fileId}, | ||
self.collection.findAndModify({files_id: self.fileId, expires: { $lt: self.lockExpireTime }}, | ||
[], | ||
{$max: {expires: self.lockExpireTime}}, // don't clobber an already extended shared read lock | ||
{$set: {expires: self.lockExpireTime}}, | ||
{w: self.lockCollection.writeConcern, new: true}, | ||
function (err, doc) { | ||
if (err) { return emitError(self, err); } | ||
if (doc == null) { return emitError(self, "Lock.renewLock document not found in collection"); } | ||
self.heldLock = doc; | ||
self.expiresSoonTimeout = setTimeout(emitExpiresSoonEvent.bind(self, ''), 0.9*(self.lockExpireTime - new Date() - self.pollingInterval)); | ||
self.expiredTimeout = setTimeout(emitExpiredEvent.bind(self, ''), (self.lockExpireTime - new Date() - self.pollingInterval)); | ||
return self.emit('renewed', doc); | ||
if (doc == null) { | ||
self.collection.findOne({files_id: self.fileId}, function (err, doc) { | ||
if (err) { return emitError(self, err); } | ||
if (doc == null) { | ||
return emitError(self, "Lock.renewLock document not found in collection"); | ||
} | ||
self.heldLock = doc; | ||
self.expiresSoonTimeout = setTimeout(emitExpiresSoonEvent.bind(self, ''), 0.9*(self.lockExpireTime - new Date() - self.pollingInterval)); | ||
self.expiredTimeout = setTimeout(emitExpiredEvent.bind(self, ''), (self.lockExpireTime - new Date() - self.pollingInterval)); | ||
return self.emit('renewed', doc); | ||
}); | ||
} else { | ||
self.heldLock = doc; | ||
self.expiresSoonTimeout = setTimeout(emitExpiresSoonEvent.bind(self, ''), 0.9*(self.lockExpireTime - new Date() - self.pollingInterval)); | ||
self.expiredTimeout = setTimeout(emitExpiredEvent.bind(self, ''), (self.lockExpireTime - new Date() - self.pollingInterval)); | ||
return self.emit('renewed', doc); | ||
} | ||
}); | ||
@@ -307,5 +349,8 @@ return self; | ||
self.timeCreated = new Date(); | ||
self.lockType = 'r'; | ||
self.expired = false; | ||
timeoutReadLockQuery(self); | ||
initializeLockDoc(self, function (err, doc) { | ||
if (err) { return emitError(self, err); } | ||
self.lockType = 'r'; | ||
self.expired = false; | ||
timeoutReadLockQuery(self); | ||
}); | ||
return self; | ||
@@ -349,5 +394,8 @@ }; | ||
self.timeCreated = new Date(); | ||
self.lockType = 'w'; | ||
self.expired = false; | ||
timeoutWriteLockQuery(self, testingOptions); | ||
initializeLockDoc(self, function (err, doc) { | ||
if (err) { return emitError(self, err); } | ||
self.expired = false; | ||
self.lockType = 'w'; | ||
timeoutWriteLockQuery(self, testingOptions); | ||
}); | ||
return self; | ||
@@ -364,2 +412,19 @@ }; | ||
// Private function that ensures an initialized lock doc is in the database | ||
var initializeLockDoc = function (self, callback) { | ||
self.collection.findAndModify({files_id: self.fileId}, | ||
[], | ||
{$setOnInsert: {files_id: self.fileId, | ||
expires: self.timeCreated, | ||
read_locks: 0, | ||
write_lock: false, | ||
write_req: false, | ||
reads: 0, | ||
writes: 0, | ||
meta: null}}, | ||
{w: self.lockCollection.writeConcern, upsert: true, new: true}, | ||
callback); | ||
}; | ||
// Private functions that implement expiration events | ||
@@ -385,53 +450,54 @@ | ||
function gotLock(doc) { | ||
self.heldLock = doc; | ||
if (self.lockExpiration) { | ||
self.expiresSoonTimeout = setTimeout(emitExpiresSoonEvent.bind(self), | ||
0.9*(self.lockExpireTime - new Date().getTime() - self.pollingInterval)); | ||
self.expiredTimeout = setTimeout(emitExpiredEvent.bind(self), | ||
(self.lockExpireTime - new Date().getTime() - self.pollingInterval)); | ||
} | ||
return self.emit('locked', doc); | ||
}; | ||
options = options || {}; | ||
// Read locks can break write locks with write_req after more than one polling cycle | ||
now = new Date(); | ||
self.lockExpireTime = new Date(now.getTime() + (self.lockExpiration || never)); | ||
self.lockExpireTime = new Date(new Date().getTime() + (self.lockExpiration || never)); | ||
self.query = {files_id: self.fileId, | ||
$or: [{write_lock: false, write_req: false, read_locks: 0}, | ||
{write_lock: false, write_req: false, expires: {$lte: self.lockExpireTime}}, | ||
{expires: {$lt: new Date(new Date() - 2*self.pollingInterval)}}]}; | ||
self.update = {$inc: {read_locks: 1, reads: 1}, $set: {write_lock: false, write_req: false, expires: self.lockExpireTime, meta: self.metaData}}; | ||
self.query = { files_id: self.fileId, | ||
$or: [ | ||
{ write_lock: false, | ||
write_req: false }, | ||
{ write_lock: true, | ||
expires: { $lt: new Date(now - 2*self.pollingInterval) }} | ||
] | ||
}; | ||
self.update = { $inc: { read_locks: 1, | ||
reads: 1 }, | ||
$set: { write_lock: false, | ||
write_req: false, | ||
meta: self.metaData }, | ||
$max: { expires: self.lockExpireTime }, | ||
$setOnInsert: { | ||
files_id: self.fileId, | ||
writes: 0 } | ||
}; | ||
self.collection.findAndModify( | ||
self.query, | ||
self.collection.findAndModify(self.query, | ||
[], | ||
self.update, | ||
{ w: self.lockCollection.writeConcern, new: true, upsert: true }, | ||
{w: self.lockCollection.writeConcern, new: true}, | ||
function (err, doc) { | ||
// if (err) { console.log("ERROR:", err); } | ||
// | ||
// XXX: Handle unique index exception when simultaneous upserts collide... | ||
// | ||
if (err && ((err.name !== 'MongoError') || (err.lastErrorObject.code !== 11000))) { return emitError(self, err); } | ||
if (err) { return emitError(self, err); } | ||
if (!doc) { | ||
if (new Date().getTime() - self.timeCreated >= self.timeOut) { | ||
return self.emit('timed-out'); | ||
} | ||
return setTimeout(timeoutReadLockQuery, self.pollingInterval, self, options); | ||
// Try again without trying to update the expire time | ||
// console.log("Second try..."); | ||
self.lockExpireTime = new Date(new Date().getTime() + (self.lockExpiration || never)); | ||
self.query = {files_id: self.fileId, write_lock: false, write_req: false, expires: { $gt: self.lockExpireTime }}; | ||
self.update = {$inc: {read_locks: 1, reads: 1}, $set: {meta: self.metaData}}; | ||
self.collection.findAndModify(self.query, | ||
[], | ||
self.update, | ||
{w: self.lockCollection.writeConcern, new: true}, | ||
function (err, doc) { | ||
if (err) { return emitError(self, err); } | ||
if (!doc) { | ||
if(new Date().getTime() - self.timeCreated >= self.timeOut) { | ||
return self.emit('timed-out'); | ||
} | ||
return setTimeout(timeoutReadLockQuery, self.pollingInterval, self, options); | ||
} else { | ||
return gotLock(doc); | ||
} | ||
} | ||
); | ||
} else { | ||
self.heldLock = doc; | ||
if (self.lockExpiration) { | ||
self.expiresSoonTimeout = setTimeout(emitExpiresSoonEvent.bind(self), | ||
0.9*(self.lockExpireTime - new Date().getTime() - self.pollingInterval)); | ||
self.expiredTimeout = setTimeout(emitExpiredEvent.bind(self), | ||
(self.lockExpireTime - new Date().getTime() - self.pollingInterval)); | ||
} | ||
return self.emit('locked', doc); | ||
return gotLock(doc); | ||
} | ||
@@ -445,38 +511,16 @@ } | ||
var timeoutWriteLockQuery = function (self, options) { | ||
options = options || {}; | ||
now = new Date(); | ||
self.lockExpireTime = new Date(now.getTime() + (self.lockExpiration || never)); | ||
self.query = {files_id: self.fileId, | ||
$or: [{expires: {$lt: new Date()}, write_req: true}, | ||
{write_lock: false, read_locks: 0}]}; | ||
self.lockExpireTime = new Date(new Date().getTime() + (self.lockExpiration || never)); | ||
self.update = {$set: {write_lock: true, write_req: false, read_locks: 0, expires: self.lockExpireTime, meta: self.metaData}, $inc:{writes: 1}}; | ||
self.query = { files_id: self.fileId, | ||
$or: [ | ||
{ expires: { $lt: now }, | ||
write_req: true }, | ||
{ write_lock: false, | ||
read_locks: 0 } | ||
] | ||
}; | ||
self.update = { $set: { write_lock: true, | ||
write_req: false, | ||
read_locks: 0, | ||
expires: self.lockExpireTime, | ||
meta: self.metaData}, | ||
$inc: { writes: 1 }, | ||
$setOnInsert: { | ||
files_id: self.fileId, | ||
reads: 0 } | ||
}; | ||
self.collection.findAndModify(self.query, | ||
[], | ||
self.update, | ||
{w: self.lockCollection.writeConcern, new: true, upsert: true}, | ||
{w: self.lockCollection.writeConcern, new: true}, | ||
function (err, doc) { | ||
// if (err) { console.log("ERROR:", err); } | ||
// | ||
// XXX: Handle unique index exception when simultaneous upserts collide... | ||
// | ||
if (err && ((err.name !== 'MongoError') || (err.lastErrorObject.code !== 11000))) { return emitError(self, err); } | ||
if (err) { return emitError(self, err); } | ||
if (doc) { | ||
@@ -491,30 +535,28 @@ self.heldLock = doc; | ||
return self.emit('locked', doc); | ||
} else { // !doc | ||
if (new Date().getTime() - self.timeCreated >= self.timeOut) { | ||
// Clear the write_req flag, since this obtainWriteLock has timed out | ||
self.collection.findAndModify({files_id: self.fileId, write_req: true}, | ||
[], | ||
{$set: {write_req: false}}, | ||
{w: self.lockCollection.writeConcern, new: true}, | ||
function (err, doc) { | ||
if (err) { return emitError(self, err); } | ||
} | ||
); | ||
return self.emit('timed-out'); | ||
} else { | ||
// write_req gets set every time because claimed write locks and timed out write requests clear it | ||
self.collection.findAndModify({files_id: self.fileId, write_req: false}, | ||
[], | ||
{$set: {write_req: true}}, | ||
{new: true}, | ||
function (err, doc) { | ||
if (err) { return emitError(self, err); } | ||
self.emit('write-req-set'); | ||
}); | ||
return setTimeout(timeoutWriteLockQuery, self.pollingInterval, self, options); | ||
} | ||
} | ||
if (new Date().getTime() - self.timeCreated >= self.timeOut) { | ||
// Clear the write_req flag, since this obtainWriteLock has timed out | ||
self.collection.findAndModify({files_id: self.fileId, write_req: true}, | ||
[], | ||
{$set: {write_req: false}}, | ||
{w: self.lockCollection.writeConcern, new: true}, | ||
function (err, doc) { | ||
if (err) { return emitError(self, err); } | ||
} | ||
); | ||
return self.emit('timed-out'); | ||
} else { | ||
// write_req gets set every time because claimed write locks and timed out write requests clear it | ||
self.collection.findAndModify({files_id: self.fileId, write_req: false}, | ||
[], | ||
{$set: {write_req: true}}, | ||
{new: true}, | ||
function (err, doc) { | ||
if (err) { return emitError(self, err); } | ||
self.emit('write-req-set'); | ||
}); | ||
return setTimeout(timeoutWriteLockQuery, self.pollingInterval, self, options); | ||
} | ||
} | ||
); | ||
}; |
{ | ||
"name": "gridfs-locks", | ||
"version": "1.1.1", | ||
"version": "1.1.2", | ||
"description": "Distributed read/write locking based on MongoDB, designed to make GridFS safe for concurrent access", | ||
@@ -8,3 +8,3 @@ "main": "index.js", | ||
"devDependencies": { | ||
"mongodb": ">=1.4.0", | ||
"mongodb": ">=1.4.2", | ||
"coffee-script": "*", | ||
@@ -11,0 +11,0 @@ "mocha": "*" |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
74570
482