* Acquire a distributed lock * @param lockName - Name of the lock * @param options - Override default options for this specific lock * @returns Promise
(
lockName: string,
options?: Partial<DistributedLockOptions>
)
| 76 | * @returns Promise<LockResult> |
| 77 | */ |
| 78 | async acquire( |
| 79 | lockName: string, |
| 80 | options?: Partial<DistributedLockOptions> |
| 81 | ): Promise<LockResult> { |
| 82 | const opts = { ...this.options, ...options }; |
| 83 | const cacheManager = await getCacheManager(); |
| 84 | const lockKey = `${opts.prefix}:${lockName}`; |
| 85 | const lockId = nanoid(); |
| 86 | const lockValue = { |
| 87 | id: lockId, |
| 88 | acquiredAt: Date.now(), |
| 89 | acquiredBy: process.pid.toString(), // Use process ID as identifier |
| 90 | }; |
| 91 | |
| 92 | let retries = 0; |
| 93 | const maxRetries = opts.skipOnFailure ? 1 : opts.maxRetries; |
| 94 | |
| 95 | while (retries < maxRetries) { |
| 96 | try { |
| 97 | // Try to acquire the lock |
| 98 | const existingLock = await cacheManager.get(lockKey); |
| 99 | |
| 100 | if (!existingLock) { |
| 101 | // Lock is available, try to acquire it |
| 102 | await cacheManager.set( |
| 103 | lockKey, |
| 104 | JSON.stringify(lockValue), |
| 105 | opts.timeout |
| 106 | ); |
| 107 | |
| 108 | await sleep(Math.random() * 100); // NOTICE: avoid setting too quickly to conflict |
| 109 | |
| 110 | // Verify we actually got the lock (handle race conditions) |
| 111 | const verifyLock = await cacheManager.get(lockKey); |
| 112 | if (verifyLock) { |
| 113 | const parsedLock = JSON.parse(String(verifyLock)); |
| 114 | if (parsedLock.id === lockId) { |
| 115 | logger.debug( |
| 116 | `[DistributedLock] Successfully acquired lock: ${lockName}` |
| 117 | ); |
| 118 | |
| 119 | return { |
| 120 | acquired: true, |
| 121 | lockId, |
| 122 | release: async () => { |
| 123 | await this.release(lockName, lockId); |
| 124 | }, |
| 125 | }; |
| 126 | } |
| 127 | } |
| 128 | } else { |
| 129 | // Check if the existing lock has expired |
| 130 | const parsedLock = JSON.parse(String(existingLock)); |
| 131 | const lockAge = Date.now() - parsedLock.acquiredAt; |
| 132 | |
| 133 | if (lockAge > opts.timeout) { |
| 134 | // Lock has expired, try to clean it up and retry |
| 135 | logger.warn( |