fix(mutex): prioritize waiting exclusive locks over newly added shared tasks
This commit is contained in:
parent
cbc9da0e26
commit
1047f54523
2 changed files with 42 additions and 2 deletions
38
app/server/core/__tests__/repository-mutex.test.ts
Normal file
38
app/server/core/__tests__/repository-mutex.test.ts
Normal 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -49,7 +49,9 @@ 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);
|
||||||
|
|
||||||
if (!state.exclusiveHolder) {
|
const hasExclusiveInQueue = state.waitQueue.some((item) => item.type === "exclusive");
|
||||||
|
|
||||||
|
if (!state.exclusiveHolder && !hasExclusiveInQueue) {
|
||||||
const lockId = this.generateLockId();
|
const lockId = this.generateLockId();
|
||||||
state.sharedHolders.set(lockId, {
|
state.sharedHolders.set(lockId, {
|
||||||
id: lockId,
|
id: lockId,
|
||||||
|
|
@ -60,7 +62,7 @@ class RepositoryMutex {
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug(
|
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) => {
|
const lockId = await new Promise<string>((resolve) => {
|
||||||
state.waitQueue.push({ type: "shared", operation, resolve });
|
state.waitQueue.push({ type: "shared", operation, resolve });
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue