From 8cf73fac12aa9fe5f92e1fa026c8b7f9f06fbc0b Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Tue, 19 May 2026 17:33:39 +0200 Subject: [PATCH] fix: throttle repository lock cleanup during polling --- app/server/core/repository-mutex.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/server/core/repository-mutex.ts b/app/server/core/repository-mutex.ts index 61b30944..760140e7 100644 --- a/app/server/core/repository-mutex.ts +++ b/app/server/core/repository-mutex.ts @@ -31,10 +31,12 @@ type QueueAttempt = { status: "acquired"; lock: AcquiredLock } | { status: "wait const LOCK_LEASE_MS = 30_000; const LOCK_HEARTBEAT_MS = 5_000; const LOCK_POLL_MS = 250; +const LOCK_POLL_CLEANUP_MS = 5_000; class RepositoryMutex { private ownerId = `owner_${Bun.randomUUIDv7()}`; private heartbeatTimers = new Map>(); + private nextPollCleanupAt = 0; private generateLockId(): string { return `lock_${Bun.randomUUIDv7()}`; @@ -89,6 +91,13 @@ class RepositoryMutex { tx.delete(repositoryLockWaitersTable).where(lte(repositoryLockWaitersTable.expiresAt, now)).run(); } + private cleanupExpiredDuringPolling(tx: RepositoryMutexTransaction, now: number) { + if (now < this.nextPollCleanupAt) return; + + this.cleanupExpired(tx, now); + this.nextPollCleanupAt = now + LOCK_POLL_CLEANUP_MS; + } + private getActiveLocks(tx: RepositoryMutexTransaction, repositoryId: string, now: number) { return tx.query.repositoryLocksTable .findMany({ @@ -235,7 +244,7 @@ class RepositoryMutex { const now = Date.now(); return db.transaction((tx) => { - this.cleanupExpired(tx, now); + this.cleanupExpiredDuringPolling(tx, now); const activeLock = this.getActiveLockById(tx, waiterId, now); if (activeLock) {