Skip to content

Commit 15e1b02

Browse files
use homemade filelock impl instead of proper-lockfile
1 parent df1231e commit 15e1b02

File tree

5 files changed

+60
-66
lines changed

5 files changed

+60
-66
lines changed

package-lock.json

Lines changed: 2 additions & 39 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
"@babel/preset-typescript": "^7.25.9",
3333
"@types/adm-zip": "^0.5.5",
3434
"@types/node": "^22.7.9",
35-
"@types/proper-lockfile": "^4.1.4",
3635
"@types/semver": "^7.5.8",
3736
"babel-jest": "^29.7.0",
3837
"jest": "^29.7.0",
@@ -41,7 +40,6 @@
4140
},
4241
"dependencies": {
4342
"adm-zip": "^0.5.16",
44-
"proper-lockfile": "^4.1.2",
4543
"semver": "^7.6.3"
4644
},
4745
"repository": {

src/libraries/Downloader.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@ import AdmZip from 'adm-zip'
66
import { normalize as normalizePath } from 'path';
77
import { randomUUID } from 'crypto';
88
import { execFile } from 'child_process';
9-
import { lockSync } from 'proper-lockfile';
109
import { BinaryInfo, InternalServerOptions } from '../../types';
11-
import { waitForLock } from './FileLock';
10+
import { lockFile, waitForLock } from './FileLock';
1211

1312
function getZipData(entry: AdmZip.IZipEntry): Promise<Buffer> {
1413
return new Promise((resolve, reject) => {
@@ -205,10 +204,10 @@ export function downloadBinary(binaryInfo: BinaryInfo, options: InternalServerOp
205204

206205
while (true) {
207206
try {
208-
releaseFunction = lockSync(extractedPath, {realpath: false})
207+
releaseFunction = await lockFile(extractedPath)
209208
break
210209
} catch (e) {
211-
if (e.code === 'ELOCKED') {
210+
if (e === 'LOCKED') {
212211
logger.log('Waiting for lock for MySQL version', version)
213212
await waitForLock(extractedPath, options)
214213
logger.log('Lock is gone for version', version)

src/libraries/Executor.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ import { GenerateRandomPort } from "./Port";
88
import DBDestroySignal from "./AbortSignal";
99
import { ExecuteFileReturn, InstalledMySQLVersion, InternalServerOptions, MySQLDB } from "../../types";
1010
import {normalize as normalizePath, resolve as resolvePath} from 'path'
11-
import { lockSync } from 'proper-lockfile';
12-
import { waitForLock } from "./FileLock";
11+
import { lockFile, waitForLock } from "./FileLock";
1312

1413
class Executor {
1514
logger: Logger;
@@ -316,10 +315,10 @@ class Executor {
316315

317316
while(true) {
318317
try {
319-
lockRelease = lockSync(copyPath, {realpath: false})
318+
lockRelease = await lockFile(copyPath)
320319
break
321320
} catch (e) {
322-
if (e.code === 'ELOCKED') {
321+
if (e === 'LOCKED') {
323322
this.logger.log('Waiting for lock for libaio copy')
324323
await waitForLock(copyPath, options)
325324
this.logger.log('Lock is gone for libaio copy')

src/libraries/FileLock.ts

Lines changed: 52 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,57 @@
1-
import { checkSync } from "proper-lockfile";
1+
import fsPromises from 'fs/promises';
22
import { InternalServerOptions } from "../../types";
33

4-
export function waitForLock(path: string, options: InternalServerOptions): Promise<void> {
5-
return new Promise(async (resolve, reject) => {
6-
let retries = 0;
7-
while (retries <= options.lockRetries) {
8-
retries++
9-
try {
10-
const locked = checkSync(path, {realpath: false});
11-
if (!locked) {
12-
return resolve()
13-
} else {
14-
await new Promise(resolve => setTimeout(resolve, options.lockRetryWait))
15-
}
16-
} catch (e) {
17-
return reject(e)
4+
export async function waitForLock(path: string, options: InternalServerOptions): Promise<void> {
5+
const lockPath = `${path}.lock`
6+
let retries = 0;
7+
do {
8+
retries++;
9+
try {
10+
const stat = await fsPromises.stat(lockPath)
11+
if (performance.now() - stat.mtime.getTime() > 10_000) {
12+
return
13+
} else {
14+
await new Promise(resolve => setTimeout(resolve, options.lockRetryWait))
1815
}
16+
} catch (e) {
17+
if (e.code === 'ENOENT') {
18+
return
19+
} else {
20+
throw e
21+
}
22+
}
23+
} while(retries <= options.lockRetries)
24+
}
25+
26+
function setupMTimeEditor(lockPath: string): () => Promise<void> {
27+
const interval = setInterval(async () => {
28+
try {
29+
const time = performance.now();
30+
await fsPromises.utimes(lockPath, time, time)
31+
} catch {}
32+
}, 2_000)
33+
34+
return async () => {
35+
clearInterval(interval)
36+
await fsPromises.rmdir(lockPath)
37+
}
38+
}
39+
40+
export async function lockFile(path: string): Promise<() => Promise<void>> {
41+
const lockPath = `${path}.lock`
42+
try {
43+
await fsPromises.mkdir(lockPath)
44+
return setupMTimeEditor(lockPath)
45+
} catch (e) {
46+
if (e.code === 'EEXIST') {
47+
const stat = await fsPromises.stat(lockPath)
48+
if (performance.now() - stat.mtime.getTime() > 10_000) {
49+
return setupMTimeEditor(lockPath)
50+
} else {
51+
throw 'LOCKED'
52+
}
53+
} else {
54+
throw e
1955
}
20-
reject(`lockRetries has been exceeded. Lock had not been released after ${options.lockRetryWait} * ${options.lockRetries} milliseconds.`)
21-
})
56+
}
2257
}

0 commit comments

Comments
 (0)