Last active
November 22, 2023 19:11
-
-
Save Munawwar/85e204b579f9b8b13a496f72437ba928 to your computer and use it in GitHub Desktop.
Distributed Lock with Dynamodb & node.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// MIT License 2021 - https://opensource.org/licenses/MIT | |
const delay = require('delay'); | |
/** | |
* @param {AWS.DynamoDB.DocumentClient} dynamodbDocumentClient | |
* @param {string} tableName | |
* @param {string} ownerId a lock's gotta have an owner! | |
* @param {Object} [options] | |
* @param {string} [options.hashKeyProperty='key'] default value is 'key' | |
* @param {string} [options.hashKeyValue='lock'] default value is 'lock' | |
* @param {string} [options.ownerIdProperty='owner'] default value is 'owner' | |
* @param {number} [options.retryDelay=200] default value is 200ms | |
* @returns {Promise<() => Promise<boolean>} returns an 'unlock' function | |
*/ | |
async function dynamoLock( | |
dynamodbDocumentClient, | |
tableName, | |
ownerId, | |
{ | |
retryDelay = 200, | |
hashKeyProperty = 'key', | |
hashKeyValue = 'lock', | |
ownerIdProperty = 'owner', | |
} = {}, | |
) { | |
if (!ownerId) throw new Error('dynamoLock call needs an owner id to reserve a lock.'); | |
let unlock; | |
while (!unlock) { | |
try { | |
await dynamodbDocumentClient.put({ | |
TableName: tableName, | |
Item: { | |
[hashKeyProperty]: hashKeyValue, | |
[ownerIdProperty]: ownerId, | |
}, | |
ConditionExpression: '#ownerIdProperty = :empty OR attribute_not_exists(#ownerIdProperty)', | |
ExpressionAttributeNames: { | |
"#ownerIdProperty": ownerIdProperty | |
}, | |
ExpressionAttributeValues: { | |
':empty': '', | |
}, | |
}).promise(); | |
unlock = async () => { | |
try { | |
await dynamodbDocumentClient.put({ | |
TableName: tableName, | |
Item: { | |
[hashKeyProperty]: hashKeyValue, | |
[ownerIdProperty]: '', | |
}, | |
ConditionExpression: `#ownerIdProperty = :ownerId`, | |
ExpressionAttributeNames: { | |
"#ownerIdProperty": ownerIdProperty | |
}, | |
ExpressionAttributeValues: { | |
':ownerId': ownerId, | |
}, | |
}).promise(); | |
return true; | |
} catch (err) { | |
// could not unlock? deadlock? you will need to delete the lock manually from db | |
return false; | |
} | |
}; | |
} catch (err) { | |
console.log(err); | |
await delay(retryDelay); | |
} | |
} | |
return unlock; | |
} | |
module.exports = dynamoLock; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const AWS = require('aws-sdk'); | |
const dynamodbClient = new AWS.DynamoDB.DocumentClient(); | |
const dynamoLock = require('./dynamo-lock'); | |
(async () => { | |
// process 1 | |
const process1Id = 'lambda1'; | |
const p1 = dynamoLock(dynamodbClient, 'Test', process1Id) | |
.then(async (unlock) => { | |
console.log('process 1 has got the lock. now unlocking...'); | |
await unlock(); | |
console.log('unlocked'); | |
}); | |
// process 2 | |
const process2Id = 'lambda2'; | |
const p2 = dynamoLock(dynamodbClient, 'Test', process2Id) | |
.then(async (unlock) => { | |
console.log('process 2 has got the lock. now unlocking...'); | |
await unlock(); | |
console.log('unlocked'); | |
}); | |
await Promise.all([p1, p2]); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
A day comes when your lock doesn't get released (because of server crashing or whatever reason). So this solution is not good enough, without a TTL.