@zukhruf/mutex 0.3.1 → 0.3.2

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.
Files changed (28) hide show
  1. package/dist/lock-stores/file-system/caller.d.ts +15 -0
  2. package/dist/lock-stores/file-system/caller.d.ts.map +1 -0
  3. package/dist/lock-stores/file-system/caller.js +35 -0
  4. package/dist/lock-stores/file-system/caller.js.map +1 -0
  5. package/dist/lock-stores/file-system/file-lock-store.d.ts +1 -3
  6. package/dist/lock-stores/file-system/file-lock-store.d.ts.map +1 -1
  7. package/dist/lock-stores/file-system/file-lock-store.js +2 -2
  8. package/dist/lock-stores/file-system/file-lock-store.js.map +1 -1
  9. package/dist/lock-stores/file-system/lock-file-store.d.ts.map +1 -1
  10. package/dist/lock-stores/file-system/lock-file-store.js +41 -15
  11. package/dist/lock-stores/file-system/lock-file-store.js.map +1 -1
  12. package/dist/lock-stores/file-system/presence.d.ts +37 -0
  13. package/dist/lock-stores/file-system/presence.d.ts.map +1 -0
  14. package/dist/lock-stores/file-system/presence.js +112 -0
  15. package/dist/lock-stores/file-system/presence.js.map +1 -0
  16. package/dist/lock-stores/file-system/ticket-queue-file-store.d.ts +1 -1
  17. package/dist/lock-stores/file-system/ticket-queue-file-store.d.ts.map +1 -1
  18. package/dist/lock-stores/file-system/ticket-queue-file-store.js +78 -50
  19. package/dist/lock-stores/file-system/ticket-queue-file-store.js.map +1 -1
  20. package/dist/shared/sqlite/is-cant-open.d.ts +3 -0
  21. package/dist/shared/sqlite/is-cant-open.d.ts.map +1 -0
  22. package/dist/shared/sqlite/is-cant-open.js +9 -0
  23. package/dist/shared/sqlite/is-cant-open.js.map +1 -0
  24. package/package.json +2 -1
  25. package/dist/lock-stores/file-system/owner.d.ts +0 -16
  26. package/dist/lock-stores/file-system/owner.d.ts.map +0 -1
  27. package/dist/lock-stores/file-system/owner.js +0 -47
  28. package/dist/lock-stores/file-system/owner.js.map +0 -1
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Identifies one request for a key and the process that made it. The process
3
+ * ID and host name are for people who read a lock file; whether the caller
4
+ * still runs is told by its presence.
5
+ */
6
+ export declare class Caller {
7
+ readonly pid: number;
8
+ readonly host: string;
9
+ readonly id: string;
10
+ constructor(pid: number, host: string, id: string);
11
+ static current(): Caller;
12
+ static parse(serialized: string): Caller;
13
+ serialize(): string;
14
+ }
15
+ //# sourceMappingURL=caller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"caller.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/file-system/caller.ts"],"names":[],"mappings":"AAKA;;;;GAIG;AACH,qBAAa,MAAM;IACjB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;gBAER,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM;IAMjD,MAAM,CAAC,OAAO,IAAI,MAAM;IAIxB,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM;IAaxC,SAAS,IAAI,MAAM;CAGpB"}
@@ -0,0 +1,35 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { hostname } from 'node:os';
3
+ import { isRecord } from "../../shared/is-record.js";
4
+ /**
5
+ * Identifies one request for a key and the process that made it. The process
6
+ * ID and host name are for people who read a lock file; whether the caller
7
+ * still runs is told by its presence.
8
+ */
9
+ export class Caller {
10
+ pid;
11
+ host;
12
+ id;
13
+ constructor(pid, host, id) {
14
+ this.pid = pid;
15
+ this.host = host;
16
+ this.id = id;
17
+ }
18
+ static current() {
19
+ return new Caller(process.pid, hostname(), randomUUID());
20
+ }
21
+ static parse(serialized) {
22
+ const parsed = JSON.parse(serialized);
23
+ if (!isRecord(parsed) ||
24
+ typeof parsed.pid !== 'number' ||
25
+ typeof parsed.host !== 'string' ||
26
+ typeof parsed.id !== 'string') {
27
+ throw new SyntaxError(`Not a lock caller: ${serialized}`);
28
+ }
29
+ return new Caller(parsed.pid, parsed.host, parsed.id);
30
+ }
31
+ serialize() {
32
+ return JSON.stringify({ pid: this.pid, host: this.host, id: this.id });
33
+ }
34
+ }
35
+ //# sourceMappingURL=caller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"caller.js","sourceRoot":"","sources":["../../../src/lock-stores/file-system/caller.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAErD;;;;GAIG;AACH,MAAM,OAAO,MAAM;IACR,GAAG,CAAS;IACZ,IAAI,CAAS;IACb,EAAE,CAAS;IAEpB,YAAY,GAAW,EAAE,IAAY,EAAE,EAAU;QAC/C,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;IACf,CAAC;IAED,MAAM,CAAC,OAAO;QACZ,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,UAAkB;QAC7B,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAC/C,IACE,CAAC,QAAQ,CAAC,MAAM,CAAC;YACjB,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ;YAC9B,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ;YAC/B,OAAO,MAAM,CAAC,EAAE,KAAK,QAAQ,EAC7B,CAAC;YACD,MAAM,IAAI,WAAW,CAAC,sBAAsB,UAAU,EAAE,CAAC,CAAC;QAC5D,CAAC;QACD,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;IACxD,CAAC;IAED,SAAS;QACP,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;IACzE,CAAC;CACF"}
@@ -10,8 +10,6 @@ export interface FileLockStoreOptions {
10
10
  export interface PollOptions {
11
11
  /** Stops between attempts when it aborts; the poll then rejects with `signal.reason`. */
12
12
  signal?: AbortSignal | undefined;
13
- /** Whether the wait between attempts keeps the process alive. */
14
- keepAlive?: boolean;
15
13
  }
16
14
  /** Shares one lock directory between processes: each key maps to `<directory>/<key>.lock`. */
17
15
  export declare abstract class FileLockStore implements LockStore {
@@ -23,7 +21,7 @@ export declare abstract class FileLockStore implements LockStore {
23
21
  protected abstract lock(path: string, signal: AbortSignal | undefined): Promise<AsyncDisposable>;
24
22
  /** Holds the lock at `path` only if no other holder has it now. */
25
23
  protected abstract tryLock(path: string): Promise<AsyncDisposable | undefined>;
26
- protected poll<T>(attempt: () => Promise<T | undefined>, { signal, keepAlive }?: PollOptions): Promise<T>;
24
+ protected poll<T>(attempt: () => Promise<T | undefined>, { signal }?: PollOptions): Promise<T>;
27
25
  /**
28
26
  * Serializes evicting a dead holder. Without it, two waiters that saw the
29
27
  * same dead holder could each remove a lock the other had just taken. The
@@ -1 +1 @@
1
- {"version":3,"file":"file-lock-store.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/file-system/file-lock-store.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAEjE,OAAO,EAAE,KAAK,KAAK,EAAY,MAAM,sBAAsB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAK3E,MAAM,WAAW,oBAAoB;IACnC,qDAAqD;IACrD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kEAAkE;IAClE,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,yFAAyF;IACzF,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IACjC,iEAAiE;IACjE,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,8FAA8F;AAC9F,8BAAsB,aAAc,YAAW,SAAS;;gBAMpD,SAAS,EAAE,MAAM,EACjB,EACE,YAAiB,EACjB,MAAuC,GACxC,GAAE,oBAAyB;IAOxB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,GAAE,cAAmB,GAAG,OAAO,CAAC,KAAK,CAAC;IAMrE,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC;IAKzD,kFAAkF;IAClF,SAAS,CAAC,QAAQ,CAAC,IAAI,CACrB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,WAAW,GAAG,SAAS,GAC9B,OAAO,CAAC,eAAe,CAAC;IAE3B,mEAAmE;IACnE,SAAS,CAAC,QAAQ,CAAC,OAAO,CACxB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC;cAEvB,IAAI,CAAC,CAAC,EACpB,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,EACrC,EAAE,MAAM,EAAE,SAAgB,EAAE,GAAE,WAAgB,GAC7C,OAAO,CAAC,CAAC,CAAC;IAab;;;;;;;;OAQG;cACa,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC;CAoBxE"}
1
+ {"version":3,"file":"file-lock-store.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/file-system/file-lock-store.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAEjE,OAAO,EAAE,KAAK,KAAK,EAAY,MAAM,sBAAsB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAK3E,MAAM,WAAW,oBAAoB;IACnC,qDAAqD;IACrD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kEAAkE;IAClE,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,yFAAyF;IACzF,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;CAClC;AAED,8FAA8F;AAC9F,8BAAsB,aAAc,YAAW,SAAS;;gBAMpD,SAAS,EAAE,MAAM,EACjB,EACE,YAAiB,EACjB,MAAuC,GACxC,GAAE,oBAAyB;IAOxB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,GAAE,cAAmB,GAAG,OAAO,CAAC,KAAK,CAAC;IAMrE,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC;IAKzD,kFAAkF;IAClF,SAAS,CAAC,QAAQ,CAAC,IAAI,CACrB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,WAAW,GAAG,SAAS,GAC9B,OAAO,CAAC,eAAe,CAAC;IAE3B,mEAAmE;IACnE,SAAS,CAAC,QAAQ,CAAC,OAAO,CACxB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC;cAEvB,IAAI,CAAC,CAAC,EACpB,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,EACrC,EAAE,MAAM,EAAE,GAAE,WAAgB,GAC3B,OAAO,CAAC,CAAC,CAAC;IAab;;;;;;;;OAQG;cACa,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC;CAoBxE"}
@@ -27,13 +27,13 @@ export class FileLockStore {
27
27
  const held = await this.tryLock(await this.#pathFor(key));
28
28
  return held && leaseFor(key, held, this.#tokens);
29
29
  }
30
- async poll(attempt, { signal, keepAlive = true } = {}) {
30
+ async poll(attempt, { signal } = {}) {
31
31
  for (;;) {
32
32
  const result = await attempt();
33
33
  if (result !== undefined)
34
34
  return result;
35
35
  try {
36
- await delay(this.#pollInterval, undefined, { signal, ref: keepAlive });
36
+ await delay(this.#pollInterval, undefined, { signal });
37
37
  }
38
38
  catch (error) {
39
39
  signal?.throwIfAborted();
@@ -1 +1 @@
1
- {"version":3,"file":"file-lock-store.js","sourceRoot":"","sources":["../../../src/lock-stores/file-system/file-lock-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"file-lock-store.js","sourceRoot":"","sources":["../../../src/lock-stores/file-system/file-lock-store.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"lock-file-store.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/file-system/lock-file-store.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGrD;;;GAGG;AACH,qBAAa,aAAc,SAAQ,aAAa;;IAC9C,SAAS,CAAC,IAAI,CACZ,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,WAAW,GAAG,SAAS,GAC9B,OAAO,CAAC,eAAe,CAAC;cAKX,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC;CAwB5E"}
1
+ {"version":3,"file":"lock-file-store.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/file-system/lock-file-store.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGrD;;;GAGG;AACH,qBAAa,aAAc,SAAQ,aAAa;;IAC9C,SAAS,CAAC,IAAI,CACZ,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,WAAW,GAAG,SAAS,GAC9B,OAAO,CAAC,eAAe,CAAC;cAKX,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC;CAmD5E"}
@@ -2,35 +2,61 @@ import { readFile, unlink } from 'node:fs/promises';
2
2
  import { createExclusive } from "../../shared/fs/create-exclusive.js";
3
3
  import { isErrno } from "../../shared/fs/errno.js";
4
4
  import { patiently } from "../../shared/fs/patiently.js";
5
+ import { Caller } from "./caller.js";
5
6
  import { FileLockStore } from "./file-lock-store.js";
6
- import { Owner } from "./owner.js";
7
+ import { Presence } from "./presence.js";
7
8
  /**
8
9
  * Whoever creates the lock file holds the key; releasing deletes it. Waiters
9
10
  * retry in no particular order, so this store is not FIFO.
10
11
  */
11
12
  export class LockFileStore extends FileLockStore {
12
13
  lock(path, signal) {
13
- const me = Owner.current();
14
+ const me = Caller.current();
14
15
  return this.poll(() => this.#attempt(path, me), { signal });
15
16
  }
16
17
  async tryLock(path) {
17
- const me = Owner.current();
18
- // An attempt that finds a dead holder removes it, so one more attempt can then succeed.
18
+ const me = Caller.current();
19
+ // An attempt that finds a gone holder removes it, so one more attempt can then succeed.
19
20
  return (await this.#attempt(path, me)) ?? this.#attempt(path, me);
20
21
  }
22
+ /** A waiter is present only while it tries to create the file, so giving up leaves nothing behind. */
21
23
  async #attempt(path, me) {
22
- if (await createExclusive(path, me.serialize())) {
23
- return { [Symbol.asyncDispose]: () => removeLockFile(path) };
24
- }
25
24
  const holder = await readHolder(path);
26
- if (holder && !holder.isAlive()) {
27
- await this.withReclaimLock(path, async () => {
28
- if ((await readHolder(path))?.id === holder.id) {
29
- await removeLockFile(path);
30
- }
31
- });
25
+ if (holder) {
26
+ await this.#evictIfGone(path, holder);
27
+ return undefined;
28
+ }
29
+ const presence = Presence.claim(Presence.pathOf(path, me));
30
+ const created = await createExclusive(path, me.serialize()).catch(async (error) => {
31
+ await presence.withdraw();
32
+ throw error;
33
+ });
34
+ if (!created) {
35
+ await presence.withdraw();
36
+ return undefined;
37
+ }
38
+ return {
39
+ [Symbol.asyncDispose]: () => presence.releaseAfter(() => removeLockFile(path)),
40
+ };
41
+ }
42
+ async #evictIfGone(path, holder) {
43
+ const presence = Presence.pathOf(path, holder);
44
+ const state = Presence.check(presence);
45
+ if (state === 'present')
46
+ return;
47
+ if (state === 'missing') {
48
+ // A release removes the record before the presence file, so a record still in place never had one.
49
+ if ((await readHolder(path))?.id === holder.id) {
50
+ throw Presence.missing(path, presence);
51
+ }
52
+ return;
32
53
  }
33
- return undefined;
54
+ await this.withReclaimLock(path, async () => {
55
+ if ((await readHolder(path))?.id !== holder.id)
56
+ return;
57
+ await removeLockFile(path);
58
+ await Presence.delete(presence);
59
+ });
34
60
  }
35
61
  }
36
62
  function removeLockFile(path) {
@@ -38,7 +64,7 @@ function removeLockFile(path) {
38
64
  }
39
65
  async function readHolder(path) {
40
66
  try {
41
- return Owner.parse(await patiently(() => readFile(path, 'utf8')));
67
+ return Caller.parse(await patiently(() => readFile(path, 'utf8')));
42
68
  }
43
69
  catch (error) {
44
70
  if (isErrno(error, 'ENOENT'))
@@ -1 +1 @@
1
- {"version":3,"file":"lock-file-store.js","sourceRoot":"","sources":["../../../src/lock-stores/file-system/lock-file-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAEpD,OAAO,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAC;AACtE,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC;;;GAGG;AACH,MAAM,OAAO,aAAc,SAAQ,aAAa;IACpC,IAAI,CACZ,IAAY,EACZ,MAA+B;QAE/B,MAAM,EAAE,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC;QAC3B,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IAC9D,CAAC;IAES,KAAK,CAAC,OAAO,CAAC,IAAY;QAClC,MAAM,EAAE,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC;QAC3B,wFAAwF;QACxF,OAAO,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,KAAK,CAAC,QAAQ,CACZ,IAAY,EACZ,EAAS;QAET,IAAI,MAAM,eAAe,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,EAAE,CAAC,EAAE,CAAC;YAChD,OAAO,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/D,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC;YAChC,MAAM,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,KAAK,IAAI,EAAE;gBAC1C,IAAI,CAAC,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,KAAK,MAAM,CAAC,EAAE,EAAE,CAAC;oBAC/C,MAAM,cAAc,CAAC,IAAI,CAAC,CAAC;gBAC7B,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;CACF;AAED,SAAS,cAAc,CAAC,IAAY;IAClC,OAAO,SAAS,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;AACvC,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,IAAY;IACpC,IAAI,CAAC;QACH,OAAO,KAAK,CAAC,KAAK,CAAC,MAAM,SAAS,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;IACpE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC;YAAE,OAAO,SAAS,CAAC;QAC/C,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"lock-file-store.js","sourceRoot":"","sources":["../../../src/lock-stores/file-system/lock-file-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import type { Caller } from './caller.ts';
2
+ /**
3
+ * A caller's presence: an exclusive SQLite transaction on a file of its own,
4
+ * held from before the caller's record (a lock file or a ticket) appears until
5
+ * after it is gone. The kernel ends it when the caller's process or thread
6
+ * stops, however it stops, so a waiter that can read the file knows the caller
7
+ * is gone. Nothing may open the file except through SQLite: closing any other
8
+ * handle to it drops this process's lock without a word.
9
+ */
10
+ export declare class Presence {
11
+ #private;
12
+ private constructor();
13
+ /** The presence file of `caller`, whose record is at `record`. */
14
+ static pathOf(record: string, caller: Caller): string;
15
+ static claim(path: string): Presence;
16
+ /**
17
+ * Whether the caller behind `path` still runs. Reading needs a lock that the
18
+ * caller's exclusive transaction refuses, and opening read-only never creates
19
+ * the file.
20
+ */
21
+ static check(path: string): 'present' | 'gone' | 'missing';
22
+ /** Deletes the file of a presence that has ended. */
23
+ static delete(path: string): Promise<void>;
24
+ /** A record names a caller whose presence file does not exist, so nobody can tell whether it runs. */
25
+ static missing(record: string, path: string): Error;
26
+ /** Lets the kernel lock go. The file stays for whoever removes the caller's record. */
27
+ end(): void;
28
+ /**
29
+ * Removes the caller's record, then ends this presence and deletes its file.
30
+ * A record that cannot be removed stays, but this presence ends anyway, so
31
+ * the record reads as gone and a waiter evicts it.
32
+ */
33
+ releaseAfter(removeRecord: () => Promise<void>): Promise<void>;
34
+ /** Ends a presence whose record never appeared, or is gone already. */
35
+ withdraw(): Promise<void>;
36
+ }
37
+ //# sourceMappingURL=presence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presence.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/file-system/presence.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAK1C;;;;;;;GAOG;AACH,qBAAa,QAAQ;;IAInB,OAAO;IAKP,kEAAkE;IAClE,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM;IAIrD,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ;IAcpC;;;;OAIG;IACH,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS;IAoB1D,qDAAqD;WACxC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWhD,sGAAsG;IACtG,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,KAAK;IAMnD,uFAAuF;IACvF,GAAG,IAAI,IAAI;IAMX;;;;OAIG;IACG,YAAY,CAAC,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAWpE,uEAAuE;IACjE,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAIhC"}
@@ -0,0 +1,112 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { unlink } from 'node:fs/promises';
3
+ import { DatabaseSync } from 'node:sqlite';
4
+ import { patiently } from "../../shared/fs/patiently.js";
5
+ import { isBusy } from "../../shared/sqlite/is-busy.js";
6
+ import { isCantOpen } from "../../shared/sqlite/is-cant-open.js";
7
+ /** Garbage collection closes a connection nobody references, which would end a presence its caller still holds. */
8
+ const open = new Set();
9
+ /**
10
+ * A caller's presence: an exclusive SQLite transaction on a file of its own,
11
+ * held from before the caller's record (a lock file or a ticket) appears until
12
+ * after it is gone. The kernel ends it when the caller's process or thread
13
+ * stops, however it stops, so a waiter that can read the file knows the caller
14
+ * is gone. Nothing may open the file except through SQLite: closing any other
15
+ * handle to it drops this process's lock without a word.
16
+ */
17
+ export class Presence {
18
+ #path;
19
+ #database;
20
+ constructor(path, database) {
21
+ this.#path = path;
22
+ this.#database = database;
23
+ }
24
+ /** The presence file of `caller`, whose record is at `record`. */
25
+ static pathOf(record, caller) {
26
+ return `${record}.${caller.id}.presence`;
27
+ }
28
+ static claim(path) {
29
+ const database = new DatabaseSync(path, { timeout: 0 });
30
+ try {
31
+ // A journal on disk would outlive a caller that dies while present.
32
+ database.exec('PRAGMA journal_mode = MEMORY');
33
+ database.exec('BEGIN EXCLUSIVE');
34
+ }
35
+ catch (error) {
36
+ database.close();
37
+ throw error;
38
+ }
39
+ open.add(database);
40
+ return new Presence(path, database);
41
+ }
42
+ /**
43
+ * Whether the caller behind `path` still runs. Reading needs a lock that the
44
+ * caller's exclusive transaction refuses, and opening read-only never creates
45
+ * the file.
46
+ */
47
+ static check(path) {
48
+ let database;
49
+ try {
50
+ database = new DatabaseSync(path, { readOnly: true, timeout: 0 });
51
+ }
52
+ catch (error) {
53
+ // A file that exists but cannot be opened is a fault, not a missing presence.
54
+ if (isCantOpen(error) && !existsSync(path))
55
+ return 'missing';
56
+ throw error;
57
+ }
58
+ try {
59
+ database.prepare('SELECT count(*) FROM sqlite_schema').get();
60
+ return 'gone';
61
+ }
62
+ catch (error) {
63
+ if (isBusy(error))
64
+ return 'present';
65
+ throw error;
66
+ }
67
+ finally {
68
+ database.close();
69
+ }
70
+ }
71
+ /** Deletes the file of a presence that has ended. */
72
+ static async delete(path) {
73
+ try {
74
+ await patiently(() => unlink(path));
75
+ }
76
+ catch (error) {
77
+ throw new Error(`The presence ${JSON.stringify(path)} has ended, but its file could not be deleted. The file locks nothing; delete it by hand.`, { cause: error });
78
+ }
79
+ }
80
+ /** A record names a caller whose presence file does not exist, so nobody can tell whether it runs. */
81
+ static missing(record, path) {
82
+ return new Error(`The lock record ${JSON.stringify(record)} names a caller with no presence file ${JSON.stringify(path)}. An older version of @zukhruf/mutex wrote the record, or someone deleted the file. Stop every process that uses this lock directory, then delete the record.`);
83
+ }
84
+ /** Lets the kernel lock go. The file stays for whoever removes the caller's record. */
85
+ end() {
86
+ open.delete(this.#database);
87
+ this.#database.exec('ROLLBACK');
88
+ this.#database.close();
89
+ }
90
+ /**
91
+ * Removes the caller's record, then ends this presence and deletes its file.
92
+ * A record that cannot be removed stays, but this presence ends anyway, so
93
+ * the record reads as gone and a waiter evicts it.
94
+ */
95
+ async releaseAfter(removeRecord) {
96
+ try {
97
+ await removeRecord();
98
+ }
99
+ catch (error) {
100
+ this.end();
101
+ throw error;
102
+ }
103
+ this.end();
104
+ await Presence.delete(this.#path);
105
+ }
106
+ /** Ends a presence whose record never appeared, or is gone already. */
107
+ async withdraw() {
108
+ this.end();
109
+ await Presence.delete(this.#path);
110
+ }
111
+ }
112
+ //# sourceMappingURL=presence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presence.js","sourceRoot":"","sources":["../../../src/lock-stores/file-system/presence.ts"],"names":[],"mappings":"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"}
@@ -4,7 +4,7 @@ import { FileLockStore } from './file-lock-store.ts';
4
4
  * lock once its ticket is first.
5
5
  *
6
6
  * Appends are atomic (O_APPEND), and the file is only ever replaced by the
7
- * live head releasing or by the reclaim-lock holder evicting a dead head, so
7
+ * live head releasing or by the reclaim-lock holder evicting a gone head, so
8
8
  * two rewrites never race. A rewrite racing an append can drop that ticket;
9
9
  * its waiter notices and appends it again.
10
10
  */
@@ -1 +1 @@
1
- {"version":3,"file":"ticket-queue-file-store.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/file-system/ticket-queue-file-store.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGrD;;;;;;;;GAQG;AACH,qBAAa,oBAAqB,SAAQ,aAAa;;cACrC,IAAI,CAClB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,WAAW,GAAG,SAAS,GAC9B,OAAO,CAAC,eAAe,CAAC;cAWX,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC;CA6D5E"}
1
+ {"version":3,"file":"ticket-queue-file-store.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/file-system/ticket-queue-file-store.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGrD;;;;;;;;GAQG;AACH,qBAAa,oBAAqB,SAAQ,aAAa;;cACrC,IAAI,CAClB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,WAAW,GAAG,SAAS,GAC9B,OAAO,CAAC,eAAe,CAAC;cAWX,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC;CAoD5E"}
@@ -2,47 +2,46 @@ import { appendFile, readFile } from 'node:fs/promises';
2
2
  import { atomicWrite } from "../../shared/fs/atomic-write.js";
3
3
  import { isErrno } from "../../shared/fs/errno.js";
4
4
  import { patiently } from "../../shared/fs/patiently.js";
5
+ import { Caller } from "./caller.js";
5
6
  import { FileLockStore } from "./file-lock-store.js";
6
- import { Owner } from "./owner.js";
7
+ import { Presence } from "./presence.js";
7
8
  /**
8
9
  * FIFO queue in one file: each waiter appends its ticket line and holds the
9
10
  * lock once its ticket is first.
10
11
  *
11
12
  * Appends are atomic (O_APPEND), and the file is only ever replaced by the
12
- * live head releasing or by the reclaim-lock holder evicting a dead head, so
13
+ * live head releasing or by the reclaim-lock holder evicting a gone head, so
13
14
  * two rewrites never race. A rewrite racing an append can drop that ticket;
14
15
  * its waiter notices and appends it again.
15
16
  */
16
17
  export class TicketQueueFileStore extends FileLockStore {
17
18
  async lock(path, signal) {
18
- const me = Owner.current();
19
+ const me = Caller.current();
20
+ await using leaving = new AsyncDisposableStack();
21
+ const presence = Presence.claim(Presence.pathOf(path, me));
22
+ leaving.defer(() => leave(path, me, presence));
19
23
  await enqueue(path, me);
20
- try {
21
- return await this.poll(() => this.#attempt(path, me), { signal });
22
- }
23
- catch (error) {
24
- this.#leaveWhenFirst(path, me);
25
- throw error;
26
- }
24
+ await this.poll(() => this.#attempt(path, me), { signal });
25
+ leaving.move();
26
+ return holding(path, me, presence);
27
27
  }
28
28
  async tryLock(path) {
29
29
  const [head] = await readTickets(path);
30
- await this.#evictIfDead(path, head);
30
+ if (head)
31
+ await this.#evictGoneHeads(path, head);
31
32
  if ((await readTickets(path)).length > 0)
32
33
  return undefined;
33
- const me = Owner.current();
34
+ const me = Caller.current();
35
+ await using leaving = new AsyncDisposableStack();
36
+ const presence = Presence.claim(Presence.pathOf(path, me));
37
+ leaving.defer(() => leave(path, me, presence));
34
38
  await enqueue(path, me);
35
39
  const [first] = await readTickets(path);
36
- if (first?.id === me.id)
37
- return holding(path, me);
38
40
  // Another waiter appended at the same moment and is first.
39
- this.#leaveWhenFirst(path, me);
40
- return undefined;
41
- }
42
- async #evictIfDead(path, head) {
43
- if (head && !head.isAlive()) {
44
- await this.withReclaimLock(path, () => evictDeadHead(path));
45
- }
41
+ if (first?.id !== me.id)
42
+ return undefined;
43
+ leaving.move();
44
+ return holding(path, me, presence);
46
45
  }
47
46
  async #attempt(path, me) {
48
47
  const tickets = await readTickets(path);
@@ -52,53 +51,82 @@ export class TicketQueueFileStore extends FileLockStore {
52
51
  }
53
52
  const [head] = tickets;
54
53
  if (head?.id === me.id)
55
- return holding(path, me);
56
- await this.#evictIfDead(path, head);
54
+ return true;
55
+ if (head)
56
+ await this.#evictGoneHeads(path, head);
57
57
  return undefined;
58
58
  }
59
59
  /**
60
- * A waiter that gives up cannot remove its ticket: only the head may
61
- * rewrite the queue. So its ticket stays in line, and this removes it when
62
- * it reaches the head. The wait does not keep the process alive; if the
63
- * process stops first, waiters evict the ticket as a dead head.
60
+ * Evicts the run of gone callers at the head of the queue. Each caller is
61
+ * found gone before the queue is read: a gone caller never writes again, so
62
+ * a queue that still starts with it is current, and writing it back cannot
63
+ * drop a later holder's ticket.
64
64
  */
65
- #leaveWhenFirst(path, me) {
66
- this.poll(async () => {
67
- const tickets = await readTickets(path);
68
- if (!tickets.some((ticket) => ticket.id === me.id))
69
- return true;
70
- const [head] = tickets;
71
- if (head?.id === me.id) {
72
- await removeTicket(path, me);
73
- return true;
65
+ async #evictGoneHeads(path, head) {
66
+ if (!(await isGone(path, head)))
67
+ return;
68
+ await this.withReclaimLock(path, async () => {
69
+ let gone = head;
70
+ for (;;) {
71
+ const tickets = await readTickets(path);
72
+ if (tickets[0]?.id !== gone.id)
73
+ return;
74
+ const rest = tickets.filter((ticket) => ticket.id !== gone.id);
75
+ await replace(path, rest);
76
+ await Presence.delete(Presence.pathOf(path, gone));
77
+ const [next] = rest;
78
+ if (!next || !(await isGone(path, next)))
79
+ return;
80
+ gone = next;
74
81
  }
75
- await this.#evictIfDead(path, head);
76
- return undefined;
77
- }, { keepAlive: false }).catch(() => {
78
- // The directory is gone, so the ticket is gone too.
79
82
  });
80
83
  }
81
84
  }
82
- function holding(path, me) {
83
- return { [Symbol.asyncDispose]: () => removeTicket(path, me) };
85
+ /** Whether the caller at the head of the queue at `path` no longer runs. */
86
+ async function isGone(path, head) {
87
+ const presence = Presence.pathOf(path, head);
88
+ const state = Presence.check(presence);
89
+ if (state === 'missing') {
90
+ // A ticket leaves the queue before its presence file, so a ticket still first never had one.
91
+ if ((await readTickets(path))[0]?.id === head.id) {
92
+ throw Presence.missing(path, presence);
93
+ }
94
+ return false;
95
+ }
96
+ return state === 'gone';
97
+ }
98
+ /**
99
+ * A waiter that gives up keeps its ticket in line: only the head may rewrite
100
+ * the queue. Its presence ends, so the ticket reads as gone and whoever finds
101
+ * it first evicts it, together with the presence file.
102
+ */
103
+ async function leave(path, me, presence) {
104
+ const tickets = await readTickets(path).catch((error) => {
105
+ presence.end();
106
+ throw error;
107
+ });
108
+ if (tickets.some((ticket) => ticket.id === me.id))
109
+ presence.end();
110
+ else
111
+ await presence.withdraw();
112
+ }
113
+ function holding(path, me, presence) {
114
+ return {
115
+ [Symbol.asyncDispose]: () => presence.releaseAfter(() => removeTicket(path, me)),
116
+ };
84
117
  }
85
118
  async function removeTicket(path, me) {
86
119
  const remaining = await readTickets(path);
87
120
  await replace(path, remaining.filter((ticket) => ticket.id !== me.id));
88
121
  }
89
- async function evictDeadHead(path) {
90
- const [head, ...rest] = await readTickets(path);
91
- if (head && !head.isAlive())
92
- await replace(path, rest);
93
- }
94
- async function enqueue(path, owner) {
95
- await patiently(() => appendFile(path, `${owner.serialize()}\n`));
122
+ async function enqueue(path, caller) {
123
+ await patiently(() => appendFile(path, `${caller.serialize()}\n`));
96
124
  }
97
125
  async function readTickets(path) {
98
126
  try {
99
127
  const content = await patiently(() => readFile(path, 'utf8'));
100
128
  // The last segment is empty or a ticket still being appended.
101
- return content.split('\n').slice(0, -1).map(Owner.parse);
129
+ return content.split('\n').slice(0, -1).map(Caller.parse);
102
130
  }
103
131
  catch (error) {
104
132
  if (isErrno(error, 'ENOENT'))
@@ -1 +1 @@
1
- {"version":3,"file":"ticket-queue-file-store.js","sourceRoot":"","sources":["../../../src/lock-stores/file-system/ticket-queue-file-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ticket-queue-file-store.js","sourceRoot":"","sources":["../../../src/lock-stores/file-system/ticket-queue-file-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ /** Whether `error` means SQLite could not open the file, for example because it does not exist. */
2
+ export declare function isCantOpen(error: unknown): boolean;
3
+ //# sourceMappingURL=is-cant-open.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"is-cant-open.d.ts","sourceRoot":"","sources":["../../../src/shared/sqlite/is-cant-open.ts"],"names":[],"mappings":"AAEA,mGAAmG;AACnG,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAOlD"}
@@ -0,0 +1,9 @@
1
+ const SQLITE_CANTOPEN = 14;
2
+ /** Whether `error` means SQLite could not open the file, for example because it does not exist. */
3
+ export function isCantOpen(error) {
4
+ return (error instanceof Error &&
5
+ 'errcode' in error &&
6
+ typeof error.errcode === 'number' &&
7
+ (error.errcode & 0xff) === SQLITE_CANTOPEN);
8
+ }
9
+ //# sourceMappingURL=is-cant-open.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"is-cant-open.js","sourceRoot":"","sources":["../../../src/shared/sqlite/is-cant-open.ts"],"names":[],"mappings":"AAAA,MAAM,eAAe,GAAG,EAAE,CAAC;AAE3B,mGAAmG;AACnG,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,OAAO,CACL,KAAK,YAAY,KAAK;QACtB,SAAS,IAAI,KAAK;QAClB,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ;QACjC,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,KAAK,eAAe,CAC3C,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zukhruf/mutex",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "A mutex with interchangeable lock stores, from one object to every process on a host, with fencing tokens on every lease.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -47,6 +47,7 @@
47
47
  "access": "public"
48
48
  },
49
49
  "devDependencies": {
50
+ "@zukhruf/testing": "^0.3.1",
50
51
  "hono": "^4.13.13"
51
52
  }
52
53
  }
@@ -1,16 +0,0 @@
1
- /**
2
- * Identifies one lock request and the process that made it, so waiters can
3
- * evict a holder whose process died. A reused pid only delays recovery; it
4
- * never breaks exclusion. Holders on another host are never evicted.
5
- */
6
- export declare class Owner {
7
- readonly pid: number;
8
- readonly host: string;
9
- readonly id: string;
10
- constructor(pid: number, host: string, id: string);
11
- static current(): Owner;
12
- static parse(serialized: string): Owner;
13
- serialize(): string;
14
- isAlive(): boolean;
15
- }
16
- //# sourceMappingURL=owner.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"owner.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/file-system/owner.ts"],"names":[],"mappings":"AAMA;;;;GAIG;AACH,qBAAa,KAAK;IAChB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;gBAER,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM;IAMjD,MAAM,CAAC,OAAO,IAAI,KAAK;IAIvB,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,KAAK;IAavC,SAAS,IAAI,MAAM;IAInB,OAAO,IAAI,OAAO;CASnB"}
@@ -1,47 +0,0 @@
1
- import { randomUUID } from 'node:crypto';
2
- import { hostname } from 'node:os';
3
- import { isErrno } from "../../shared/fs/errno.js";
4
- import { isRecord } from "../../shared/is-record.js";
5
- /**
6
- * Identifies one lock request and the process that made it, so waiters can
7
- * evict a holder whose process died. A reused pid only delays recovery; it
8
- * never breaks exclusion. Holders on another host are never evicted.
9
- */
10
- export class Owner {
11
- pid;
12
- host;
13
- id;
14
- constructor(pid, host, id) {
15
- this.pid = pid;
16
- this.host = host;
17
- this.id = id;
18
- }
19
- static current() {
20
- return new Owner(process.pid, hostname(), randomUUID());
21
- }
22
- static parse(serialized) {
23
- const parsed = JSON.parse(serialized);
24
- if (!isRecord(parsed) ||
25
- typeof parsed.pid !== 'number' ||
26
- typeof parsed.host !== 'string' ||
27
- typeof parsed.id !== 'string') {
28
- throw new SyntaxError(`Not a lock owner: ${serialized}`);
29
- }
30
- return new Owner(parsed.pid, parsed.host, parsed.id);
31
- }
32
- serialize() {
33
- return JSON.stringify({ pid: this.pid, host: this.host, id: this.id });
34
- }
35
- isAlive() {
36
- if (this.host !== hostname())
37
- return true;
38
- try {
39
- process.kill(this.pid, 0);
40
- return true;
41
- }
42
- catch (error) {
43
- return !isErrno(error, 'ESRCH');
44
- }
45
- }
46
- }
47
- //# sourceMappingURL=owner.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"owner.js","sourceRoot":"","sources":["../../../src/lock-stores/file-system/owner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAErD;;;;GAIG;AACH,MAAM,OAAO,KAAK;IACP,GAAG,CAAS;IACZ,IAAI,CAAS;IACb,EAAE,CAAS;IAEpB,YAAY,GAAW,EAAE,IAAY,EAAE,EAAU;QAC/C,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;IACf,CAAC;IAED,MAAM,CAAC,OAAO;QACZ,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC;IAC1D,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,UAAkB;QAC7B,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAC/C,IACE,CAAC,QAAQ,CAAC,MAAM,CAAC;YACjB,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ;YAC9B,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ;YAC/B,OAAO,MAAM,CAAC,EAAE,KAAK,QAAQ,EAC7B,CAAC;YACD,MAAM,IAAI,WAAW,CAAC,qBAAqB,UAAU,EAAE,CAAC,CAAC;QAC3D,CAAC;QACD,OAAO,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;IACvD,CAAC;IAED,SAAS;QACP,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;IACzE,CAAC;IAED,OAAO;QACL,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE;YAAE,OAAO,IAAI,CAAC;QAC1C,IAAI,CAAC;YACH,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YAC1B,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;CACF"}