@zukhruf/mutex 0.3.40 → 0.3.42

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 (57) hide show
  1. package/README.md +18 -10
  2. package/dist/lock-stores/docker/docker-engine-store.d.ts +39 -0
  3. package/dist/lock-stores/docker/docker-engine-store.d.ts.map +1 -0
  4. package/dist/lock-stores/docker/docker-engine-store.js +339 -0
  5. package/dist/lock-stores/docker/docker-engine-store.js.map +1 -0
  6. package/dist/lock-stores/docker/index.d.ts +2 -0
  7. package/dist/lock-stores/docker/index.d.ts.map +1 -0
  8. package/dist/lock-stores/docker/index.js +2 -0
  9. package/dist/lock-stores/docker/index.js.map +1 -0
  10. package/dist/lock-stores/mariadb/index.d.ts +2 -0
  11. package/dist/lock-stores/mariadb/index.d.ts.map +1 -0
  12. package/dist/lock-stores/mariadb/index.js +2 -0
  13. package/dist/lock-stores/mariadb/index.js.map +1 -0
  14. package/dist/lock-stores/mariadb/mariadb-store.d.ts +33 -0
  15. package/dist/lock-stores/mariadb/mariadb-store.d.ts.map +1 -0
  16. package/dist/lock-stores/mariadb/mariadb-store.js +36 -0
  17. package/dist/lock-stores/mariadb/mariadb-store.js.map +1 -0
  18. package/dist/lock-stores/mysql/get-lock-dialect.d.ts +46 -0
  19. package/dist/lock-stores/mysql/get-lock-dialect.d.ts.map +1 -0
  20. package/dist/lock-stores/mysql/get-lock-dialect.js +80 -0
  21. package/dist/lock-stores/mysql/get-lock-dialect.js.map +1 -0
  22. package/dist/lock-stores/mysql/index.d.ts +2 -0
  23. package/dist/lock-stores/mysql/index.d.ts.map +1 -0
  24. package/dist/lock-stores/mysql/index.js +2 -0
  25. package/dist/lock-stores/mysql/index.js.map +1 -0
  26. package/dist/lock-stores/mysql/mysql-store.d.ts +37 -0
  27. package/dist/lock-stores/mysql/mysql-store.d.ts.map +1 -0
  28. package/dist/lock-stores/mysql/mysql-store.js +35 -0
  29. package/dist/lock-stores/mysql/mysql-store.js.map +1 -0
  30. package/dist/lock-stores/postgres/postgres-store.d.ts +2 -8
  31. package/dist/lock-stores/postgres/postgres-store.d.ts.map +1 -1
  32. package/dist/lock-stores/postgres/postgres-store.js +55 -226
  33. package/dist/lock-stores/postgres/postgres-store.js.map +1 -1
  34. package/dist/lock-stores/session/session-lock-store.d.ts +72 -0
  35. package/dist/lock-stores/session/session-lock-store.d.ts.map +1 -0
  36. package/dist/lock-stores/session/session-lock-store.js +221 -0
  37. package/dist/lock-stores/session/session-lock-store.js.map +1 -0
  38. package/dist/lock-stores/sqlite/sqlite-store.d.ts.map +1 -1
  39. package/dist/lock-stores/sqlite/sqlite-store.js +6 -25
  40. package/dist/lock-stores/sqlite/sqlite-store.js.map +1 -1
  41. package/dist/lock-stores/sqlserver/index.d.ts +2 -0
  42. package/dist/lock-stores/sqlserver/index.d.ts.map +1 -0
  43. package/dist/lock-stores/sqlserver/index.js +2 -0
  44. package/dist/lock-stores/sqlserver/index.js.map +1 -0
  45. package/dist/lock-stores/sqlserver/sqlserver-store.d.ts +40 -0
  46. package/dist/lock-stores/sqlserver/sqlserver-store.d.ts.map +1 -0
  47. package/dist/lock-stores/sqlserver/sqlserver-store.js +104 -0
  48. package/dist/lock-stores/sqlserver/sqlserver-store.js.map +1 -0
  49. package/dist/shared/in-process-turns.d.ts +17 -0
  50. package/dist/shared/in-process-turns.d.ts.map +1 -0
  51. package/dist/shared/in-process-turns.js +39 -0
  52. package/dist/shared/in-process-turns.js.map +1 -0
  53. package/dist/shared/key-digest.d.ts +8 -0
  54. package/dist/shared/key-digest.d.ts.map +1 -0
  55. package/dist/shared/key-digest.js +11 -0
  56. package/dist/shared/key-digest.js.map +1 -0
  57. package/package.json +21 -8
@@ -0,0 +1,221 @@
1
+ import { setTimeout as delay } from 'node:timers/promises';
2
+ import { untilAborted } from '@zukhruf/async';
3
+ import { FencingToken } from '@zukhruf/fencing';
4
+ import { LeaseController } from '@zukhruf/lease';
5
+ import { InProcessTurns } from "../../shared/in-process-turns.js";
6
+ /**
7
+ * Holds a session-level lock of a SQL server for each key, on a session of
8
+ * its own. The server releases the lock when the session ends, so a holder
9
+ * that stops never leaves a key locked. Every process on every machine that
10
+ * reaches one database shares the locks.
11
+ *
12
+ * Callers in this process first line up in a queue in memory, so only the
13
+ * first one gets a session: one session per key per process, however many
14
+ * callers wait, and first-come order within the process.
15
+ */
16
+ export class SessionLockStore {
17
+ #turns = new InProcessTurns();
18
+ #options;
19
+ #dialect;
20
+ constructor(options, dialect) {
21
+ this.#options = options;
22
+ this.#dialect = dialect;
23
+ }
24
+ acquire(key, options) {
25
+ return this.#turns.acquire(key, options, async () => {
26
+ const signal = options?.signal;
27
+ const session = await this.#session(key, signal);
28
+ await session.wait(signal);
29
+ return session.hold();
30
+ });
31
+ }
32
+ tryAcquire(key) {
33
+ return this.#turns.tryAcquire(key, async () => {
34
+ const session = await this.#session(key, undefined);
35
+ if (!(await session.try()))
36
+ return undefined;
37
+ return session.hold();
38
+ });
39
+ }
40
+ /** Asks the server on a short session, and never acquires the lock. */
41
+ async isHeld(key) {
42
+ const dialect = this.#dialect;
43
+ const session = new OwnedSession(await this.#options.connect(), dialect);
44
+ try {
45
+ const held = await dialect.isHeld(session.session, {
46
+ key,
47
+ name: dialect.lockName(key),
48
+ });
49
+ session.giveBack();
50
+ return held;
51
+ }
52
+ catch (error) {
53
+ session.destroy();
54
+ throw error;
55
+ }
56
+ }
57
+ /** Gets a session for `key`. A cancel never waits for `connect`: a session that comes later is given back at once. */
58
+ async #session(key, signal) {
59
+ const dialect = this.#dialect;
60
+ const connecting = this.#options.connect();
61
+ try {
62
+ return new KeySession(new OwnedSession(await untilAborted(connecting, signal), dialect), key, this.#options.heartbeatInterval, dialect);
63
+ }
64
+ catch (error) {
65
+ if (signal?.aborted && error === signal.reason) {
66
+ void connecting.then((late) => dialect.giveBack(late), () => { });
67
+ }
68
+ throw error;
69
+ }
70
+ }
71
+ }
72
+ /**
73
+ * A session from `connect` that the store owns until it gives it back or
74
+ * destroys it, once. Its `ended` signal aborts when the session emits `error`
75
+ * or `end`. Its listeners stay on a destroyed session, because a driver can
76
+ * emit `error` after the close.
77
+ */
78
+ class OwnedSession {
79
+ session;
80
+ #dialect;
81
+ #ended = new AbortController();
82
+ #owned = new AbortController();
83
+ #listeners = {
84
+ error: (error) => this.#ended.abort(error),
85
+ end: () => this.#ended.abort(new Error(`The ${this.#dialect.server} session ended.`)),
86
+ };
87
+ constructor(session, dialect) {
88
+ this.session = session;
89
+ this.#dialect = dialect;
90
+ session.on('error', this.#listeners.error);
91
+ session.on('end', this.#listeners.end);
92
+ }
93
+ get ended() {
94
+ return this.#ended.signal;
95
+ }
96
+ /** The session holds nothing. */
97
+ giveBack() {
98
+ if (this.#owned.signal.aborted)
99
+ return;
100
+ this.#owned.abort();
101
+ this.#dialect.detach(this.session, this.#listeners);
102
+ this.#dialect.giveBack(this.session);
103
+ }
104
+ /** The lock state of the session is not known: the server releases each lock of a session that ends. */
105
+ destroy() {
106
+ if (this.#owned.signal.aborted)
107
+ return;
108
+ this.#owned.abort();
109
+ this.#dialect.destroy(this.session);
110
+ }
111
+ }
112
+ /** One session for one key: it waits for the lock or tries it, then holds it with a heartbeat. */
113
+ class KeySession {
114
+ #owned;
115
+ #lock;
116
+ #heartbeatInterval;
117
+ #dialect;
118
+ #lease;
119
+ /** Aborts when the store stops holding: a release, or a lost session. */
120
+ #done = new AbortController();
121
+ constructor(owned, key, heartbeatInterval, dialect) {
122
+ this.#owned = owned;
123
+ this.#lock = { key, name: dialect.lockName(key) };
124
+ this.#heartbeatInterval = heartbeatInterval;
125
+ this.#dialect = dialect;
126
+ this.#lease = new LeaseController(key);
127
+ }
128
+ /** Waits in the server's lock queue. A cancel destroys the session, and the end of the session releases a late grant. */
129
+ async wait(signal) {
130
+ try {
131
+ await this.#dialect.wait(this.#owned.session, this.#lock, signal);
132
+ }
133
+ catch (error) {
134
+ this.#owned.destroy();
135
+ throw error;
136
+ }
137
+ }
138
+ async try() {
139
+ try {
140
+ if (await this.#dialect.try(this.#owned.session, this.#lock))
141
+ return true;
142
+ }
143
+ catch (error) {
144
+ this.#owned.destroy();
145
+ throw error;
146
+ }
147
+ this.#owned.giveBack();
148
+ return false;
149
+ }
150
+ /** Mints the token of a granted lock and starts the heartbeat. A failed mint releases the key. */
151
+ async hold() {
152
+ let token;
153
+ try {
154
+ token = new FencingToken(await this.#dialect.mint(this.#owned.session));
155
+ }
156
+ catch (error) {
157
+ await this.#release();
158
+ throw error;
159
+ }
160
+ const ended = this.#owned.ended;
161
+ ended.addEventListener('abort', () => this.#lose(ended.reason), {
162
+ once: true,
163
+ signal: this.#done.signal,
164
+ });
165
+ void this.#beat();
166
+ return {
167
+ token,
168
+ signal: this.#lease.signal,
169
+ [Symbol.asyncDispose]: () => this.#release(),
170
+ };
171
+ }
172
+ /**
173
+ * Asks the server each interval whether this session still holds the lock.
174
+ * A session that connected again by itself answers, but holds nothing. A
175
+ * failed, late or negative answer loses the lease.
176
+ */
177
+ async #beat() {
178
+ const interval = this.#heartbeatInterval;
179
+ const done = this.#done.signal;
180
+ try {
181
+ for (;;) {
182
+ await delay(interval, undefined, { signal: done });
183
+ const holds = await untilAborted(this.#dialect.holds(this.#owned.session, this.#lock), AbortSignal.any([done, AbortSignal.timeout(interval)]));
184
+ if (!holds) {
185
+ throw new Error(`The session no longer holds the lock of ${JSON.stringify(this.#lock.key)}.`);
186
+ }
187
+ }
188
+ }
189
+ catch (cause) {
190
+ if (!done.aborted)
191
+ this.#lose(cause);
192
+ }
193
+ }
194
+ /** Another holder can have the key now. */
195
+ #lose(cause) {
196
+ if (this.#done.signal.aborted)
197
+ return;
198
+ this.#done.abort();
199
+ this.#lease.lose(cause);
200
+ this.#owned.destroy();
201
+ }
202
+ async #release() {
203
+ if (this.#done.signal.aborted)
204
+ return;
205
+ this.#done.abort();
206
+ try {
207
+ if (!(await this.#dialect.release(this.#owned.session, this.#lock))) {
208
+ throw new Error(`The session did not hold the lock of ${JSON.stringify(this.#lock.key)}.`);
209
+ }
210
+ }
211
+ catch (cause) {
212
+ // A plain give-back would give the next borrower a lock that this session may still hold.
213
+ this.#lease.lose(cause);
214
+ this.#owned.destroy();
215
+ return;
216
+ }
217
+ this.#lease.end();
218
+ this.#owned.giveBack();
219
+ }
220
+ }
221
+ //# sourceMappingURL=session-lock-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-lock-store.js","sourceRoot":"","sources":["../../../src/lock-stores/session/session-lock-store.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite-store.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/sqlite/sqlite-store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAIlE;;;;;;;;;;;GAWG;AACH,qBAAa,WAAY,SAAQ,aAAa;;IAC5C;;;;;OAKG;IACH,SAAS,CAAC,QAAQ,CAAC,aAAa,SAAqB;IAGtC,OAAO,CACpB,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,UAAU,CAAC;IAOP,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;cASvD,IAAI,CAClB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,WAAW,GAAG,SAAS,GAC9B,OAAO,CAAC,eAAe,CAAC;cASX,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC;IAQ3E,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAGnD"}
1
+ {"version":3,"file":"sqlite-store.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/sqlite/sqlite-store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAEhE,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAGlE;;;;;;;;;;;GAWG;AACH,qBAAa,WAAY,SAAQ,aAAa;;IAC5C;;;;;OAKG;IACH,SAAS,CAAC,QAAQ,CAAC,aAAa,SAAqB;IAG5C,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC;IAInE,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;cAIjD,IAAI,CAClB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,WAAW,GAAG,SAAS,GAC9B,OAAO,CAAC,eAAe,CAAC;cASX,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC;IAQ3E,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAGnD"}
@@ -1,6 +1,6 @@
1
1
  import { FileLock } from '@zukhruf/fs';
2
+ import { InProcessTurns } from "../../shared/in-process-turns.js";
2
3
  import { FileLockStore } from "../file-system/file-lock-store.js";
3
- import { MemoryStore } from "../memory/memory-store.js";
4
4
  import { HolderRecord } from "./holder-record.js";
5
5
  /**
6
6
  * Holds the file lock of the key's database file. The kernel releases it when
@@ -22,21 +22,12 @@ export class SqliteStore extends FileLockStore {
22
22
  * holder folder `<path>.holder` is shorter.
23
23
  */
24
24
  longestSuffix = '-journal'.length;
25
- #inProcess = new MemoryStore();
26
- async acquire(key, options = {}) {
27
- await using turn = new AsyncDisposableStack();
28
- // Joining the in-process queue is synchronous, so the order is the order of the calls.
29
- turn.use(await this.#inProcess.acquire(key, options));
30
- return withTurn(await super.acquire(key, options), turn);
25
+ #turns = new InProcessTurns();
26
+ acquire(key, options) {
27
+ return this.#turns.acquire(key, options, () => super.acquire(key, options));
31
28
  }
32
- async tryAcquire(key) {
33
- await using turn = new AsyncDisposableStack();
34
- const inProcess = await this.#inProcess.tryAcquire(key);
35
- if (!inProcess)
36
- return undefined;
37
- turn.use(inProcess);
38
- const lease = await super.tryAcquire(key);
39
- return lease && withTurn(lease, turn);
29
+ tryAcquire(key) {
30
+ return this.#turns.tryAcquire(key, () => super.tryAcquire(key));
40
31
  }
41
32
  async lock(path, signal) {
42
33
  await using held = new AsyncDisposableStack();
@@ -58,14 +49,4 @@ export class SqliteStore extends FileLockStore {
58
49
  return new HolderRecord(path).isPresent();
59
50
  }
60
51
  }
61
- /** Releases the database lock first, then lets the next caller in this process try. */
62
- function withTurn(handle, turn) {
63
- turn.use(handle);
64
- const release = turn.move();
65
- return {
66
- token: handle.token,
67
- signal: handle.signal,
68
- [Symbol.asyncDispose]: () => release.disposeAsync(),
69
- };
70
- }
71
52
  //# sourceMappingURL=sqlite-store.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite-store.js","sourceRoot":"","sources":["../../../src/lock-stores/sqlite/sqlite-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sqlite-store.js","sourceRoot":"","sources":["../../../src/lock-stores/sqlite/sqlite-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAIvC,OAAO,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,WAAY,SAAQ,aAAa;IAC5C;;;;;OAKG;IACgB,aAAa,GAAG,UAAU,CAAC,MAAM,CAAC;IAC5C,MAAM,GAAG,IAAI,cAAc,EAAE,CAAC;IAE9B,OAAO,CAAC,GAAW,EAAE,OAAwB;QACpD,OAAO,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC;IAC9E,CAAC;IAEQ,UAAU,CAAC,GAAW;QAC7B,OAAO,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;IAClE,CAAC;IAES,KAAK,CAAC,IAAI,CAClB,IAAY,EACZ,MAA+B;QAE/B,YAAY,IAAI,GAAG,IAAI,oBAAoB,EAAE,CAAC;QAC9C,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAC3C,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,CAAC;QACzE,oFAAoF;QACpF,IAAI,CAAC,GAAG,CAAC,MAAM,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;QAClD,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC;IACrB,CAAC;IAES,KAAK,CAAC,OAAO,CAAC,IAAY;QAClC,YAAY,IAAI,GAAG,IAAI,oBAAoB,EAAE,CAAC;QAC9C,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAC3C,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE;YAAE,OAAO,SAAS,CAAC;QACtC,IAAI,CAAC,GAAG,CAAC,MAAM,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;QAClD,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC;IACrB,CAAC;IAES,QAAQ,CAAC,IAAY;QAC7B,OAAO,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC;IAC5C,CAAC;CACF"}
@@ -0,0 +1,2 @@
1
+ export { SqlServerStore, type SqlServerSession, type SqlServerStoreOptions, } from './sqlserver-store.ts';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/sqlserver/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,GAC3B,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { SqlServerStore, } from "./sqlserver-store.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lock-stores/sqlserver/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,GAGf,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,40 @@
1
+ import { SessionLockStore } from '../session/session-lock-store.ts';
2
+ /**
3
+ * One connection to SQL Server: only the methods that the store calls. No
4
+ * driver object fits it as it is; a `Connection` of tedious fits it through a
5
+ * small wrapper. The session must never connect again by itself.
6
+ */
7
+ export interface SqlServerSession {
8
+ /** Runs one batch. Each parameter is an `nvarchar`. Resolves to the rows of the batch. */
9
+ query(text: string, parameters: Record<string, string>): Promise<Record<string, unknown>[]>;
10
+ on(event: 'error', listener: (error: Error) => void): unknown;
11
+ on(event: 'end', listener: () => void): unknown;
12
+ /** Ends the connection. The server then releases each lock of the session. */
13
+ close(): void;
14
+ }
15
+ export interface SqlServerStoreOptions {
16
+ /** Gives a new session. The store owns each session that it gets, and closes it. */
17
+ connect(): Promise<SqlServerSession>;
18
+ /** The name of an existing sequence for the fencing tokens. The store runs no DDL. */
19
+ sequence: string;
20
+ /**
21
+ * Milliseconds between two heartbeats of a holder, and the time limit of
22
+ * each heartbeat: the longest time that a holder can work after its session
23
+ * ended, before its signal aborts.
24
+ */
25
+ heartbeatInterval: number;
26
+ }
27
+ /**
28
+ * Holds a session-owned application lock of SQL Server (`sp_getapplock`) for
29
+ * each key, on a session of its own. The server releases the lock when the
30
+ * session ends, so a holder that stops never leaves a key locked. Every
31
+ * process on every machine that reaches one database shares the locks.
32
+ *
33
+ * Callers in this process first line up in a queue in memory, so only the
34
+ * first one gets a session: one session per key per process, however many
35
+ * callers wait, and first-come order within the process.
36
+ */
37
+ export declare class SqlServerStore extends SessionLockStore<SqlServerSession> {
38
+ constructor(options: SqlServerStoreOptions);
39
+ }
40
+ //# sourceMappingURL=sqlserver-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlserver-store.d.ts","sourceRoot":"","sources":["../../../src/lock-stores/sqlserver/sqlserver-store.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,gBAAgB,EACjB,MAAM,kCAAkC,CAAC;AAE1C;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,0FAA0F;IAC1F,KAAK,CACH,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACjC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;IACtC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,OAAO,CAAC;IAC9D,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC;IAChD,8EAA8E;IAC9E,KAAK,IAAI,IAAI,CAAC;CACf;AAED,MAAM,WAAW,qBAAqB;IACpC,oFAAoF;IACpF,OAAO,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACrC,sFAAsF;IACtF,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAoCD;;;;;;;;;GASG;AACH,qBAAa,cAAe,SAAQ,gBAAgB,CAAC,gBAAgB,CAAC;gBACxD,OAAO,EAAE,qBAAqB;CAG3C"}
@@ -0,0 +1,104 @@
1
+ import { untilAborted } from '@zukhruf/async';
2
+ import { keyDigest } from "../../shared/key-digest.js";
3
+ import { SessionLockStore, } from "../session/session-lock-store.js";
4
+ /** `0` is the principal ID of `public`, the default `@DbPrincipal` of `sp_getapplock`. */
5
+ const heldQuery = `SELECT CASE WHEN EXISTS (
6
+ SELECT 1 FROM sys.dm_tran_locks
7
+ WHERE resource_type = N'APPLICATION'
8
+ AND resource_database_id = DB_ID()
9
+ AND request_status = N'GRANT'
10
+ AND LEFT(resource_description, 36) = N'0:[' + @resource + N']'
11
+ ) THEN 1 ELSE 0 END AS held`;
12
+ /** `APPLOCK_MODE` reads the lock mode of this session, and never takes the lock. */
13
+ const holdsQuery = `SELECT CASE WHEN APPLOCK_MODE('public', @resource, 'Session') <> 'NoLock' THEN 1 ELSE 0 END AS held`;
14
+ const waitQuery = `DECLARE @result int;
15
+ EXEC @result = sp_getapplock @Resource = @resource, @LockMode = 'Exclusive', @LockOwner = 'Session', @LockTimeout = -1;
16
+ SELECT @result AS result;`;
17
+ const tryQuery = `DECLARE @result int;
18
+ EXEC @result = sp_getapplock @Resource = @resource, @LockMode = 'Exclusive', @LockOwner = 'Session', @LockTimeout = 0;
19
+ SELECT @result AS result;`;
20
+ const releaseQuery = `DECLARE @result int;
21
+ EXEC @result = sp_releaseapplock @Resource = @resource, @LockOwner = 'Session';
22
+ SELECT @result AS result;`;
23
+ /** `NEXT VALUE FOR` takes no parameter, so the batch finds the sequence by name, as `regclass` does, and quotes it. */
24
+ const tokenQuery = `DECLARE @id int = OBJECT_ID(@sequence, 'SO');
25
+ IF @id IS NULL
26
+ BEGIN
27
+ DECLARE @message nvarchar(2048) = N'The sequence ' + @sequence + N' does not exist, or the role has no permission on it.';
28
+ THROW 50000, @message, 1;
29
+ END;
30
+ DECLARE @next nvarchar(400) = N'SELECT CAST(NEXT VALUE FOR ' + QUOTENAME(OBJECT_SCHEMA_NAME(@id)) + N'.' + QUOTENAME(OBJECT_NAME(@id)) + N' AS nvarchar(20)) AS token';
31
+ EXEC sp_executesql @next;`;
32
+ /**
33
+ * Holds a session-owned application lock of SQL Server (`sp_getapplock`) for
34
+ * each key, on a session of its own. The server releases the lock when the
35
+ * session ends, so a holder that stops never leaves a key locked. Every
36
+ * process on every machine that reaches one database shares the locks.
37
+ *
38
+ * Callers in this process first line up in a queue in memory, so only the
39
+ * first one gets a session: one session per key per process, however many
40
+ * callers wait, and first-come order within the process.
41
+ */
42
+ export class SqlServerStore extends SessionLockStore {
43
+ constructor(options) {
44
+ super(options, sqlServerDialect(options));
45
+ }
46
+ }
47
+ /**
48
+ * The application lock resource of `key`: the first 16 bytes of the key
49
+ * digest, as 32 hex characters. Any key fits the 255 characters of
50
+ * `@Resource`, and `sys.dm_tran_locks` shows only the first 32 characters of
51
+ * a resource, so the holder check sees the whole name.
52
+ */
53
+ function resourceName(key) {
54
+ return keyDigest(key).subarray(0, 16).toString('hex');
55
+ }
56
+ function sqlServerDialect(options) {
57
+ return {
58
+ server: 'SQL Server',
59
+ lockName: resourceName,
60
+ wait: async (session, { key, name }, signal) => {
61
+ const [row] = await untilAborted(session.query(waitQuery, { resource: name }), signal);
62
+ if (typeof row?.result !== 'number' || row.result < 0) {
63
+ throw new Error(`sp_getapplock for ${JSON.stringify(key)} returned ${String(row?.result)}.`);
64
+ }
65
+ },
66
+ try: async (session, { key, name }) => {
67
+ const [row] = await session.query(tryQuery, { resource: name });
68
+ if (row?.result === 0)
69
+ return true;
70
+ if (row?.result !== -1) {
71
+ throw new Error(`sp_getapplock for ${JSON.stringify(key)} returned ${String(row?.result)}.`);
72
+ }
73
+ return false;
74
+ },
75
+ mint: async (session) => {
76
+ const [row] = await session.query(tokenQuery, {
77
+ sequence: options.sequence,
78
+ });
79
+ if (typeof row?.token !== 'string') {
80
+ throw new Error(`NEXT VALUE FOR sequence ${JSON.stringify(options.sequence)} gave no number.`);
81
+ }
82
+ return BigInt(row.token);
83
+ },
84
+ holds: async (session, { name }) => {
85
+ const [row] = await session.query(holdsQuery, { resource: name });
86
+ return row?.held === 1;
87
+ },
88
+ // Releases the lock on the server before the close: the server sees a closed connection a little later.
89
+ release: async (session, { name }) => {
90
+ const [row] = await session.query(releaseQuery, { resource: name });
91
+ return row?.result === 0;
92
+ },
93
+ /** Reads `sys.dm_tran_locks`. `APPLOCK_TEST` would take the lock for an instant. */
94
+ isHeld: async (session, { name }) => {
95
+ const [row] = await session.query(heldQuery, { resource: name });
96
+ return row?.held === 1;
97
+ },
98
+ // A closed session keeps its listeners, because a driver can emit `error` after the close.
99
+ detach: () => { },
100
+ giveBack: (session) => session.close(),
101
+ destroy: (session) => session.close(),
102
+ };
103
+ }
104
+ //# sourceMappingURL=sqlserver-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlserver-store.js","sourceRoot":"","sources":["../../../src/lock-stores/sqlserver/sqlserver-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { LockHandle } from '../mutex/lease.ts';
2
+ import type { AcquireOptions } from '../mutex/lock-store.ts';
3
+ /**
4
+ * The line of the callers of each key in this process. A lock store puts each
5
+ * call in the line first, so only the first caller of a key takes the
6
+ * store's resource of the key (a file, a session or a claim): one resource
7
+ * per key per process, however many callers wait, and first-come order within
8
+ * the process.
9
+ */
10
+ export declare class InProcessTurns {
11
+ #private;
12
+ /** Waits for the turn of `key`, then calls `take`. A cancel of `options.signal` also ends the wait for the turn. */
13
+ acquire(key: string, options: AcquireOptions | undefined, take: () => Promise<LockHandle>): Promise<LockHandle>;
14
+ /** Calls `take` only when no caller in this process has the turn of `key`. */
15
+ tryAcquire(key: string, take: () => Promise<LockHandle | undefined>): Promise<LockHandle | undefined>;
16
+ }
17
+ //# sourceMappingURL=in-process-turns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"in-process-turns.d.ts","sourceRoot":"","sources":["../../src/shared/in-process-turns.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE7D;;;;;;GAMG;AACH,qBAAa,cAAc;;IAGzB,oHAAoH;IAC9G,OAAO,CACX,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,cAAc,GAAG,SAAS,EACnC,IAAI,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,GAC9B,OAAO,CAAC,UAAU,CAAC;IAOtB,8EAA8E;IACxE,UAAU,CACd,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,GAC1C,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;CAQnC"}
@@ -0,0 +1,39 @@
1
+ import { MemoryStore } from "../lock-stores/memory/memory-store.js";
2
+ /**
3
+ * The line of the callers of each key in this process. A lock store puts each
4
+ * call in the line first, so only the first caller of a key takes the
5
+ * store's resource of the key (a file, a session or a claim): one resource
6
+ * per key per process, however many callers wait, and first-come order within
7
+ * the process.
8
+ */
9
+ export class InProcessTurns {
10
+ #lines = new MemoryStore();
11
+ /** Waits for the turn of `key`, then calls `take`. A cancel of `options.signal` also ends the wait for the turn. */
12
+ async acquire(key, options, take) {
13
+ await using turn = new AsyncDisposableStack();
14
+ // Joining the line is synchronous, so the order is the order of the calls.
15
+ turn.use(await this.#lines.acquire(key, options));
16
+ return withTurn(await take(), turn);
17
+ }
18
+ /** Calls `take` only when no caller in this process has the turn of `key`. */
19
+ async tryAcquire(key, take) {
20
+ await using turn = new AsyncDisposableStack();
21
+ const inProcess = await this.#lines.tryAcquire(key);
22
+ if (!inProcess)
23
+ return undefined;
24
+ turn.use(inProcess);
25
+ const handle = await take();
26
+ return handle && withTurn(handle, turn);
27
+ }
28
+ }
29
+ /** Releases the key in the store first, then lets the next caller in this process continue. */
30
+ function withTurn(handle, turn) {
31
+ turn.use(handle);
32
+ const release = turn.move();
33
+ return {
34
+ token: handle.token,
35
+ signal: handle.signal,
36
+ [Symbol.asyncDispose]: () => release.disposeAsync(),
37
+ };
38
+ }
39
+ //# sourceMappingURL=in-process-turns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"in-process-turns.js","sourceRoot":"","sources":["../../src/shared/in-process-turns.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,uCAAuC,CAAC;AAIpE;;;;;;GAMG;AACH,MAAM,OAAO,cAAc;IAChB,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;IAEpC,oHAAoH;IACpH,KAAK,CAAC,OAAO,CACX,GAAW,EACX,OAAmC,EACnC,IAA+B;QAE/B,YAAY,IAAI,GAAG,IAAI,oBAAoB,EAAE,CAAC;QAC9C,2EAA2E;QAC3E,IAAI,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC;QAClD,OAAO,QAAQ,CAAC,MAAM,IAAI,EAAE,EAAE,IAAI,CAAC,CAAC;IACtC,CAAC;IAED,8EAA8E;IAC9E,KAAK,CAAC,UAAU,CACd,GAAW,EACX,IAA2C;QAE3C,YAAY,IAAI,GAAG,IAAI,oBAAoB,EAAE,CAAC;QAC9C,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QACpD,IAAI,CAAC,SAAS;YAAE,OAAO,SAAS,CAAC;QACjC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACpB,MAAM,MAAM,GAAG,MAAM,IAAI,EAAE,CAAC;QAC5B,OAAO,MAAM,IAAI,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC1C,CAAC;CACF;AAED,+FAA+F;AAC/F,SAAS,QAAQ,CAAC,MAAkB,EAAE,IAA0B;IAC9D,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACjB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5B,OAAO;QACL,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,YAAY,EAAE;KACpD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The SHA-256 digest of the UTF-16 code units of `key`. The code units, not
3
+ * UTF-8, so that keys that differ only in a lone surrogate get different
4
+ * digests. Each lock store encodes the digest in the form that its server
5
+ * takes.
6
+ */
7
+ export declare function keyDigest(key: string): Buffer;
8
+ //# sourceMappingURL=key-digest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"key-digest.d.ts","sourceRoot":"","sources":["../../src/shared/key-digest.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE7C"}
@@ -0,0 +1,11 @@
1
+ import { createHash } from 'node:crypto';
2
+ /**
3
+ * The SHA-256 digest of the UTF-16 code units of `key`. The code units, not
4
+ * UTF-8, so that keys that differ only in a lone surrogate get different
5
+ * digests. Each lock store encodes the digest in the form that its server
6
+ * takes.
7
+ */
8
+ export function keyDigest(key) {
9
+ return createHash('sha256').update(Buffer.from(key, 'utf16le')).digest();
10
+ }
11
+ //# sourceMappingURL=key-digest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"key-digest.js","sourceRoot":"","sources":["../../src/shared/key-digest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;AAC3E,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zukhruf/mutex",
3
- "version": "0.3.40",
3
+ "version": "0.3.42",
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": {
@@ -28,6 +28,7 @@
28
28
  "default": "./dist/index.js"
29
29
  },
30
30
  "./remote": null,
31
+ "./session": null,
31
32
  "./*": {
32
33
  "@zukhruf/source": "./src/lock-stores/*/index.ts",
33
34
  "types": "./dist/lock-stores/*/index.d.ts",
@@ -45,16 +46,28 @@
45
46
  "access": "public"
46
47
  },
47
48
  "dependencies": {
48
- "@zukhruf/async": "^0.3.40",
49
- "@zukhruf/election": "^0.3.40",
50
- "@zukhruf/fencing": "^0.3.40",
51
- "@zukhruf/fs": "^0.3.40",
52
- "@zukhruf/lease": "^0.3.40"
49
+ "@zukhruf/async": "^0.3.42",
50
+ "@zukhruf/election": "^0.3.42",
51
+ "@zukhruf/fencing": "^0.3.42",
52
+ "@zukhruf/fs": "^0.3.42",
53
+ "@zukhruf/lease": "^0.3.42"
54
+ },
55
+ "peerDependencies": {
56
+ "@zukhruf/docker": "^0.3.42"
57
+ },
58
+ "peerDependenciesMeta": {
59
+ "@zukhruf/docker": {
60
+ "optional": true
61
+ }
53
62
  },
54
63
  "devDependencies": {
55
64
  "@types/pg": "^8.23.1",
56
- "@zukhruf/testing": "^0.3.40",
65
+ "@zukhruf/docker": "^0.3.42",
66
+ "@zukhruf/testing": "^0.3.42",
57
67
  "hono": "^4.13.13",
58
- "pg": "^8.23.1"
68
+ "mariadb": "^3.5.4",
69
+ "mysql2": "^3.24.5",
70
+ "pg": "^8.23.1",
71
+ "tedious": "^20.3.3"
59
72
  }
60
73
  }