@livestore/wa-sqlite 0.4.0-dev.22 → 0.4.0-dev.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -36
- package/dist/README.md +13 -13
- package/dist/fts5/wa-sqlite.mjs +1 -1
- package/dist/fts5/wa-sqlite.node.mjs +1 -1
- package/dist/fts5/wa-sqlite.node.wasm +0 -0
- package/dist/fts5/wa-sqlite.wasm +0 -0
- package/dist/wa-sqlite-async.mjs +1 -1
- package/dist/wa-sqlite-async.wasm +0 -0
- package/dist/wa-sqlite-jspi.mjs +1 -1
- package/dist/wa-sqlite-jspi.wasm +0 -0
- package/dist/wa-sqlite.mjs +1 -1
- package/dist/wa-sqlite.node.mjs +1 -1
- package/dist/wa-sqlite.node.wasm +0 -0
- package/dist/wa-sqlite.wasm +0 -0
- package/package.json +40 -29
- package/src/FacadeVFS.js +252 -261
- package/src/VFS.js +84 -85
- package/src/WebLocksMixin.js +357 -351
- package/src/examples/AccessHandlePoolVFS.js +185 -194
- package/src/examples/IDBBatchAtomicVFS.js +429 -409
- package/src/examples/IDBMirrorVFS.js +402 -409
- package/src/examples/MemoryAsyncVFS.js +32 -37
- package/src/examples/MemoryVFS.js +71 -75
- package/src/examples/OPFSAdaptiveVFS.js +206 -206
- package/src/examples/OPFSAnyContextVFS.js +141 -140
- package/src/examples/OPFSCoopSyncVFS.js +297 -299
- package/src/examples/OPFSPermutedVFS.js +529 -540
- package/src/examples/README.md +27 -15
- package/src/examples/tag.js +27 -27
- package/src/sqlite-api.js +910 -941
- package/src/sqlite-constants.js +246 -232
- package/src/types/globals.d.ts +52 -52
- package/src/types/index.d.ts +586 -576
- package/test/AccessHandlePoolVFS.test.js +21 -21
- package/test/IDBBatchAtomicVFS.test.js +69 -69
- package/test/IDBMirrorVFS.test.js +21 -21
- package/test/MemoryAsyncVFS.test.js +21 -21
- package/test/MemoryVFS.test.js +21 -21
- package/test/OPFSAdaptiveVFS.test.js +21 -21
- package/test/OPFSAnyContextVFS.test.js +21 -21
- package/test/OPFSCoopSyncVFS.test.js +21 -21
- package/test/OPFSPermutedVFS.test.js +21 -21
- package/test/TestContext.js +44 -41
- package/test/WebLocksMixin.test.js +369 -360
- package/test/api.test.js +23 -23
- package/test/api_exec.js +72 -61
- package/test/api_misc.js +53 -54
- package/test/api_statements.js +271 -279
- package/test/callbacks.test.js +492 -478
- package/test/data/idbv5.json +1135 -1
- package/test/sql.test.js +30 -30
- package/test/sql_0001.js +49 -33
- package/test/sql_0002.js +55 -34
- package/test/sql_0003.js +85 -49
- package/test/sql_0004.js +76 -47
- package/test/sql_0005.js +60 -44
- package/test/test-worker.js +171 -163
- package/test/vfs_xAccess.js +1 -2
- package/test/vfs_xClose.js +50 -49
- package/test/vfs_xOpen.js +73 -72
- package/test/vfs_xRead.js +31 -31
- package/test/vfs_xWrite.js +30 -29
package/src/WebLocksMixin.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import * as VFS from './VFS.js'
|
|
1
|
+
import * as VFS from './VFS.js'
|
|
2
2
|
|
|
3
3
|
// Options for navigator.locks.request().
|
|
4
|
-
/** @type {LockOptions} */ const SHARED = { mode: 'shared' }
|
|
5
|
-
/** @type {LockOptions} */ const POLL_SHARED = { ifAvailable: true, mode: 'shared' }
|
|
6
|
-
/** @type {LockOptions} */ const POLL_EXCLUSIVE = { ifAvailable: true, mode: 'exclusive' }
|
|
4
|
+
/** @type {LockOptions} */ const SHARED = { mode: 'shared' }
|
|
5
|
+
/** @type {LockOptions} */ const POLL_SHARED = { ifAvailable: true, mode: 'shared' }
|
|
6
|
+
/** @type {LockOptions} */ const POLL_EXCLUSIVE = { ifAvailable: true, mode: 'exclusive' }
|
|
7
7
|
|
|
8
|
-
const POLICIES = ['exclusive', 'shared', 'shared+hint']
|
|
8
|
+
const POLICIES = ['exclusive', 'shared', 'shared+hint']
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* @typedef LockState
|
|
12
12
|
* @property {string} baseName
|
|
13
13
|
* @property {number} type
|
|
14
14
|
* @property {boolean} writeHint
|
|
15
|
-
*
|
|
15
|
+
*
|
|
16
16
|
* These properties are functions that release a specific lock.
|
|
17
17
|
* @property {(() => void)?} [gate]
|
|
18
18
|
* @property {(() => void)?} [access]
|
|
@@ -23,392 +23,398 @@ const POLICIES = ['exclusive', 'shared', 'shared+hint'];
|
|
|
23
23
|
/**
|
|
24
24
|
* Mix-in for FacadeVFS that implements the SQLite VFS locking protocol.
|
|
25
25
|
* @param {*} superclass FacadeVFS (or subclass)
|
|
26
|
-
* @returns
|
|
26
|
+
* @returns
|
|
27
27
|
*/
|
|
28
|
-
export const WebLocksMixin = superclass =>
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
export const WebLocksMixin = (superclass) =>
|
|
29
|
+
class extends superclass {
|
|
30
|
+
#options = {
|
|
31
|
+
lockPolicy: 'exclusive',
|
|
32
|
+
lockTimeout: Infinity,
|
|
33
|
+
}
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
/** @type {Map<number, LockState>} */ #mapIdToState = new Map()
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
constructor(name, module, options) {
|
|
38
|
+
super(name, module, options)
|
|
39
|
+
Object.assign(this.#options, options)
|
|
40
|
+
if (POLICIES.indexOf(this.#options.lockPolicy) === -1) {
|
|
41
|
+
throw new Error(`WebLocksMixin: invalid lock mode: ${options.lockPolicy}`)
|
|
42
|
+
}
|
|
41
43
|
}
|
|
42
|
-
}
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
45
|
+
/**
|
|
46
|
+
* @param {number} fileId
|
|
47
|
+
* @param {number} lockType
|
|
48
|
+
* @returns {Promise<number>}
|
|
49
|
+
*/
|
|
50
|
+
async jLock(fileId, lockType) {
|
|
51
|
+
try {
|
|
52
|
+
// Create state on first lock.
|
|
53
|
+
if (!this.#mapIdToState.has(fileId)) {
|
|
54
|
+
const name = this.getFilename(fileId)
|
|
55
|
+
const state = {
|
|
56
|
+
baseName: name,
|
|
57
|
+
type: VFS.SQLITE_LOCK_NONE,
|
|
58
|
+
writeHint: false,
|
|
59
|
+
}
|
|
60
|
+
this.#mapIdToState.set(fileId, state)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const lockState = this.#mapIdToState.get(fileId)
|
|
64
|
+
if (lockType <= lockState.type) return VFS.SQLITE_OK
|
|
61
65
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
switch (this.#options.lockPolicy) {
|
|
67
|
+
case 'exclusive':
|
|
68
|
+
return await this.#lockExclusive(lockState, lockType)
|
|
69
|
+
case 'shared':
|
|
70
|
+
case 'shared+hint':
|
|
71
|
+
return await this.#lockShared(lockState, lockType)
|
|
72
|
+
}
|
|
73
|
+
} catch (e) {
|
|
74
|
+
console.error('WebLocksMixin: lock error', e)
|
|
75
|
+
return VFS.SQLITE_IOERR_LOCK
|
|
71
76
|
}
|
|
72
|
-
} catch (e) {
|
|
73
|
-
console.error('WebLocksMixin: lock error', e);
|
|
74
|
-
return VFS.SQLITE_IOERR_LOCK;
|
|
75
77
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @param {number} fileId
|
|
81
|
+
* @param {number} lockType
|
|
82
|
+
* @returns {Promise<number>}
|
|
83
|
+
*/
|
|
84
|
+
async jUnlock(fileId, lockType) {
|
|
85
|
+
try {
|
|
86
|
+
// SQLite can call xUnlock() without ever calling xLock() so
|
|
87
|
+
// the state may not exist.
|
|
88
|
+
const lockState = this.#mapIdToState.get(fileId)
|
|
89
|
+
if (!(lockType < lockState?.type)) return VFS.SQLITE_OK
|
|
90
|
+
|
|
91
|
+
switch (this.#options.lockPolicy) {
|
|
92
|
+
case 'exclusive':
|
|
93
|
+
return await this.#unlockExclusive(lockState, lockType)
|
|
94
|
+
case 'shared':
|
|
95
|
+
case 'shared+hint':
|
|
96
|
+
return await this.#unlockShared(lockState, lockType)
|
|
97
|
+
}
|
|
98
|
+
} catch (e) {
|
|
99
|
+
console.error('WebLocksMixin: unlock error', e)
|
|
100
|
+
return VFS.SQLITE_IOERR_UNLOCK
|
|
96
101
|
}
|
|
97
|
-
} catch (e) {
|
|
98
|
-
console.error('WebLocksMixin: unlock error', e);
|
|
99
|
-
return VFS.SQLITE_IOERR_UNLOCK;
|
|
100
102
|
}
|
|
101
|
-
}
|
|
102
103
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
104
|
+
/**
|
|
105
|
+
* @param {number} fileId
|
|
106
|
+
* @param {DataView} pResOut
|
|
107
|
+
* @returns {Promise<number>}
|
|
108
|
+
*/
|
|
109
|
+
async jCheckReservedLock(fileId, pResOut) {
|
|
110
|
+
try {
|
|
111
|
+
const lockState = this.#mapIdToState.get(fileId)
|
|
112
|
+
switch (this.#options.lockPolicy) {
|
|
113
|
+
case 'exclusive':
|
|
114
|
+
return this.#checkReservedExclusive(lockState, pResOut)
|
|
115
|
+
case 'shared':
|
|
116
|
+
case 'shared+hint':
|
|
117
|
+
return await this.#checkReservedShared(lockState, pResOut)
|
|
118
|
+
}
|
|
119
|
+
} catch (e) {
|
|
120
|
+
console.error('WebLocksMixin: check reserved lock error', e)
|
|
121
|
+
return VFS.SQLITE_IOERR_CHECKRESERVEDLOCK
|
|
117
122
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return VFS.SQLITE_IOERR_CHECKRESERVEDLOCK;
|
|
123
|
+
pResOut.setInt32(0, 0, true)
|
|
124
|
+
return VFS.SQLITE_OK
|
|
121
125
|
}
|
|
122
|
-
pResOut.setInt32(0, 0, true);
|
|
123
|
-
return VFS.SQLITE_OK;
|
|
124
|
-
}
|
|
125
126
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
127
|
+
/**
|
|
128
|
+
* @param {number} fileId
|
|
129
|
+
* @param {number} op
|
|
130
|
+
* @param {DataView} pArg
|
|
131
|
+
* @returns {number|Promise<number>}
|
|
132
|
+
*/
|
|
133
|
+
jFileControl(fileId, op, pArg) {
|
|
134
|
+
const lockState =
|
|
135
|
+
this.#mapIdToState.get(fileId) ??
|
|
136
|
+
(() => {
|
|
137
|
+
// Call jLock() to create the lock state.
|
|
138
|
+
this.jLock(fileId, VFS.SQLITE_LOCK_NONE)
|
|
139
|
+
return this.#mapIdToState.get(fileId)
|
|
140
|
+
})()
|
|
141
|
+
if (op === WebLocksMixin.WRITE_HINT_OP_CODE && this.#options.lockPolicy === 'shared+hint') {
|
|
142
|
+
lockState.writeHint = true
|
|
143
|
+
}
|
|
144
|
+
return VFS.SQLITE_NOTFOUND
|
|
142
145
|
}
|
|
143
|
-
return VFS.SQLITE_NOTFOUND;
|
|
144
|
-
}
|
|
145
146
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
147
|
+
/**
|
|
148
|
+
* @param {LockState} lockState
|
|
149
|
+
* @param {number} lockType
|
|
150
|
+
* @returns
|
|
151
|
+
*/
|
|
152
|
+
async #lockExclusive(lockState, lockType) {
|
|
153
|
+
if (!lockState.access) {
|
|
154
|
+
if (!(await this.#acquire(lockState, 'access'))) {
|
|
155
|
+
return VFS.SQLITE_BUSY
|
|
156
|
+
}
|
|
157
|
+
console.assert(!!lockState.access)
|
|
155
158
|
}
|
|
156
|
-
|
|
159
|
+
lockState.type = lockType
|
|
160
|
+
return VFS.SQLITE_OK
|
|
157
161
|
}
|
|
158
|
-
lockState.type = lockType;
|
|
159
|
-
return VFS.SQLITE_OK;
|
|
160
|
-
}
|
|
161
162
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
163
|
+
/**
|
|
164
|
+
* @param {LockState} lockState
|
|
165
|
+
* @param {number} lockType
|
|
166
|
+
* @returns {number}
|
|
167
|
+
*/
|
|
168
|
+
#unlockExclusive(lockState, lockType) {
|
|
169
|
+
if (lockType === VFS.SQLITE_LOCK_NONE) {
|
|
170
|
+
lockState.access?.()
|
|
171
|
+
console.assert(!lockState.access)
|
|
172
|
+
}
|
|
173
|
+
lockState.type = lockType
|
|
174
|
+
return VFS.SQLITE_OK
|
|
171
175
|
}
|
|
172
|
-
lockState.type = lockType;
|
|
173
|
-
return VFS.SQLITE_OK;
|
|
174
|
-
}
|
|
175
176
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
177
|
+
/**
|
|
178
|
+
* @param {LockState} lockState
|
|
179
|
+
* @param {DataView} pResOut
|
|
180
|
+
* @returns {number}
|
|
181
|
+
*/
|
|
182
|
+
#checkReservedExclusive(lockState, pResOut) {
|
|
183
|
+
pResOut.setInt32(0, 0, true)
|
|
184
|
+
return VFS.SQLITE_OK
|
|
185
|
+
}
|
|
185
186
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
187
|
+
/**
|
|
188
|
+
* @param {LockState} lockState
|
|
189
|
+
* @param {number} lockType
|
|
190
|
+
* @returns
|
|
191
|
+
*/
|
|
192
|
+
async #lockShared(lockState, lockType) {
|
|
193
|
+
switch (lockState.type) {
|
|
194
|
+
case VFS.SQLITE_LOCK_NONE:
|
|
195
|
+
switch (lockType) {
|
|
196
|
+
case VFS.SQLITE_LOCK_SHARED:
|
|
197
|
+
if (lockState.writeHint) {
|
|
198
|
+
// xFileControl() has hinted that this transaction will
|
|
199
|
+
// write. Acquire the hint lock, which is required to reach
|
|
200
|
+
// the RESERVED state.
|
|
201
|
+
if (!(await this.#acquire(lockState, 'hint'))) {
|
|
202
|
+
// Timeout before lock acquired.
|
|
203
|
+
return VFS.SQLITE_BUSY
|
|
204
|
+
}
|
|
203
205
|
}
|
|
204
|
-
}
|
|
205
206
|
|
|
206
|
-
|
|
207
|
-
|
|
207
|
+
// Must have the gate lock to request the access lock.
|
|
208
|
+
if (!(await this.#acquire(lockState, 'gate', SHARED))) {
|
|
208
209
|
// Timeout before lock acquired.
|
|
209
|
-
lockState.hint?.()
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
210
|
+
lockState.hint?.()
|
|
211
|
+
return VFS.SQLITE_BUSY
|
|
212
|
+
}
|
|
213
|
+
await this.#acquire(lockState, 'access', SHARED)
|
|
214
|
+
lockState.gate()
|
|
215
|
+
console.assert(!lockState.gate)
|
|
216
|
+
console.assert(!!lockState.access)
|
|
217
|
+
console.assert(!lockState.reserved)
|
|
218
|
+
break
|
|
218
219
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
220
|
+
default:
|
|
221
|
+
throw new Error('unsupported lock transition')
|
|
222
|
+
}
|
|
223
|
+
break
|
|
224
|
+
case VFS.SQLITE_LOCK_SHARED:
|
|
225
|
+
switch (lockType) {
|
|
226
|
+
case VFS.SQLITE_LOCK_RESERVED:
|
|
227
|
+
if (this.#options.lockPolicy === 'shared+hint') {
|
|
228
|
+
// Ideally we should already have the hint lock, but if not
|
|
229
|
+
// poll for it here.
|
|
230
|
+
if (!lockState.hint && !(await this.#acquire(lockState, 'hint', POLL_EXCLUSIVE))) {
|
|
231
|
+
// Another connection has the hint lock so this is a
|
|
232
|
+
// deadlock. This connection must retry.
|
|
233
|
+
return VFS.SQLITE_BUSY
|
|
234
|
+
}
|
|
234
235
|
}
|
|
235
|
-
}
|
|
236
236
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
237
|
+
// Poll for the reserved lock. This should always succeed
|
|
238
|
+
// if all clients use the 'shared+hint' policy.
|
|
239
|
+
if (!(await this.#acquire(lockState, 'reserved', POLL_EXCLUSIVE))) {
|
|
240
|
+
// This is a deadlock. The connection holding the reserved
|
|
241
|
+
// lock blocks us, and it can't acquire an exclusive access
|
|
242
|
+
// lock because we hold a shared access lock. This connection
|
|
243
|
+
// must retry.
|
|
244
|
+
lockState.hint?.()
|
|
245
|
+
return VFS.SQLITE_BUSY
|
|
246
|
+
}
|
|
247
|
+
lockState.access()
|
|
248
|
+
console.assert(!lockState.gate)
|
|
249
|
+
console.assert(!lockState.access)
|
|
250
|
+
console.assert(!!lockState.reserved)
|
|
251
|
+
break
|
|
252
252
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
253
|
+
case VFS.SQLITE_LOCK_EXCLUSIVE:
|
|
254
|
+
// Jumping directly from SHARED to EXCLUSIVE without passing
|
|
255
|
+
// through RESERVED is only done with a hot journal.
|
|
256
|
+
if (!(await this.#acquire(lockState, 'gate'))) {
|
|
257
|
+
// Timeout before lock acquired.
|
|
258
|
+
return VFS.SQLITE_BUSY
|
|
259
|
+
}
|
|
260
|
+
lockState.access()
|
|
261
|
+
if (!(await this.#acquire(lockState, 'access'))) {
|
|
262
|
+
// Timeout before lock acquired.
|
|
263
|
+
lockState.gate()
|
|
264
|
+
return VFS.SQLITE_BUSY
|
|
265
|
+
}
|
|
266
|
+
console.assert(!!lockState.gate)
|
|
267
|
+
console.assert(!!lockState.access)
|
|
268
|
+
console.assert(!lockState.reserved)
|
|
269
|
+
break
|
|
270
270
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
271
|
+
default:
|
|
272
|
+
throw new Error('unsupported lock transition')
|
|
273
|
+
}
|
|
274
|
+
break
|
|
275
|
+
case VFS.SQLITE_LOCK_RESERVED:
|
|
276
|
+
switch (lockType) {
|
|
277
|
+
case VFS.SQLITE_LOCK_EXCLUSIVE:
|
|
278
|
+
// Prevent other connections from entering the SHARED state.
|
|
279
|
+
if (!(await this.#acquire(lockState, 'gate'))) {
|
|
280
|
+
// Timeout before lock acquired.
|
|
281
|
+
return VFS.SQLITE_BUSY
|
|
282
|
+
}
|
|
283
283
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
284
|
+
// Block until all other connections exit the SHARED state.
|
|
285
|
+
if (!(await this.#acquire(lockState, 'access'))) {
|
|
286
|
+
// Timeout before lock acquired.
|
|
287
|
+
lockState.gate()
|
|
288
|
+
return VFS.SQLITE_BUSY
|
|
289
|
+
}
|
|
290
|
+
console.assert(!!lockState.gate)
|
|
291
|
+
console.assert(!!lockState.access)
|
|
292
|
+
console.assert(!!lockState.reserved)
|
|
293
|
+
break
|
|
294
294
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
295
|
+
default:
|
|
296
|
+
throw new Error('unsupported lock transition')
|
|
297
|
+
}
|
|
298
|
+
break
|
|
299
|
+
}
|
|
300
|
+
lockState.type = lockType
|
|
301
|
+
return VFS.SQLITE_OK
|
|
299
302
|
}
|
|
300
|
-
lockState.type = lockType;
|
|
301
|
-
return VFS.SQLITE_OK;
|
|
302
|
-
}
|
|
303
303
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
304
|
+
/**
|
|
305
|
+
* @param {LockState} lockState
|
|
306
|
+
* @param {number} lockType
|
|
307
|
+
* @returns
|
|
308
|
+
*/
|
|
309
|
+
async #unlockShared(lockState, lockType) {
|
|
310
|
+
// lockType can only be SQLITE_LOCK_SHARED or SQLITE_LOCK_NONE.
|
|
311
|
+
if (lockType === VFS.SQLITE_LOCK_NONE) {
|
|
312
|
+
lockState.access?.()
|
|
313
|
+
lockState.gate?.()
|
|
314
|
+
lockState.reserved?.()
|
|
315
|
+
lockState.hint?.()
|
|
316
|
+
lockState.writeHint = false
|
|
317
|
+
console.assert(!lockState.access)
|
|
318
|
+
console.assert(!lockState.gate)
|
|
319
|
+
console.assert(!lockState.reserved)
|
|
320
|
+
console.assert(!lockState.hint)
|
|
321
|
+
} else {
|
|
322
|
+
// lockType === VFS.SQLITE_LOCK_SHARED
|
|
323
|
+
switch (lockState.type) {
|
|
324
|
+
case VFS.SQLITE_LOCK_EXCLUSIVE:
|
|
325
|
+
// Release our exclusive access lock and reacquire it with a
|
|
326
|
+
// shared lock. This should always succeed because we hold
|
|
327
|
+
// the gate lock.
|
|
328
|
+
lockState.access()
|
|
329
|
+
await this.#acquire(lockState, 'access', SHARED)
|
|
329
330
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
331
|
+
// Release our gate and reserved locks. We might not have a
|
|
332
|
+
// reserved lock if we were handling a hot journal.
|
|
333
|
+
lockState.gate()
|
|
334
|
+
lockState.reserved?.()
|
|
335
|
+
lockState.hint?.()
|
|
336
|
+
console.assert(!!lockState.access)
|
|
337
|
+
console.assert(!lockState.gate)
|
|
338
|
+
console.assert(!lockState.reserved)
|
|
339
|
+
break
|
|
339
340
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
341
|
+
case VFS.SQLITE_LOCK_RESERVED:
|
|
342
|
+
// This transition is rare, probably only on an I/O error
|
|
343
|
+
// while writing to a journal file.
|
|
344
|
+
await this.#acquire(lockState, 'access', SHARED)
|
|
345
|
+
lockState.reserved()
|
|
346
|
+
lockState.hint?.()
|
|
347
|
+
console.assert(!!lockState.access)
|
|
348
|
+
console.assert(!lockState.gate)
|
|
349
|
+
console.assert(!lockState.reserved)
|
|
350
|
+
break
|
|
351
|
+
}
|
|
350
352
|
}
|
|
353
|
+
lockState.type = lockType
|
|
354
|
+
return VFS.SQLITE_OK
|
|
351
355
|
}
|
|
352
|
-
lockState.type = lockType;
|
|
353
|
-
return VFS.SQLITE_OK;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/**
|
|
357
|
-
* @param {LockState} lockState
|
|
358
|
-
* @param {DataView} pResOut
|
|
359
|
-
* @returns {Promise<number>}
|
|
360
|
-
*/
|
|
361
|
-
async #checkReservedShared(lockState, pResOut) {
|
|
362
|
-
if (await this.#acquire(lockState, 'reserved', POLL_SHARED)) {
|
|
363
|
-
// We were able to get the lock so it was not reserved.
|
|
364
|
-
lockState.reserved();
|
|
365
|
-
pResOut.setInt32(0, 0, true);
|
|
366
|
-
} else {
|
|
367
|
-
pResOut.setInt32(0, 1, true);
|
|
368
|
-
}
|
|
369
|
-
return VFS.SQLITE_OK;
|
|
370
|
-
}
|
|
371
356
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
options = Object.assign({}, options, { signal: controller.signal });
|
|
385
|
-
setTimeout(() => {
|
|
386
|
-
controller.abort();
|
|
387
|
-
resolve?.(false);
|
|
388
|
-
}, this.#options.lockTimeout);
|
|
357
|
+
/**
|
|
358
|
+
* @param {LockState} lockState
|
|
359
|
+
* @param {DataView} pResOut
|
|
360
|
+
* @returns {Promise<number>}
|
|
361
|
+
*/
|
|
362
|
+
async #checkReservedShared(lockState, pResOut) {
|
|
363
|
+
if (await this.#acquire(lockState, 'reserved', POLL_SHARED)) {
|
|
364
|
+
// We were able to get the lock so it was not reserved.
|
|
365
|
+
lockState.reserved()
|
|
366
|
+
pResOut.setInt32(0, 0, true)
|
|
367
|
+
} else {
|
|
368
|
+
pResOut.setInt32(0, 1, true)
|
|
389
369
|
}
|
|
370
|
+
return VFS.SQLITE_OK
|
|
371
|
+
}
|
|
390
372
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
373
|
+
/**
|
|
374
|
+
* @param {LockState} lockState
|
|
375
|
+
* @param {'gate'|'access'|'reserved'|'hint'} name
|
|
376
|
+
* @param {LockOptions} options
|
|
377
|
+
* @returns {Promise<boolean>}
|
|
378
|
+
*/
|
|
379
|
+
#acquire(lockState, name, options = {}) {
|
|
380
|
+
console.assert(!lockState[name])
|
|
381
|
+
return new Promise((resolve) => {
|
|
382
|
+
if (!options.ifAvailable && this.#options.lockTimeout < Infinity) {
|
|
383
|
+
// Add a timeout to the lock request.
|
|
384
|
+
const controller = new AbortController()
|
|
385
|
+
options = Object.assign({}, options, { signal: controller.signal })
|
|
386
|
+
setTimeout(
|
|
387
|
+
() => {
|
|
388
|
+
controller.abort()
|
|
389
|
+
resolve?.(false)
|
|
390
|
+
},
|
|
391
|
+
this.#options.lockTimeout,
|
|
392
|
+
)
|
|
406
393
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
394
|
+
|
|
395
|
+
const lockName = `lock##${lockState.baseName}##${name}`
|
|
396
|
+
navigator.locks
|
|
397
|
+
.request(lockName, options, (lock) => {
|
|
398
|
+
if (lock) {
|
|
399
|
+
return new Promise((release) => {
|
|
400
|
+
lockState[name] = () => {
|
|
401
|
+
release()
|
|
402
|
+
lockState[name] = null
|
|
403
|
+
}
|
|
404
|
+
resolve(true)
|
|
405
|
+
resolve = null
|
|
406
|
+
})
|
|
407
|
+
} else {
|
|
408
|
+
lockState[name] = null
|
|
409
|
+
resolve(false)
|
|
410
|
+
resolve = null
|
|
411
|
+
}
|
|
412
|
+
})
|
|
413
|
+
.catch((e) => {
|
|
414
|
+
if (e.name !== 'AbortError') throw e
|
|
415
|
+
})
|
|
416
|
+
})
|
|
417
|
+
}
|
|
411
418
|
}
|
|
412
|
-
}
|
|
413
419
|
|
|
414
|
-
WebLocksMixin.WRITE_HINT_OP_CODE = -9999
|
|
420
|
+
WebLocksMixin.WRITE_HINT_OP_CODE = -9999
|