1
1
import type { ResourceRelease } from '@matrixai/resources' ;
2
- import type { LockBox , LockRequest as AsyncLocksLockRequest } from '@matrixai/async-locks' ;
2
+ import type { LockBox , MultiLockRequest as AsyncLocksMultiLockRequest } from '@matrixai/async-locks' ;
3
3
import type DB from './DB' ;
4
4
import type {
5
5
ToString ,
@@ -8,7 +8,7 @@ import type {
8
8
DBIteratorOptions ,
9
9
DBClearOptions ,
10
10
DBCountOptions ,
11
- LockRequest ,
11
+ MultiLockRequest ,
12
12
} from './types' ;
13
13
import type {
14
14
RocksDBTransaction ,
@@ -31,7 +31,11 @@ class DBTransaction {
31
31
protected _db : DB ;
32
32
protected logger : Logger ;
33
33
protected lockBox : LockBox < RWLockWriter > ;
34
- protected _locks : Map < string , [ ResourceRelease , RWLockWriter ] > = new Map ( ) ;
34
+ protected _locks : Map < string , {
35
+ lock : RWLockWriter ,
36
+ type : 'read' | 'write' ,
37
+ release : ResourceRelease ,
38
+ } > = new Map ( ) ;
35
39
protected _options : RocksDBTransactionOptions ;
36
40
protected _transaction : RocksDBTransaction ;
37
41
protected _snapshot : RocksDBTransactionSnapshot ;
@@ -114,7 +118,11 @@ class DBTransaction {
114
118
return this . _rollbacked ;
115
119
}
116
120
117
- get locks ( ) : ReadonlyMap < string , [ ResourceRelease , RWLockWriter ] > {
121
+ get locks ( ) : ReadonlyMap < string , {
122
+ lock : RWLockWriter ,
123
+ type : 'read' | 'write' ,
124
+ release : ResourceRelease ,
125
+ } > {
118
126
return this . _locks ;
119
127
}
120
128
@@ -127,41 +135,55 @@ class DBTransaction {
127
135
128
136
/**
129
137
* Lock a sequence of lock requests
130
- * If a lock request is not an array, it is converted to `[ToString, RWLockWriter, 'write']`
138
+ * If the lock request doesn't specify, it
139
+ * defaults to using `RWLockWriter` with `write` type
131
140
* Keys are locked in string sorted order
132
141
* Even though keys can be arbitrary strings, by convention, you should use
133
142
* keys that correspond to keys in the database
134
143
* Locking with the same key is idempotent therefore lock re-entrancy is enabled
135
- * There is no deadlock detection, so becareful!
136
144
* Keys are automatically unlocked in reverse sorted order
137
145
* when the transaction is destroyed
146
+ * There is no support for lock upgrading or downgrading
147
+ * There is no deadlock detection
138
148
*/
139
- public async lock ( ...requests : Array < LockRequest | string > ) : Promise < void > {
140
- const requests_ : Array < AsyncLocksLockRequest < RWLockWriter > > = [ ] ;
149
+ public async lock ( ...requests : Array < MultiLockRequest | string > ) : Promise < void > {
150
+ const requests_ : Array < AsyncLocksMultiLockRequest < RWLockWriter > > = [ ] ;
141
151
for ( const request of requests ) {
142
152
if ( Array . isArray ( request ) ) {
143
- const [ key , ...rest ] = request ;
153
+ const [ key , ...lockingParams ] = request ;
144
154
const key_ = key . toString ( ) ;
145
- if ( ! this . _locks . has ( key_ ) ) {
146
- requests_ . push ( [ key_ , RWLockWriter , ...rest ] ) ;
155
+ const lock = this . _locks . get ( key_ ) ;
156
+ // Default the lock type to `write`
157
+ const lockType = lockingParams [ 0 ] = lockingParams [ 0 ] ?? 'write' ;
158
+ if ( lock == null ) {
159
+ requests_ . push ( [ key_ , RWLockWriter , ...lockingParams ] ) ;
160
+ } else if ( lock . type !== lockType ) {
161
+ throw new errors . ErrorDBTransactionLockType ( ) ;
147
162
}
148
163
} else {
149
164
const key_ = request . toString ( ) ;
150
- if ( ! this . _locks . has ( key_ ) ) {
165
+ const lock = this . _locks . get ( key_ ) ;
166
+ if ( lock == null ) {
151
167
// Default to using `RWLockWriter` write lock for just string keys
152
168
requests_ . push ( [ key_ , RWLockWriter , 'write' ] ) ;
169
+ } else if ( lock . type !== 'write' ) {
170
+ throw new errors . ErrorDBTransactionLockType ( ) ;
153
171
}
154
172
}
155
173
}
156
174
if ( requests_ . length > 0 ) {
157
175
// Duplicates are eliminated, and the returned acquisitions are sorted
158
176
const lockAcquires = this . lockBox . lockMulti ( ...requests_ ) ;
159
- for ( const [ key , lockAcquire ] of lockAcquires ) {
177
+ for ( const [ key , lockAcquire , ... lockingParams ] of lockAcquires ) {
160
178
const [ lockRelease , lock ] = await lockAcquire ( ) ;
161
179
// The `Map` will maintain insertion order
162
180
// these must be unlocked in reverse order
163
181
// when the transaction is destroyed
164
- this . _locks . set ( key as string , [ lockRelease , lock ! ] ) ;
182
+ this . _locks . set ( key as string , {
183
+ lock : lock ! ,
184
+ type : lockingParams [ 0 ] ! , // The `type` is defaulted to `write`
185
+ release : lockRelease ,
186
+ } ) ;
165
187
}
166
188
}
167
189
}
@@ -178,9 +200,8 @@ class DBTransaction {
178
200
const key_ = key . toString ( ) ;
179
201
const lock = this . _locks . get ( key_ ) ;
180
202
if ( lock == null ) continue ;
181
- const [ lockRelease ] = lock ;
182
203
this . _locks . delete ( key_ ) ;
183
- await lockRelease ( ) ;
204
+ await lock . release ( ) ;
184
205
}
185
206
}
186
207
@@ -426,7 +447,9 @@ class DBTransaction {
426
447
this . logger . debug (
427
448
`Failed Committing ${ this . constructor . name } ${ this . id } due to ${ errors . ErrorDBTransactionConflict . name } ` ,
428
449
) ;
429
- throw new errors . ErrorDBTransactionConflict ( undefined , { cause : e } ) ;
450
+ throw new errors . ErrorDBTransactionConflict ( undefined , {
451
+ cause : e ,
452
+ } ) ;
430
453
} else {
431
454
this . logger . debug (
432
455
`Failed Committing ${ this . constructor . name } ${ this . id } due to ${ e . message } ` ,
0 commit comments