@dxos/lock-file 0.1.57 → 0.1.58-main.0e9c99e

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -26,15 +26,15 @@ module.exports = __toCommonJS(src_exports);
26
26
 
27
27
  // packages/common/lock-file/src/lock-file.ts
28
28
  var import_fs_ext = require("fs-ext");
29
- var import_node_fs = require("node:fs");
30
29
  var import_promises = require("node:fs/promises");
31
30
  var LockFile = class _LockFile {
32
31
  static async acquire(filename) {
33
- const handle = await (0, import_promises.open)(filename, import_node_fs.constants.O_CREAT);
32
+ const handle = await (0, import_promises.open)(filename, import_promises.constants.O_CREAT);
34
33
  await new Promise((resolve, reject) => {
35
- (0, import_fs_ext.flock)(handle.fd, "exnb", (err) => {
34
+ (0, import_fs_ext.flock)(handle.fd, "exnb", async (err) => {
36
35
  if (err) {
37
36
  reject(err);
37
+ await handle.close();
38
38
  return;
39
39
  }
40
40
  resolve();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/index.ts", "../../../src/lock-file.ts"],
4
- "sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nexport * from './lock-file';\n", "//\n// Copyright 2023 DXOS.org\n//\n\nimport { flock } from 'fs-ext';\nimport { constants } from 'node:fs';\nimport { open, FileHandle } from 'node:fs/promises';\n\nexport class LockFile {\n static async acquire(filename: string): Promise<FileHandle> {\n const handle = await open(filename, constants.O_CREAT);\n await new Promise<void>((resolve, reject) => {\n flock(handle.fd, 'exnb', (err) => {\n if (err) {\n reject(err);\n return;\n }\n resolve();\n });\n });\n return handle;\n }\n\n static async release(handle: FileHandle): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n flock(handle.fd, 'un', (err) => {\n if (err) {\n reject(err);\n return;\n }\n resolve();\n });\n });\n await handle.close();\n }\n\n static async isLocked(filename: string): Promise<boolean> {\n try {\n const handle = await LockFile.acquire(filename);\n await LockFile.release(handle);\n await handle.close();\n\n return false;\n } catch (e) {\n return true;\n }\n }\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;ACIA,oBAAsB;AACtB,qBAA0B;AAC1B,sBAAiC;AAE1B,IAAMA,WAAN,MAAMA,UAAAA;EACX,aAAaC,QAAQC,UAAuC;AAC1D,UAAMC,SAAS,UAAMC,sBAAKF,UAAUG,yBAAUC,OAAO;AACrD,UAAM,IAAIC,QAAc,CAACC,SAASC,WAAAA;AAChCC,+BAAMP,OAAOQ,IAAI,QAAQ,CAACC,QAAAA;AACxB,YAAIA,KAAK;AACPH,iBAAOG,GAAAA;AACP;QACF;AACAJ,gBAAAA;MACF,CAAA;IACF,CAAA;AACA,WAAOL;EACT;EAEA,aAAaU,QAAQV,QAAmC;AACtD,UAAM,IAAII,QAAc,CAACC,SAASC,WAAAA;AAChCC,+BAAMP,OAAOQ,IAAI,MAAM,CAACC,QAAAA;AACtB,YAAIA,KAAK;AACPH,iBAAOG,GAAAA;AACP;QACF;AACAJ,gBAAAA;MACF,CAAA;IACF,CAAA;AACA,UAAML,OAAOW,MAAK;EACpB;EAEA,aAAaC,SAASb,UAAoC;AACxD,QAAI;AACF,YAAMC,SAAS,MAAMH,UAASC,QAAQC,QAAAA;AACtC,YAAMF,UAASa,QAAQV,MAAAA;AACvB,YAAMA,OAAOW,MAAK;AAElB,aAAO;IACT,SAASE,GAAG;AACV,aAAO;IACT;EACF;AACF;",
6
- "names": ["LockFile", "acquire", "filename", "handle", "open", "constants", "O_CREAT", "Promise", "resolve", "reject", "flock", "fd", "err", "release", "close", "isLocked", "e"]
4
+ "sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nexport * from './lock-file';\n", "//\n// Copyright 2023 DXOS.org\n//\n\nimport { flock } from 'fs-ext';\nimport { open, FileHandle, constants } from 'node:fs/promises';\n\nexport class LockFile {\n static async acquire(filename: string): Promise<FileHandle> {\n const handle = await open(filename, constants.O_CREAT);\n await new Promise<void>((resolve, reject) => {\n flock(handle.fd, 'exnb', async (err) => {\n if (err) {\n reject(err);\n await handle.close();\n return;\n }\n resolve();\n });\n });\n return handle;\n }\n\n static async release(handle: FileHandle): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n flock(handle.fd, 'un', (err) => {\n if (err) {\n reject(err);\n return;\n }\n resolve();\n });\n });\n await handle.close();\n }\n\n static async isLocked(filename: string): Promise<boolean> {\n try {\n const handle = await LockFile.acquire(filename);\n await LockFile.release(handle);\n await handle.close();\n\n return false;\n } catch (e) {\n return true;\n }\n }\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;ACIA,oBAAsB;AACtB,sBAA4C;AAErC,IAAMA,WAAN,MAAMA,UAAAA;EACX,aAAaC,QAAQC,UAAuC;AAC1D,UAAMC,SAAS,UAAMC,sBAAKF,UAAUG,0BAAUC,OAAO;AACrD,UAAM,IAAIC,QAAc,CAACC,SAASC,WAAAA;AAChCC,+BAAMP,OAAOQ,IAAI,QAAQ,OAAOC,QAAAA;AAC9B,YAAIA,KAAK;AACPH,iBAAOG,GAAAA;AACP,gBAAMT,OAAOU,MAAK;AAClB;QACF;AACAL,gBAAAA;MACF,CAAA;IACF,CAAA;AACA,WAAOL;EACT;EAEA,aAAaW,QAAQX,QAAmC;AACtD,UAAM,IAAII,QAAc,CAACC,SAASC,WAAAA;AAChCC,+BAAMP,OAAOQ,IAAI,MAAM,CAACC,QAAAA;AACtB,YAAIA,KAAK;AACPH,iBAAOG,GAAAA;AACP;QACF;AACAJ,gBAAAA;MACF,CAAA;IACF,CAAA;AACA,UAAML,OAAOU,MAAK;EACpB;EAEA,aAAaE,SAASb,UAAoC;AACxD,QAAI;AACF,YAAMC,SAAS,MAAMH,UAASC,QAAQC,QAAAA;AACtC,YAAMF,UAASc,QAAQX,MAAAA;AACvB,YAAMA,OAAOU,MAAK;AAElB,aAAO;IACT,SAASG,GAAG;AACV,aAAO;IACT;EACF;AACF;",
6
+ "names": ["LockFile", "acquire", "filename", "handle", "open", "constants", "O_CREAT", "Promise", "resolve", "reject", "flock", "fd", "err", "close", "release", "isLocked", "e"]
7
7
  }
@@ -1 +1 @@
1
- {"inputs":{"packages/common/lock-file/src/lock-file.ts":{"bytes":4094,"imports":[{"path":"fs-ext","kind":"import-statement","external":true},{"path":"node:fs","kind":"import-statement","external":true},{"path":"node:fs/promises","kind":"import-statement","external":true}],"format":"esm"},"packages/common/lock-file/src/index.ts":{"bytes":460,"imports":[{"path":"packages/common/lock-file/src/lock-file.ts","kind":"import-statement","original":"./lock-file"}],"format":"esm"}},"outputs":{"packages/common/lock-file/dist/lib/node/index.cjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":2251},"packages/common/lock-file/dist/lib/node/index.cjs":{"imports":[{"path":"fs-ext","kind":"require-call","external":true},{"path":"node:fs","kind":"require-call","external":true},{"path":"node:fs/promises","kind":"require-call","external":true}],"exports":[],"entryPoint":"packages/common/lock-file/src/index.ts","inputs":{"packages/common/lock-file/src/index.ts":{"bytesInOutput":121},"packages/common/lock-file/src/lock-file.ts":{"bytesInOutput":1044}},"bytes":2191}}}
1
+ {"inputs":{"packages/common/lock-file/src/lock-file.ts":{"bytes":4154,"imports":[{"path":"fs-ext","kind":"import-statement","external":true},{"path":"node:fs/promises","kind":"import-statement","external":true}],"format":"esm"},"packages/common/lock-file/src/index.ts":{"bytes":470,"imports":[{"path":"packages/common/lock-file/src/lock-file.ts","kind":"import-statement","original":"./lock-file"}],"format":"esm"}},"outputs":{"packages/common/lock-file/dist/lib/node/index.cjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":2274},"packages/common/lock-file/dist/lib/node/index.cjs":{"imports":[{"path":"fs-ext","kind":"require-call","external":true},{"path":"node:fs/promises","kind":"require-call","external":true}],"exports":[],"entryPoint":"packages/common/lock-file/src/index.ts","inputs":{"packages/common/lock-file/src/index.ts":{"bytesInOutput":121},"packages/common/lock-file/src/lock-file.ts":{"bytesInOutput":1042}},"bytes":2189}}}
@@ -1 +1 @@
1
- {"version":3,"file":"lock-file.d.ts","sourceRoot":"","sources":["../../../src/lock-file.ts"],"names":[],"mappings":";AAMA,OAAO,EAAQ,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEpD,qBAAa,QAAQ;WACN,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;WAc9C,OAAO,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;WAa1C,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAW1D"}
1
+ {"version":3,"file":"lock-file.d.ts","sourceRoot":"","sources":["../../../src/lock-file.ts"],"names":[],"mappings":";AAKA,OAAO,EAAQ,UAAU,EAAa,MAAM,kBAAkB,CAAC;AAE/D,qBAAa,QAAQ;WACN,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;WAe9C,OAAO,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;WAa1C,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAW1D"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/lock-file",
3
- "version": "0.1.57",
3
+ "version": "0.1.58-main.0e9c99e",
4
4
  "description": "Lock file .",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -14,9 +14,9 @@
14
14
  ],
15
15
  "dependencies": {
16
16
  "fs-ext": "^2.0.0",
17
- "@dxos/async": "0.1.57",
18
- "@dxos/log": "0.1.57",
19
- "@dxos/node-std": "0.1.57"
17
+ "@dxos/async": "0.1.58-main.0e9c99e",
18
+ "@dxos/log": "0.1.58-main.0e9c99e",
19
+ "@dxos/node-std": "0.1.58-main.0e9c99e"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/fs-ext": "^2.0.0",
@@ -5,17 +5,24 @@
5
5
  import chai, { expect } from 'chai';
6
6
  import chaiAsPromised from 'chai-as-promised';
7
7
  import { spawn } from 'node:child_process';
8
- import { existsSync } from 'node:fs';
8
+ import { existsSync, mkdirSync, unlinkSync } from 'node:fs';
9
9
  import { join } from 'node:path';
10
+ import waitForExpect from 'wait-for-expect';
10
11
 
11
- import { Trigger, sleep } from '@dxos/async';
12
- import { describe, test } from '@dxos/test';
12
+ import { Trigger } from '@dxos/async';
13
+ import { afterTest, beforeAll, describe, test } from '@dxos/test';
13
14
 
14
15
  import { LockFile } from './lock-file';
15
16
 
16
17
  chai.use(chaiAsPromised);
17
18
 
19
+ const TEST_DIR = '/tmp/dxos/testing/lock-file';
20
+
18
21
  describe('LockFile', () => {
22
+ beforeAll(() => {
23
+ mkdirSync(TEST_DIR, { recursive: true });
24
+ });
25
+
19
26
  test('basic', async () => {
20
27
  const filename = join('/tmp', `lock-${Math.random()}.lock`);
21
28
 
@@ -27,32 +34,55 @@ describe('LockFile', () => {
27
34
  await LockFile.release(handle2);
28
35
  });
29
36
 
30
- test('released when process exists', async () => {
31
- const filename = join('/tmp', `lock-${Math.random()}.lock`);
32
- const processHandle = spawn('node', ['-e', `(${lockInProcess.toString()})(${JSON.stringify(filename)})`], {
33
- stdio: 'inherit',
37
+ test('released when process exits', async () => {
38
+ const filename = join(TEST_DIR, `lock-${Math.random()}.lock`);
39
+ afterTest(() => {
40
+ if (existsSync(filename)) {
41
+ unlinkSync(filename);
42
+ }
34
43
  });
35
44
 
36
- // Wait for process to start
37
45
  const trigger = new Trigger();
38
- const intervalId = setInterval(() => {
39
- if (existsSync(filename)) {
46
+ const processHandle = spawn('node', ['-e', `(${lockInProcess.toString()})(${JSON.stringify(filename)})`], {
47
+ stdio: 'pipe',
48
+ });
49
+
50
+ {
51
+ // Wait for process to start
52
+
53
+ processHandle.stdout.on('data', (data: Uint8Array) => {
54
+ expect(data.toString().trim()).to.equal('locked');
40
55
  trigger.wake();
41
- }
42
- }, 100);
56
+ });
57
+ }
58
+
43
59
  await trigger.wait({ timeout: 1_000 });
44
- clearInterval(intervalId);
45
60
 
46
61
  await expect(LockFile.acquire(filename)).to.be.rejected;
47
62
 
63
+ processHandle.stdin.write('close');
48
64
  processHandle.kill();
49
65
 
50
66
  // Wait for process to be killed
51
- await sleep(400);
67
+ await waitForExpect(async () => {
68
+ expect(await LockFile.isLocked(filename)).to.be.false;
69
+ });
52
70
 
53
71
  const handle = await LockFile.acquire(filename);
54
72
  await LockFile.release(handle);
55
73
  });
74
+
75
+ test('spam with isLocked calls', async () => {
76
+ const checksNumber = 1000;
77
+ const filename = join('/tmp', `lock-${Math.random()}.lock`);
78
+
79
+ for (const _ of Array(checksNumber).keys()) {
80
+ const handle = await LockFile.acquire(filename);
81
+ expect(await LockFile.isLocked(filename)).to.be.true;
82
+ await LockFile.release(handle);
83
+ expect(await LockFile.isLocked(filename)).to.be.false;
84
+ }
85
+ });
56
86
  });
57
87
 
58
88
  // NOTE: Self-contained so when function.toString is called the code runs.
@@ -64,6 +94,9 @@ const lockInProcess = (filename: string) => {
64
94
  // eslint-disable-next-line @typescript-eslint/no-var-requires
65
95
  const { flock } = require('fs-ext');
66
96
 
97
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
98
+ // @ts-ignore
99
+ let fileHandle;
67
100
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
68
101
  // @ts-ignore
69
102
  open(filename, constants.O_CREAT).then((handle) => {
@@ -71,11 +104,23 @@ const lockInProcess = (filename: string) => {
71
104
  // @ts-ignore
72
105
  flock(handle.fd, 'exnb', (err) => {
73
106
  if (err) {
107
+ handle.close();
74
108
  console.error(err);
75
109
  return;
76
110
  }
111
+ fileHandle = handle;
112
+ console.log('locked');
77
113
  // Hang
78
114
  setTimeout(() => {}, 1_000_000);
79
115
  });
80
116
  });
117
+
118
+ // Close file handle on stdin close.
119
+ process.stdin.on('data', (data) => {
120
+ if (data.toString().trim() === 'close') {
121
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
122
+ // @ts-ignore
123
+ fileHandle.close();
124
+ }
125
+ });
81
126
  };
package/src/lock-file.ts CHANGED
@@ -3,16 +3,16 @@
3
3
  //
4
4
 
5
5
  import { flock } from 'fs-ext';
6
- import { constants } from 'node:fs';
7
- import { open, FileHandle } from 'node:fs/promises';
6
+ import { open, FileHandle, constants } from 'node:fs/promises';
8
7
 
9
8
  export class LockFile {
10
9
  static async acquire(filename: string): Promise<FileHandle> {
11
10
  const handle = await open(filename, constants.O_CREAT);
12
11
  await new Promise<void>((resolve, reject) => {
13
- flock(handle.fd, 'exnb', (err) => {
12
+ flock(handle.fd, 'exnb', async (err) => {
14
13
  if (err) {
15
14
  reject(err);
15
+ await handle.close();
16
16
  return;
17
17
  }
18
18
  resolve();