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
JavaScript
/* 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}`,
};
}
};
}