1
1
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' ;
3
9
import { withF , withG } from '@matrixai/resources' ;
4
10
import { ErrorAsyncLocksLockBoxConflict } from './errors' ;
5
11
6
- class LockBox < L extends Lockable > implements Lockable {
12
+ class LockBox < L extends Lockable = Lockable > implements Lockable {
7
13
protected _locks : Map < string , L > = new Map ( ) ;
8
14
9
- public lock ( ...requests : Array < LockRequest < L > > ) : ResourceAcquire < LockBox < L > > {
15
+ public lock (
16
+ ...requests : Array < MultiLockRequest < L > >
17
+ ) : ResourceAcquire < LockBox < L > > {
10
18
return async ( ) => {
11
19
// Convert to strings
12
20
// This creates a copy of the requests
@@ -26,42 +34,48 @@ class LockBox<L extends Lockable> implements Lockable {
26
34
( [ key ] , i , arr ) => i === 0 || key !== arr [ i - 1 ] [ 0 ] ,
27
35
) ;
28
36
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
+ }
40
50
}
51
+ const lockAcquire = lock . lock ( ...lockingParams ) ;
52
+ const [ lockRelease ] = await lockAcquire ( ) ;
53
+ locks . push ( [ key , lockRelease , lock ] ) ;
41
54
}
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 ) ;
54
64
}
55
- throw e ;
56
65
}
57
- locks . push ( [ key , lockRelease , lock ] ) ;
66
+ throw e ;
58
67
}
68
+ let released = false ;
59
69
return [
60
70
async ( ) => {
71
+ if ( released ) return ;
72
+ released = true ;
61
73
// Release all locks in reverse order
62
74
locks . reverse ( ) ;
63
75
for ( const [ key , lockRelease , lock ] of locks ) {
64
76
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
65
79
if ( ! lock . isLocked ( ) ) {
66
80
this . _locks . delete ( key ) ;
67
81
}
@@ -72,6 +86,76 @@ class LockBox<L extends Lockable> implements Lockable {
72
86
} ;
73
87
}
74
88
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
+
75
159
get locks ( ) : ReadonlyMap < string , L > {
76
160
return this . _locks ;
77
161
}
@@ -116,31 +200,88 @@ class LockBox<L extends Lockable> implements Lockable {
116
200
117
201
public async withF < T > (
118
202
...params : [
119
- ...requests : Array < LockRequest < L > > ,
203
+ ...requests : Array < MultiLockRequest < L > > ,
120
204
f : ( lockBox : LockBox < L > ) => Promise < T > ,
121
205
]
122
206
) : Promise < T > {
123
207
const f = params . pop ( ) as ( lockBox : LockBox < L > ) => Promise < T > ;
124
208
return withF (
125
- [ this . lock ( ...( params as Array < LockRequest < L > > ) ) ] ,
209
+ [ this . lock ( ...( params as Array < MultiLockRequest < L > > ) ) ] ,
126
210
( [ lockBox ] ) => f ( lockBox ) ,
127
211
) ;
128
212
}
129
213
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
+
130
242
public withG < T , TReturn , TNext > (
131
243
...params : [
132
- ...requests : Array < LockRequest < L > > ,
244
+ ...requests : Array < MultiLockRequest < L > > ,
133
245
g : ( lockBox : LockBox < L > ) => AsyncGenerator < T , TReturn , TNext > ,
134
246
]
135
247
) : AsyncGenerator < T , TReturn , TNext > {
136
248
const g = params . pop ( ) as (
137
249
lockBox : LockBox < L > ,
138
250
) => AsyncGenerator < T , TReturn , TNext > ;
139
251
return withG (
140
- [ this . lock ( ...( params as Array < LockRequest < L > > ) ) ] ,
252
+ [ this . lock ( ...( params as Array < MultiLockRequest < L > > ) ) ] ,
141
253
( [ lockBox ] ) => g ( lockBox ) ,
142
254
) ;
143
255
}
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
+ }
144
285
}
145
286
146
287
export default LockBox ;
0 commit comments