syncguard
Version:
Functional TypeScript library for distributed locking across microservices. Prevents race conditions with Redis, Firestore, and custom backends. Features automatic lock management, timeout handling, and extensible architecture.
39 lines (38 loc) • 4.35 kB
TypeScript
/**
* Lua script for atomic lock acquisition
* This script:
* 1. Checks if lock exists and is not expired
* 2. If lock doesn't exist or is expired, creates new lock
* 3. Properly cleans up old lockId index using keyPrefix
* 4. Sets both main lock key and lockId index
* 5. Returns 1 for success, 0 for failure
*/
export declare const ACQUIRE_SCRIPT = "\nlocal lockKey = KEYS[1]\nlocal lockIdKey = KEYS[2]\nlocal lockData = ARGV[1]\nlocal ttlSeconds = tonumber(ARGV[2])\nlocal currentTime = tonumber(ARGV[3])\nlocal keyPrefix = ARGV[4]\n\n-- Check if lock exists\nlocal existingData = redis.call('GET', lockKey)\nif existingData then\n local data = cjson.decode(existingData)\n -- If lock is not expired, return failure\n if data.expiresAt > currentTime then\n return 0\n end\n -- Lock is expired, we can clean up the old lockId index\n if data.lockId then\n local oldLockIdKey = keyPrefix .. 'id:' .. data.lockId\n redis.call('DEL', oldLockIdKey)\n end\nend\n\n-- Acquire the lock\nredis.call('SET', lockKey, lockData, 'EX', ttlSeconds)\nredis.call('SET', lockIdKey, lockKey, 'EX', ttlSeconds)\nreturn 1\n";
/**
* Lua script for atomic lock release
* This script:
* 1. Gets the lock key from the lockId index
* 2. Verifies ownership by checking lockId in the lock data
* 3. Deletes both main lock and lockId index if ownership is verified
* 4. Returns 1 for success, 0 for failure (not found or not owned)
*/
export declare const RELEASE_SCRIPT = "\nlocal lockIdKey = KEYS[1]\nlocal lockId = ARGV[1]\n\n-- Get the lock key from the lockId index\nlocal lockKey = redis.call('GET', lockIdKey)\nif not lockKey then\n return 0 -- Lock ID not found\nend\n\n-- Get the lock data\nlocal lockData = redis.call('GET', lockKey)\nif not lockData then\n -- Lock key doesn't exist, but lockId index does - clean up index\n redis.call('DEL', lockIdKey)\n return 0\nend\n\n-- Verify ownership\nlocal data = cjson.decode(lockData)\nif data.lockId ~= lockId then\n return 0 -- Not the owner\nend\n\n-- Delete both keys atomically\nredis.call('DEL', lockKey)\nredis.call('DEL', lockIdKey)\nreturn 1\n";
/**
* Lua script for atomic lock extension
* This script:
* 1. Gets the lock key from the lockId index
* 2. Verifies ownership and that lock hasn't expired
* 3. Updates the lock data with new expiration time
* 4. Updates TTL on both keys
* 5. Returns 1 for success, 0 for failure
*/
export declare const EXTEND_SCRIPT = "\nlocal lockIdKey = KEYS[1]\nlocal lockId = ARGV[1]\nlocal ttlMs = tonumber(ARGV[2])\nlocal currentTime = tonumber(ARGV[3])\n\n-- Get the lock key from the lockId index\nlocal lockKey = redis.call('GET', lockIdKey)\nif not lockKey then\n return 0 -- Lock ID not found\nend\n\n-- Get the lock data\nlocal lockData = redis.call('GET', lockKey)\nif not lockData then\n -- Lock key doesn't exist, but lockId index does - clean up index\n redis.call('DEL', lockIdKey)\n return 0\nend\n\n-- Verify ownership and expiration\nlocal data = cjson.decode(lockData)\nif data.lockId ~= lockId then\n return 0 -- Not the owner\nend\n\nif data.expiresAt <= currentTime then\n return 0 -- Lock already expired\nend\n\n-- Update the lock data with new expiration\ndata.expiresAt = currentTime + ttlMs\nlocal ttlSeconds = math.ceil(ttlMs / 1000)\n\n-- Update both keys with new data and TTL\nredis.call('SET', lockKey, cjson.encode(data), 'EX', ttlSeconds)\nredis.call('EXPIRE', lockIdKey, ttlSeconds)\n\nreturn 1\n";
/**
* Lua script for checking lock status with cleanup
* This script:
* 1. Gets the lock data
* 2. Checks if it's expired
* 3. If expired, cleans up both lock and lockId index (fire-and-forget)
* 4. Returns 1 if locked and not expired, 0 otherwise
*/
export declare const IS_LOCKED_SCRIPT = "\nlocal lockKey = KEYS[1]\nlocal keyPrefix = ARGV[1]\nlocal currentTime = tonumber(ARGV[2])\n\n-- Get the lock data\nlocal lockData = redis.call('GET', lockKey)\nif not lockData then\n return 0 -- No lock exists\nend\n\n-- Check if expired\nlocal data = cjson.decode(lockData)\nif data.expiresAt <= currentTime then\n -- Lock is expired, clean up (fire-and-forget)\n redis.call('DEL', lockKey)\n if data.lockId then\n local lockIdKey = keyPrefix .. 'id:' .. data.lockId\n redis.call('DEL', lockIdKey)\n end\n return 0\nend\n\nreturn 1 -- Lock exists and is not expired\n";