Skip to content

Commit

Permalink
fix: add file locking mechanism
Browse files Browse the repository at this point in the history
  • Loading branch information
NeuralFlux committed Nov 5, 2024
1 parent 0e5358b commit 5ee52e6
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 1 deletion.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@
"devDependencies": {
"@commitlint/cli": "^18.2.0",
"@commitlint/config-conventional": "^11.0.0",
"biolink-model": "workspace:../biolink-model",
"@types/debug": "^4.1.10",
"@types/jest": "^29.5.7",
"@types/lodash": "^4.14.200",
"@types/node": "^20.8.10",
"@typescript-eslint/eslint-plugin": "^6.8.0",
"@typescript-eslint/parser": "^6.8.0",
"biolink-model": "workspace:../biolink-model",
"eslint": "^8.53.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.1",
Expand All @@ -59,6 +59,7 @@
"ioredis": "^5.3.2",
"ioredis-mock": "^8.9.0",
"lodash": "^4.17.21",
"proper-lockfile": "^4.1.2",
"redlock": "5.0.0-beta.2"
}
}
62 changes: 62 additions & 0 deletions src/misc.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import lockfile from "proper-lockfile";
import { setTimeout as sleep } from "timers/promises";

export function toArray<Type>(input: Type | Type[]): Type[] {
if (Array.isArray(input)) {
return input;
Expand Down Expand Up @@ -76,3 +79,62 @@ export function timeoutPromise<T>(promise: Promise<T>, timeout: number): Promise
reject = newReject;
});
}

export const LOCKFILE_STALENESS = {stale: 5000}; // lock expiration in milliseconds to prevent deadlocks
export const LOCKFILE_RETRY_CONFIG = {
retries: {
retries: 10,
factor: 2,
minTimeout: 100,
maxTimeout: 1000,
},
stale: LOCKFILE_STALENESS["stale"],
};

export async function lockWithActionAsync<T>(filePath: string, action: () => Promise<T>, debug?: (message: string) => void): Promise<T> {
if (process.env.NODE_ENV !== "production") {
debug(`Development mode: Skipping lockfile ${process.env.NODE_ENV}`);
const result = await action();
return result;

Check warning on line 98 in src/misc.ts

View check run for this annotation

Codecov / codecov/patch

src/misc.ts#L96-L98

Added lines #L96 - L98 were not covered by tests
}

let release;
try {
release = await lockfile.lock(filePath, LOCKFILE_RETRY_CONFIG);
const result = await action();
return result;

Check warning on line 105 in src/misc.ts

View check run for this annotation

Codecov / codecov/patch

src/misc.ts#L102-L105

Added lines #L102 - L105 were not covered by tests
} catch (error) {
debug(`Lockfile error: ${error}`);

Check warning on line 107 in src/misc.ts

View check run for this annotation

Codecov / codecov/patch

src/misc.ts#L107

Added line #L107 was not covered by tests
// throw error;
} finally {
if (release) release();
}
}

export function lockWithActionSync<T>(filePath: string, action: () => T, debug?: (message: string) => void): T {
if (process.env.NODE_ENV !== "production") {
debug(`Development mode: Skipping lockfile ${process.env.NODE_ENV}`);
return action();

Check warning on line 117 in src/misc.ts

View check run for this annotation

Codecov / codecov/patch

src/misc.ts#L116-L117

Added lines #L116 - L117 were not covered by tests
}

let release;
try {
const startTime = Date.now();

Check warning on line 122 in src/misc.ts

View check run for this annotation

Codecov / codecov/patch

src/misc.ts#L121-L122

Added lines #L121 - L122 were not covered by tests

while (Date.now() - startTime < LOCKFILE_STALENESS["stale"]) {

Check warning on line 124 in src/misc.ts

View check run for this annotation

Codecov / codecov/patch

src/misc.ts#L124

Added line #L124 was not covered by tests
if (!lockfile.checkSync(filePath)) {
release = lockfile.lockSync(filePath, LOCKFILE_STALENESS);
const result = action();
return result;

Check warning on line 128 in src/misc.ts

View check run for this annotation

Codecov / codecov/patch

src/misc.ts#L126-L128

Added lines #L126 - L128 were not covered by tests
} else {
sleep(LOCKFILE_RETRY_CONFIG["retries"]["minTimeout"]);

Check warning on line 130 in src/misc.ts

View check run for this annotation

Codecov / codecov/patch

src/misc.ts#L130

Added line #L130 was not covered by tests
}
}
debug("Lockfile timeout: did not read file");

Check warning on line 133 in src/misc.ts

View check run for this annotation

Codecov / codecov/patch

src/misc.ts#L133

Added line #L133 was not covered by tests
} catch (error) {
debug(`Lockfile error: ${error}`);

Check warning on line 135 in src/misc.ts

View check run for this annotation

Codecov / codecov/patch

src/misc.ts#L135

Added line #L135 was not covered by tests
// throw error;
} finally {
if (release) release();
}
}

0 comments on commit 5ee52e6

Please sign in to comment.