72 lines
1.8 KiB
TypeScript
72 lines
1.8 KiB
TypeScript
import { ErrCode, RecaseError } from "@autumn/shared";
|
|
import { tryRedisWrite } from "@/utils/cacheUtils/cacheUtils.js";
|
|
import { redis } from "./initRedis.js";
|
|
|
|
export const clearLock = async ({ lockKey }: { lockKey: string }) => {
|
|
await tryRedisWrite(() => redis.del(lockKey));
|
|
};
|
|
|
|
interface LockData {
|
|
errorMessage: string;
|
|
}
|
|
|
|
const DEFAULT_ERROR_MESSAGE =
|
|
"Operation already in progress, try again in a few seconds";
|
|
|
|
/**
|
|
* Acquire a distributed lock using Redis.
|
|
* If Redis is not ready or errors, returns true to allow the operation to proceed.
|
|
* @returns true if lock was acquired (or Redis unavailable), throws if lock already exists
|
|
*/
|
|
export const acquireLock = async ({
|
|
lockKey,
|
|
ttlMs = 10000,
|
|
errorMessage = DEFAULT_ERROR_MESSAGE,
|
|
}: {
|
|
lockKey: string;
|
|
ttlMs?: number;
|
|
errorMessage?: string;
|
|
}): Promise<boolean> => {
|
|
// If Redis not ready, allow operation to proceed
|
|
if (redis.status !== "ready") {
|
|
return true;
|
|
}
|
|
|
|
try {
|
|
// NX = only set if key doesn't exist, PX = set expiry in milliseconds
|
|
// Store as JSON for future extensibility
|
|
const lockData: LockData = { errorMessage };
|
|
const result = await redis.set(
|
|
lockKey,
|
|
JSON.stringify(lockData),
|
|
"PX",
|
|
ttlMs,
|
|
"NX",
|
|
);
|
|
|
|
// If result is null, lock already exists (NX failed)
|
|
if (result === null) {
|
|
const existingData = await redis.get(lockKey);
|
|
const parsed = existingData
|
|
? (JSON.parse(existingData) as LockData)
|
|
: null;
|
|
|
|
throw new RecaseError({
|
|
message: parsed?.errorMessage || DEFAULT_ERROR_MESSAGE,
|
|
code: ErrCode.InvalidRequest,
|
|
statusCode: 429,
|
|
});
|
|
}
|
|
|
|
return true;
|
|
} catch (error) {
|
|
// Re-throw lock conflict errors
|
|
if (error instanceof RecaseError) {
|
|
throw error;
|
|
}
|
|
|
|
// Redis error - allow operation to proceed
|
|
return true;
|
|
}
|
|
};
|