1
1
import {
2
- ISQLite ,
3
2
ConcurrentLockType ,
4
- QuickSQLiteConnection ,
5
3
ContextLockID ,
4
+ ISQLite ,
6
5
LockContext ,
7
6
LockOptions ,
8
- TransactionContext ,
9
- UpdateCallback ,
10
- SQLBatchTuple ,
11
7
OpenOptions ,
12
- QueryResult
8
+ QueryResult ,
9
+ QuickSQLiteConnection ,
10
+ SQLBatchTuple ,
11
+ TransactionContext ,
12
+ UpdateCallback
13
13
} from './types' ;
14
14
15
- import { enhanceQueryResult } from './utils' ;
16
15
import { DBListenerManagerInternal } from './DBListenerManager' ;
17
16
import { LockHooks } from './lock-hooks' ;
17
+ import { enhanceQueryResult } from './utils' ;
18
18
19
19
type LockCallbackRecord = {
20
20
callback : ( context : LockContext ) => Promise < any > ;
@@ -39,11 +39,16 @@ const getRequestId = () => {
39
39
const LockCallbacks : Record < ContextLockID , LockCallbackRecord > = { } ;
40
40
let proxy : ISQLite ;
41
41
42
+ /**
43
+ * Creates a unique identifier for all a database's lock requests
44
+ */
45
+ const lockKey = ( dbName : string , id : ContextLockID ) => `${ dbName } :${ id } ` ;
46
+
42
47
/**
43
48
* Closes the context in JS and C++
44
49
*/
45
50
function closeContextLock ( dbName : string , id : ContextLockID ) {
46
- delete LockCallbacks [ id ] ;
51
+ delete LockCallbacks [ lockKey ( dbName , id ) ] ;
47
52
48
53
// This is configured by the setupOpen function
49
54
proxy . releaseLock ( dbName , id ) ;
@@ -59,7 +64,11 @@ global.onLockContextIsAvailable = async (dbName: string, lockId: ContextLockID)
59
64
// Don't hold C++ bridge side up waiting to complete
60
65
setImmediate ( async ( ) => {
61
66
try {
62
- const record = LockCallbacks [ lockId ] ;
67
+ const key = lockKey ( dbName , lockId ) ;
68
+ const record = LockCallbacks [ key ] ;
69
+ // clear record after fetching, the hash should only contain pending requests
70
+ delete LockCallbacks [ key ] ;
71
+
63
72
if ( record ?. timeout ) {
64
73
clearTimeout ( record . timeout ) ;
65
74
}
@@ -116,12 +125,12 @@ export function setupOpen(QuickSQLite: ISQLite) {
116
125
// Wrap the callback in a promise that will resolve to the callback result
117
126
return new Promise < T > ( ( resolve , reject ) => {
118
127
// Add callback to the queue for timing
119
- const record = ( LockCallbacks [ id ] = {
128
+ const key = lockKey ( dbName , id ) ;
129
+ const record = ( LockCallbacks [ key ] = {
120
130
callback : async ( context : LockContext ) => {
121
131
try {
122
132
await hooks ?. lockAcquired ?.( ) ;
123
133
const res = await callback ( context ) ;
124
-
125
134
closeContextLock ( dbName , id ) ;
126
135
resolve ( res ) ;
127
136
} catch ( ex ) {
@@ -134,18 +143,19 @@ export function setupOpen(QuickSQLite: ISQLite) {
134
143
} as LockCallbackRecord ) ;
135
144
136
145
try {
146
+ // throws if lock could not be requested
137
147
QuickSQLite . requestLock ( dbName , id , type ) ;
138
148
const timeout = options ?. timeoutMs ;
139
149
if ( timeout ) {
140
150
record . timeout = setTimeout ( ( ) => {
141
151
// The callback won't be executed
142
- delete LockCallbacks [ id ] ;
152
+ delete LockCallbacks [ key ] ;
143
153
reject ( new Error ( `Lock request timed out after ${ timeout } ms` ) ) ;
144
154
} , timeout ) ;
145
155
}
146
156
} catch ( ex ) {
147
157
// Remove callback from the queue
148
- delete LockCallbacks [ id ] ;
158
+ delete LockCallbacks [ key ] ;
149
159
reject ( ex ) ;
150
160
}
151
161
} ) ;
@@ -224,7 +234,26 @@ export function setupOpen(QuickSQLite: ISQLite) {
224
234
225
235
// Return the concurrent connection object
226
236
return {
227
- close : ( ) => QuickSQLite . close ( dbName ) ,
237
+ close : ( ) => {
238
+ QuickSQLite . close ( dbName ) ;
239
+ // Reject any pending lock requests
240
+ Object . entries ( LockCallbacks ) . forEach ( ( [ key , record ] ) => {
241
+ const recordDBName = key . split ( ':' ) [ 0 ] ;
242
+ if ( dbName !== recordDBName ) {
243
+ return ;
244
+ }
245
+ // A bit of a hack, let the callbacks run with an execute method that will fail
246
+ record
247
+ . callback ( {
248
+ execute : async ( ) => {
249
+ throw new Error ( 'Connection is closed' ) ;
250
+ }
251
+ } )
252
+ . catch ( ( ) => { } ) ;
253
+
254
+ delete LockCallbacks [ key ] ;
255
+ } ) ;
256
+ } ,
228
257
refreshSchema : ( ) => QuickSQLite . refreshSchema ( dbName ) ,
229
258
execute : ( sql : string , args ?: any [ ] ) => writeLock ( ( context ) => context . execute ( sql , args ) ) ,
230
259
readLock,
0 commit comments