/** * Менеджер soft-locks для ресурсов * Предотвращает одновременное редактирование одного ресурса */ // In-memory locks: key -> { owner, expiresAt } const locks = new Map(); /** * Очистить истекшие блокировки */ function cleanupExpiredLocks() { const now = Date.now(); for (const [k, v] of locks.entries()) { if (!v || typeof v.expiresAt !== 'number' || v.expiresAt <= now) { locks.delete(k); } } } // Автоматическая очистка каждые 30 секунд setInterval(cleanupExpiredLocks, 30_000); /** * Получить статус блокировки ресурса */ function getLockStatus(resource) { cleanupExpiredLocks(); const info = locks.get(resource); if (!info) return { locked: false }; return { locked: true, owner: info.owner, expiresAt: info.expiresAt }; } /** * Получить или обновить блокировку ресурса */ function acquireLock(resource, owner = 'anonymous', ttlSeconds = 120) { cleanupExpiredLocks(); const now = Date.now(); const existing = locks.get(resource); if (existing && existing.expiresAt > now && existing.owner !== owner) { return { success: false, locked: true, owner: existing.owner, expiresAt: existing.expiresAt }; } const expiresAt = now + Math.max(30, Math.min(600, Number(ttlSeconds) || 120)) * 1000; locks.set(resource, { owner, expiresAt }); return { success: true, locked: true, owner, expiresAt }; } /** * Освободить блокировку ресурса */ function releaseLock(resource) { locks.delete(resource); return { released: true }; } module.exports = { getLockStatus, acquireLock, releaseLock, cleanupExpiredLocks, };