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
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,35 +34,36 @@ class LockBox<L extends Lockable = 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
}
59
68
let released = false ;
60
69
return [
@@ -65,6 +74,8 @@ class LockBox<L extends Lockable = Lockable> implements Lockable {
65
74
locks . reverse ( ) ;
66
75
for ( const [ key , lockRelease , lock ] of locks ) {
67
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
68
79
if ( ! lock . isLocked ( ) ) {
69
80
this . _locks . delete ( key ) ;
70
81
}
@@ -75,6 +86,76 @@ class LockBox<L extends Lockable = Lockable> implements Lockable {
75
86
} ;
76
87
}
77
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
+
78
159
get locks ( ) : ReadonlyMap < string , L > {
79
160
return this . _locks ;
80
161
}
@@ -119,31 +200,88 @@ class LockBox<L extends Lockable = Lockable> implements Lockable {
119
200
120
201
public async withF < T > (
121
202
...params : [
122
- ...requests : Array < LockRequest < L > > ,
203
+ ...requests : Array < MultiLockRequest < L > > ,
123
204
f : ( lockBox : LockBox < L > ) => Promise < T > ,
124
205
]
125
206
) : Promise < T > {
126
207
const f = params . pop ( ) as ( lockBox : LockBox < L > ) => Promise < T > ;
127
208
return withF (
128
- [ this . lock ( ...( params as Array < LockRequest < L > > ) ) ] ,
209
+ [ this . lock ( ...( params as Array < MultiLockRequest < L > > ) ) ] ,
129
210
( [ lockBox ] ) => f ( lockBox ) ,
130
211
) ;
131
212
}
132
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
+
133
242
public withG < T , TReturn , TNext > (
134
243
...params : [
135
- ...requests : Array < LockRequest < L > > ,
244
+ ...requests : Array < MultiLockRequest < L > > ,
136
245
g : ( lockBox : LockBox < L > ) => AsyncGenerator < T , TReturn , TNext > ,
137
246
]
138
247
) : AsyncGenerator < T , TReturn , TNext > {
139
248
const g = params . pop ( ) as (
140
249
lockBox : LockBox < L > ,
141
250
) => AsyncGenerator < T , TReturn , TNext > ;
142
251
return withG (
143
- [ this . lock ( ...( params as Array < LockRequest < L > > ) ) ] ,
252
+ [ this . lock ( ...( params as Array < MultiLockRequest < L > > ) ) ] ,
144
253
( [ lockBox ] ) => g ( lockBox ) ,
145
254
) ;
146
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
+ }
147
285
}
148
286
149
287
export default LockBox ;
0 commit comments