fix(mutex): prioritize waiting exclusive locks over newly added shared tasks

This commit is contained in:
Nicolas Meienberger 2025-12-20 11:15:43 +01:00
parent cbc9da0e26
commit 1047f54523
2 changed files with 42 additions and 2 deletions

View file

@ -0,0 +1,38 @@
import { test, describe, expect } from "bun:test";
import { repoMutex } from "../repository-mutex";
describe("RepositoryMutex", () => {
test("should prioritize waiting exclusive locks over new shared locks", async () => {
const repoId = "test-repo";
const results: string[] = [];
const releaseShared1 = await repoMutex.acquireShared(repoId, "backup-1");
results.push("acquired-shared-1");
const exclusivePromise = repoMutex.acquireExclusive(repoId, "unlock").then((release) => {
results.push("acquired-exclusive");
return release;
});
const shared2Promise = repoMutex.acquireShared(repoId, "backup-2").then((release) => {
results.push("acquired-shared-2");
return release;
});
await new Promise((resolve) => setTimeout(resolve, 50));
expect(results).toEqual(["acquired-shared-1"]);
releaseShared1();
const releaseExclusive = await exclusivePromise;
expect(results).toEqual(["acquired-shared-1", "acquired-exclusive"]);
releaseExclusive();
const releaseShared2 = await shared2Promise;
expect(results).toEqual(["acquired-shared-1", "acquired-exclusive", "acquired-shared-2"]);
releaseShared2();
});
});

View file

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