Compare commits

..

2 Commits

Author SHA1 Message Date
Nicolas Meienberger
7ff38f0128 refactor: run forget commands in the background 2025-12-01 20:22:43 +01:00
Nicolas Meienberger
33e6f3773b fix: only lock shared tasks if an exclusive is running 2025-12-01 20:07:30 +01:00
2 changed files with 6 additions and 10 deletions

View File

@@ -49,8 +49,7 @@ class RepositoryMutex {
async acquireShared(repositoryId: string, operation: string): Promise<() => void> { async acquireShared(repositoryId: string, operation: string): Promise<() => void> {
const state = this.getOrCreateState(repositoryId); const state = this.getOrCreateState(repositoryId);
const hasExclusiveWaiter = state.waitQueue.some((w) => w.type === "exclusive"); if (!state.exclusiveHolder) {
if (!state.exclusiveHolder && !hasExclusiveWaiter) {
const lockId = this.generateLockId(); const lockId = this.generateLockId();
state.sharedHolders.set(lockId, { state.sharedHolders.set(lockId, {
id: lockId, id: lockId,
@@ -60,7 +59,9 @@ class RepositoryMutex {
return () => this.releaseShared(repositoryId, lockId); return () => this.releaseShared(repositoryId, lockId);
} }
logger.debug(`[Mutex] Waiting for shared lock on repo ${repositoryId}: ${operation}`); logger.debug(
`[Mutex] Waiting for shared lock on repo ${repositoryId}: ${operation} (exclusive held by: ${state.exclusiveHolder.operation})`,
);
const lockId = await new Promise<string>((resolve) => { const lockId = await new Promise<string>((resolve) => {
state.waitQueue.push({ type: "shared", operation, resolve }); state.waitQueue.push({ type: "shared", operation, resolve });
}); });

View File

@@ -263,12 +263,7 @@ const executeBackup = async (scheduleId: number, manual = false) => {
} }
if (schedule.retentionPolicy) { if (schedule.retentionPolicy) {
const releaseForgetLock = await repoMutex.acquireExclusive(repository.id, `forget:${volume.name}`); void runForget(schedule.id);
try {
await restic.forget(repository.config, schedule.retentionPolicy, { tag: schedule.id.toString() });
} finally {
releaseForgetLock();
}
} }
const nextBackupAt = calculateNextRun(schedule.cronExpression); const nextBackupAt = calculateNextRun(schedule.cronExpression);
@@ -415,7 +410,7 @@ const runForget = async (scheduleId: number) => {
throw new NotFoundError("Repository not found"); throw new NotFoundError("Repository not found");
} }
logger.info(`Manually running retention policy (forget) for schedule ${scheduleId}`); logger.info(`running retention policy (forget) for schedule ${scheduleId}`);
const releaseLock = await repoMutex.acquireExclusive(repository.id, `forget:manual:${scheduleId}`); const releaseLock = await repoMutex.acquireExclusive(repository.id, `forget:manual:${scheduleId}`);
try { try {
await restic.forget(repository.config, schedule.retentionPolicy, { tag: schedule.id.toString() }); await restic.forget(repository.config, schedule.retentionPolicy, { tag: schedule.id.toString() });