Skip to content

Commit 6504452

Browse files
authored
Merge pull request #14 from MatrixAI/feature-default
Integrating `LockBox` to `DBTransaction` - introducing `lockMulti`
2 parents cdbe3fe + e3d4d97 commit 6504452

16 files changed

+333
-48
lines changed

docs/assets/search.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/classes/Lock.html

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

docs/classes/LockBox.html

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

docs/classes/RWLockReader.html

Lines changed: 2 additions & 2 deletions
Large diffs are not rendered by default.

docs/classes/RWLockWriter.html

Lines changed: 2 additions & 2 deletions
Large diffs are not rendered by default.

docs/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,4 @@ <h3>Publishing</h3>
3131
</a>
3232
<pre><code class="language-sh"><span class="hl-1"># npm login</span><br/><span class="hl-0">npm version patch </span><span class="hl-1"># major/minor/patch</span><br/><span class="hl-0">npm run build</span><br/><span class="hl-0">npm publish --access public</span><br/><span class="hl-0">git push</span><br/><span class="hl-0">git push --tags</span>
3333
</code></pre>
34-
</div></div><div class="col-4 col-menu menu-sticky-wrap menu-highlight"><nav class="tsd-navigation primary"><ul><li class="current"><a href="modules.html">Exports</a></li><li class=" tsd-kind-namespace"><a href="modules/errors.html">errors</a></li><li class=" tsd-kind-namespace"><a href="modules/utils.html">utils</a></li></ul></nav><nav class="tsd-navigation secondary menu-sticky"><ul><li class="tsd-kind-class"><a href="classes/Lock.html" class="tsd-kind-icon">Lock</a></li><li class="tsd-kind-class tsd-has-type-parameter"><a href="classes/LockBox.html" class="tsd-kind-icon">Lock<wbr/>Box</a></li><li class="tsd-kind-class"><a href="classes/RWLockReader.html" class="tsd-kind-icon">RWLock<wbr/>Reader</a></li><li class="tsd-kind-class"><a href="classes/RWLockWriter.html" class="tsd-kind-icon">RWLock<wbr/>Writer</a></li><li class="tsd-kind-interface"><a href="interfaces/Lockable.html" class="tsd-kind-icon">Lockable</a></li><li class="tsd-kind-interface"><a href="interfaces/ToString.html" class="tsd-kind-icon">To<wbr/>String</a></li><li class="tsd-kind-type-alias tsd-has-type-parameter"><a href="modules.html#LockRequest" class="tsd-kind-icon">Lock<wbr/>Request</a></li><li class="tsd-kind-type-alias"><a href="modules.html#POJO" class="tsd-kind-icon">POJO</a></li></ul></nav></div></div></div><footer class="with-border-bottom"><div class="container"><h2>Legend</h2><div class="tsd-legend-group"><ul class="tsd-legend"><li class="tsd-kind-property tsd-parent-kind-interface"><span class="tsd-kind-icon">Property</span></li><li class="tsd-kind-method tsd-parent-kind-interface"><span class="tsd-kind-icon">Method</span></li></ul><ul class="tsd-legend"><li class="tsd-kind-constructor tsd-parent-kind-class"><span class="tsd-kind-icon">Constructor</span></li><li class="tsd-kind-method tsd-parent-kind-class"><span class="tsd-kind-icon">Method</span></li></ul><ul class="tsd-legend"><li class="tsd-kind-property tsd-parent-kind-class tsd-is-protected"><span class="tsd-kind-icon">Protected property</span></li></ul></div><h2>Settings</h2><p>Theme <select id="theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></p></div></footer><div class="container tsd-generator"><p>Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></div><div class="overlay"></div><script src="assets/main.js"></script></body></html>
34+
</div></div><div class="col-4 col-menu menu-sticky-wrap menu-highlight"><nav class="tsd-navigation primary"><ul><li class="current"><a href="modules.html">Exports</a></li><li class=" tsd-kind-namespace"><a href="modules/errors.html">errors</a></li><li class=" tsd-kind-namespace"><a href="modules/utils.html">utils</a></li></ul></nav><nav class="tsd-navigation secondary menu-sticky"><ul><li class="tsd-kind-class"><a href="classes/Lock.html" class="tsd-kind-icon">Lock</a></li><li class="tsd-kind-class tsd-has-type-parameter"><a href="classes/LockBox.html" class="tsd-kind-icon">Lock<wbr/>Box</a></li><li class="tsd-kind-class"><a href="classes/RWLockReader.html" class="tsd-kind-icon">RWLock<wbr/>Reader</a></li><li class="tsd-kind-class"><a href="classes/RWLockWriter.html" class="tsd-kind-icon">RWLock<wbr/>Writer</a></li><li class="tsd-kind-interface"><a href="interfaces/Lockable.html" class="tsd-kind-icon">Lockable</a></li><li class="tsd-kind-interface"><a href="interfaces/ToString.html" class="tsd-kind-icon">To<wbr/>String</a></li><li class="tsd-kind-type-alias tsd-has-type-parameter"><a href="modules.html#MultiLockAcquire" class="tsd-kind-icon">Multi<wbr/>Lock<wbr/>Acquire</a></li><li class="tsd-kind-type-alias tsd-has-type-parameter"><a href="modules.html#MultiLockAcquired" class="tsd-kind-icon">Multi<wbr/>Lock<wbr/>Acquired</a></li><li class="tsd-kind-type-alias tsd-has-type-parameter"><a href="modules.html#MultiLockRequest" class="tsd-kind-icon">Multi<wbr/>Lock<wbr/>Request</a></li><li class="tsd-kind-type-alias"><a href="modules.html#POJO" class="tsd-kind-icon">POJO</a></li></ul></nav></div></div></div><footer class="with-border-bottom"><div class="container"><h2>Legend</h2><div class="tsd-legend-group"><ul class="tsd-legend"><li class="tsd-kind-property tsd-parent-kind-interface"><span class="tsd-kind-icon">Property</span></li><li class="tsd-kind-method tsd-parent-kind-interface"><span class="tsd-kind-icon">Method</span></li></ul><ul class="tsd-legend"><li class="tsd-kind-constructor tsd-parent-kind-class"><span class="tsd-kind-icon">Constructor</span></li><li class="tsd-kind-method tsd-parent-kind-class"><span class="tsd-kind-icon">Method</span></li></ul><ul class="tsd-legend"><li class="tsd-kind-property tsd-parent-kind-class tsd-is-protected"><span class="tsd-kind-icon">Protected property</span></li></ul></div><h2>Settings</h2><p>Theme <select id="theme"><option value="os">OS</option><option value="light">Light</option><option value="dark">Dark</option></select></p></div></footer><div class="container tsd-generator"><p>Generated using <a href="https://typedoc.org/" target="_blank">TypeDoc</a></p></div><div class="overlay"></div><script src="assets/main.js"></script></body></html>

docs/modules.html

Lines changed: 2 additions & 2 deletions
Large diffs are not rendered by default.

src/Lock.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@ class Lock implements Lockable {
2424
--this._count;
2525
throw e;
2626
}
27+
let released = false;
2728
return [
2829
async () => {
30+
if (released) return;
31+
released = true;
2932
--this._count;
3033
release();
3134
// Allow semaphore to settle https://github.com/DirtyHairy/async-mutex/issues/54

src/LockBox.ts

Lines changed: 173 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
import type { ResourceAcquire, ResourceRelease } from '@matrixai/resources';
2-
import type { Lockable, ToString, LockRequest } from './types';
2+
import type {
3+
ToString,
4+
Lockable,
5+
MultiLockRequest,
6+
MultiLockAcquire,
7+
MultiLockAcquired,
8+
} from './types';
39
import { withF, withG } from '@matrixai/resources';
410
import { ErrorAsyncLocksLockBoxConflict } from './errors';
511

6-
class LockBox<L extends Lockable> implements Lockable {
12+
class LockBox<L extends Lockable = Lockable> implements Lockable {
713
protected _locks: Map<string, L> = new Map();
814

9-
public lock(...requests: Array<LockRequest<L>>): ResourceAcquire<LockBox<L>> {
15+
public lock(
16+
...requests: Array<MultiLockRequest<L>>
17+
): ResourceAcquire<LockBox<L>> {
1018
return async () => {
1119
// Convert to strings
1220
// This creates a copy of the requests
@@ -26,42 +34,48 @@ class LockBox<L extends Lockable> implements Lockable {
2634
([key], i, arr) => i === 0 || key !== arr[i - 1][0],
2735
);
2836
const locks: Array<[string, ResourceRelease, L]> = [];
29-
for (const [key, LockConstructor, ...lockingParams] of requests_) {
30-
let lock = this._locks.get(key);
31-
if (lock == null) {
32-
lock = new LockConstructor();
33-
this._locks.set(key, lock);
34-
} else {
35-
// It is possible to swap the lock class, but only after the lock key is released
36-
if (!(lock instanceof LockConstructor)) {
37-
throw new ErrorAsyncLocksLockBoxConflict(
38-
`Lock ${key} is already locked with class ${lock.constructor.name}, which conflicts with class ${LockConstructor.name}`,
39-
);
37+
try {
38+
for (const [key, LockConstructor, ...lockingParams] of requests_) {
39+
let lock = this._locks.get(key);
40+
if (lock == null) {
41+
lock = new LockConstructor();
42+
this._locks.set(key, lock);
43+
} else {
44+
// It is possible to swap the lock class, but only after the lock key is released
45+
if (!(lock instanceof LockConstructor)) {
46+
throw new ErrorAsyncLocksLockBoxConflict(
47+
`Lock ${key} is already locked with class ${lock.constructor.name}, which conflicts with class ${LockConstructor.name}`,
48+
);
49+
}
4050
}
51+
const lockAcquire = lock.lock(...lockingParams);
52+
const [lockRelease] = await lockAcquire();
53+
locks.push([key, lockRelease, lock]);
4154
}
42-
const lockAcquire = lock.lock(...lockingParams);
43-
let lockRelease: ResourceRelease;
44-
try {
45-
[lockRelease] = await lockAcquire();
46-
} catch (e) {
47-
// Release all intermediate locks in reverse order
48-
locks.reverse();
49-
for (const [key, lockRelease, lock] of locks) {
50-
await lockRelease();
51-
if (!lock.isLocked()) {
52-
this._locks.delete(key);
53-
}
55+
} catch (e) {
56+
// Release all intermediate locks in reverse order
57+
locks.reverse();
58+
for (const [key, lockRelease, lock] of locks) {
59+
await lockRelease();
60+
// If it is still locked, then it is held by a different context
61+
// only delete if no contexts are locking the lock
62+
if (!lock.isLocked()) {
63+
this._locks.delete(key);
5464
}
55-
throw e;
5665
}
57-
locks.push([key, lockRelease, lock]);
66+
throw e;
5867
}
68+
let released = false;
5969
return [
6070
async () => {
71+
if (released) return;
72+
released = true;
6173
// Release all locks in reverse order
6274
locks.reverse();
6375
for (const [key, lockRelease, lock] of locks) {
6476
await lockRelease();
77+
// If it is still locked, then it is held by a different context
78+
// only delete if no contexts are locking the lock
6579
if (!lock.isLocked()) {
6680
this._locks.delete(key);
6781
}
@@ -72,6 +86,76 @@ class LockBox<L extends Lockable> implements Lockable {
7286
};
7387
}
7488

89+
public lockMulti(
90+
...requests: Array<MultiLockRequest<L>>
91+
): Array<MultiLockAcquire<L>> {
92+
// Convert to strings
93+
// This creates a copy of the requests
94+
let requests_: Array<
95+
[string, ToString, new () => L, ...Parameters<L['lock']>]
96+
> = requests.map(([key, ...rest]) =>
97+
typeof key === 'string'
98+
? [key, key, ...rest]
99+
: [key.toString(), key, ...rest],
100+
);
101+
// Sort to ensure lock hierarchy
102+
requests_.sort(([key1], [key2]) => {
103+
// Deterministic string comparison according to 16-bit code units
104+
if (key1 < key2) return -1;
105+
if (key1 > key2) return 1;
106+
return 0;
107+
});
108+
// Avoid duplicate locking
109+
requests_ = requests_.filter(
110+
([key], i, arr) => i === 0 || key !== arr[i - 1][0],
111+
);
112+
const lockAcquires: Array<MultiLockAcquire<L>> = [];
113+
for (const [key, keyOrig, LockConstructor, ...lockingParams] of requests_) {
114+
const lockAcquire: ResourceAcquire<L> = async () => {
115+
let lock = this._locks.get(key);
116+
let lockRelease: ResourceRelease;
117+
try {
118+
if (lock == null) {
119+
lock = new LockConstructor();
120+
this._locks.set(key, lock);
121+
} else {
122+
// It is possible to swap the lock class, but only after the lock key is released
123+
if (!(lock instanceof LockConstructor)) {
124+
throw new ErrorAsyncLocksLockBoxConflict(
125+
`Lock ${key} is already locked with class ${lock.constructor.name}, which conflicts with class ${LockConstructor.name}`,
126+
);
127+
}
128+
}
129+
const lockAcquire = lock.lock(...lockingParams);
130+
[lockRelease] = await lockAcquire();
131+
} catch (e) {
132+
// If it is still locked, then it is held by a different context
133+
// only delete if no contexts are locking the lock
134+
if (!lock!.isLocked()) {
135+
this._locks.delete(key);
136+
}
137+
throw e;
138+
}
139+
let released = false;
140+
return [
141+
async () => {
142+
if (released) return;
143+
released = true;
144+
await lockRelease();
145+
// If it is still locked, then it is held by a different context
146+
// only delete if no contexts are locking the lock
147+
if (!lock!.isLocked()) {
148+
this._locks.delete(key);
149+
}
150+
},
151+
lock,
152+
];
153+
};
154+
lockAcquires.push([keyOrig, lockAcquire, ...lockingParams]);
155+
}
156+
return lockAcquires;
157+
}
158+
75159
get locks(): ReadonlyMap<string, L> {
76160
return this._locks;
77161
}
@@ -116,31 +200,88 @@ class LockBox<L extends Lockable> implements Lockable {
116200

117201
public async withF<T>(
118202
...params: [
119-
...requests: Array<LockRequest<L>>,
203+
...requests: Array<MultiLockRequest<L>>,
120204
f: (lockBox: LockBox<L>) => Promise<T>,
121205
]
122206
): Promise<T> {
123207
const f = params.pop() as (lockBox: LockBox<L>) => Promise<T>;
124208
return withF(
125-
[this.lock(...(params as Array<LockRequest<L>>))],
209+
[this.lock(...(params as Array<MultiLockRequest<L>>))],
126210
([lockBox]) => f(lockBox),
127211
);
128212
}
129213

214+
public async withMultiF<T>(
215+
...params: [
216+
...requests: Array<MultiLockRequest<L>>,
217+
f: (multiLocks: Array<MultiLockAcquired<L>>) => Promise<T>,
218+
]
219+
): Promise<T> {
220+
const f = params.pop() as (
221+
multiLocks: Array<MultiLockAcquired<L>>,
222+
) => Promise<T>;
223+
const lockAcquires = this.lockMulti(
224+
...(params as Array<MultiLockRequest<L>>),
225+
);
226+
227+
const lockAcquires_: Array<ResourceAcquire<MultiLockAcquired<L>>> =
228+
lockAcquires.map(
229+
([key, lockAcquire, ...lockingParams]) =>
230+
(...r) =>
231+
lockAcquire(...r).then(
232+
([lockRelease, lock]) =>
233+
[lockRelease, [key, lock, ...lockingParams]] as [
234+
ResourceRelease,
235+
MultiLockAcquired<L>,
236+
],
237+
),
238+
);
239+
return withF(lockAcquires_, f);
240+
}
241+
130242
public withG<T, TReturn, TNext>(
131243
...params: [
132-
...requests: Array<LockRequest<L>>,
244+
...requests: Array<MultiLockRequest<L>>,
133245
g: (lockBox: LockBox<L>) => AsyncGenerator<T, TReturn, TNext>,
134246
]
135247
): AsyncGenerator<T, TReturn, TNext> {
136248
const g = params.pop() as (
137249
lockBox: LockBox<L>,
138250
) => AsyncGenerator<T, TReturn, TNext>;
139251
return withG(
140-
[this.lock(...(params as Array<LockRequest<L>>))],
252+
[this.lock(...(params as Array<MultiLockRequest<L>>))],
141253
([lockBox]) => g(lockBox),
142254
);
143255
}
256+
257+
public withMultiG<T, TReturn, TNext>(
258+
...params: [
259+
...requests: Array<MultiLockRequest<L>>,
260+
g: (
261+
multiLocks: Array<MultiLockAcquired<L>>,
262+
) => AsyncGenerator<T, TReturn, TNext>,
263+
]
264+
) {
265+
const g = params.pop() as (
266+
multiLocks: Array<MultiLockAcquired<L>>,
267+
) => AsyncGenerator<T, TReturn, TNext>;
268+
const lockAcquires = this.lockMulti(
269+
...(params as Array<MultiLockRequest<L>>),
270+
);
271+
const lockAcquires_: Array<ResourceAcquire<MultiLockAcquired<L>>> =
272+
lockAcquires.map(
273+
([key, lockAcquire, ...lockingParams]) =>
274+
(...r) =>
275+
lockAcquire(...r).then(
276+
([lockRelease, lock]) =>
277+
[lockRelease, [key, lock, ...lockingParams]] as [
278+
ResourceRelease,
279+
MultiLockAcquired<L>,
280+
],
281+
),
282+
);
283+
return withG(lockAcquires_, g);
284+
}
144285
}
145286

146287
export default LockBox;

src/RWLockReader.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class RWLockReader implements Lockable {
1818
protected _writerCount: number = 0;
1919

2020
public lock(
21-
type: 'read' | 'write',
21+
type: 'read' | 'write' = 'write',
2222
timeout?: number,
2323
): ResourceAcquire<RWLockReader> {
2424
switch (type) {
@@ -74,8 +74,11 @@ class RWLockReader implements Lockable {
7474
// Yield for the first reader to finish locking
7575
await yieldMicro();
7676
}
77+
let released = false;
7778
return [
7879
async () => {
80+
if (released) return;
81+
released = true;
7982
readersRelease = await this.readersLock.acquire();
8083
const readerCount = --this._readerCount;
8184
// The last reader unlocks
@@ -109,8 +112,11 @@ class RWLockReader implements Lockable {
109112
--this._writerCount;
110113
throw e;
111114
}
115+
let released = false;
112116
return [
113117
async () => {
118+
if (released) return;
119+
released = true;
114120
release();
115121
--this._writerCount;
116122
// Allow semaphore to settle https://github.com/DirtyHairy/async-mutex/issues/54

src/RWLockWriter.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class RWLockWriter implements Lockable {
1919
protected _writerCount: number = 0;
2020

2121
public lock(
22-
type: 'read' | 'write',
22+
type: 'read' | 'write' = 'write',
2323
timeout?: number,
2424
): ResourceAcquire<RWLockWriter> {
2525
switch (type) {
@@ -74,8 +74,11 @@ class RWLockWriter implements Lockable {
7474
// Yield for the first reader to finish locking
7575
await yieldMicro();
7676
}
77+
let released = false;
7778
return [
7879
async () => {
80+
if (released) return;
81+
released = true;
7982
const readerCount = --this._readerCount;
8083
// The last reader unlocks
8184
if (readerCount === 0) {
@@ -126,8 +129,11 @@ class RWLockWriter implements Lockable {
126129
await yieldMicro();
127130
throw e;
128131
}
132+
let released = false;
129133
return [
130134
async () => {
135+
if (released) return;
136+
released = true;
131137
this.readersRelease();
132138
writersRelease();
133139
--this._writerCount;

src/types.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,29 @@ interface Lockable {
2323
): AsyncGenerator<T, TReturn, TNext>;
2424
}
2525

26-
type LockRequest<L extends Lockable = Lockable> = [
26+
type MultiLockRequest<L extends Lockable = Lockable> = [
2727
key: ToString,
2828
lockConstructor: new () => L,
2929
...lockingParams: Parameters<L['lock']>,
3030
];
3131

32-
export type { POJO, ToString, Lockable, LockRequest };
32+
type MultiLockAcquire<L extends Lockable = Lockable> = [
33+
key: ToString,
34+
lockAcquire: ResourceAcquire<L>,
35+
...lockingParams: Parameters<L['lock']>,
36+
];
37+
38+
type MultiLockAcquired<L extends Lockable = Lockable> = [
39+
key: ToString,
40+
lock: L,
41+
...lockingParams: Parameters<L['lock']>,
42+
];
43+
44+
export type {
45+
POJO,
46+
ToString,
47+
Lockable,
48+
MultiLockRequest,
49+
MultiLockAcquire,
50+
MultiLockAcquired,
51+
};

0 commit comments

Comments
 (0)