UNPKG

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.

79 lines (78 loc) 3.33 kB
/* SPDX-FileCopyrightText: 2025-present Kriasoft */ /* SPDX-License-Identifier: MIT */ import { generateLockId, mergeLockConfig } from "../../common/backend.js"; import { withAcquireRetries } from "../retry.js"; /** * Creates an acquire operation for Firestore backend */ export function createAcquireOperation(db, locksCollection, config) { return async (lockConfig) => { const mergedConfig = mergeLockConfig(lockConfig); const lockId = generateLockId(); const startTime = Date.now(); try { const result = await withAcquireRetries(async () => { // Check timeout before starting transaction to avoid orphaned locks if (Date.now() - startTime > mergedConfig.timeoutMs) { return { acquired: false, reason: "Acquisition timeout before transaction", }; } const docRef = locksCollection.doc(mergedConfig.key); const transactionResult = await db.runTransaction(async (trx) => { const doc = await trx.get(docRef); const currentTime = Date.now(); const expiresAt = currentTime + mergedConfig.ttlMs; // Final timeout check inside transaction before committing if (currentTime - startTime > mergedConfig.timeoutMs) { return { acquired: false, reason: "Acquisition timeout during transaction", }; } if (doc.exists) { const data = doc.data(); if (data.expiresAt > currentTime) { return { acquired: false, reason: "Lock already held", }; } // Lock exists but is expired - we can overwrite it atomically } const lockDocument = { lockId, expiresAt, createdAt: currentTime, key: mergedConfig.key, }; // Use set() to atomically create or overwrite (including expired locks) trx.set(docRef, lockDocument); return { acquired: true, expiresAt }; }); return transactionResult; }, config, mergedConfig.timeoutMs); if (result.acquired) { return { success: true, lockId, expiresAt: new Date(result.expiresAt), }; } else { return { success: false, error: result.reason || "Failed to acquire lock", }; } } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; return { success: false, error: `Failed to acquire lock "${mergedConfig.key}": ${errorMessage}`, }; } }; }