Skip to content

Commit dc81eaa

Browse files
authored
Merge pull request #89 from rhashimoto/idb-timeout-workaround
Rework IDBBatchAtomicVFS to avoid transaction timeouts
2 parents 96de5e0 + bddeba9 commit dc81eaa

File tree

6 files changed

+188
-113
lines changed

6 files changed

+188
-113
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "wa-sqlite",
3-
"version": "0.9.1",
3+
"version": "0.9.2",
44
"type": "module",
55
"main": "src/sqlite-api.js",
66
"types": "src/types/index.d.ts",

src/examples/IDBBatchAtomicVFS.js

Lines changed: 67 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { WebLocksExclusive as WebLocks } from './WebLocks.js';
44
import { IDBContext } from './IDBContext.js';
55

66
const SECTOR_SIZE = 512;
7+
const MAX_TASK_MILLIS = 3000;
78

89
/**
910
* @typedef VFSOptions
@@ -53,6 +54,9 @@ export class IDBBatchAtomicVFS extends VFS.Base {
5354
/** @type {IDBContext} */ #idb;
5455
/** @type {Set<string>} */ #pendingPurges = new Set();
5556

57+
#taskTimestamp = performance.now();
58+
#pendingAsync = new Set();
59+
5660
constructor(idbDatabaseName = 'wa-sqlite', options = DEFAULT_OPTIONS) {
5761
super();
5862
this.name = idbDatabaseName;
@@ -67,7 +71,7 @@ export class IDBBatchAtomicVFS extends VFS.Base {
6771
await this.xClose(fileId);
6872
}
6973

70-
this.#idb?.close();
74+
await this.#idb?.close();
7175
this.#idb = null;
7276
}
7377

@@ -203,6 +207,36 @@ export class IDBBatchAtomicVFS extends VFS.Base {
203207
* @returns {number}
204208
*/
205209
xWrite(fileId, pData, iOffset) {
210+
// Handle asynchronously every MAX_TASK_MILLIS milliseconds. This is
211+
// tricky because Asyncify calls asynchronous methods twice: once
212+
// to initiate the call and unwinds the stack, then rewinds the
213+
// stack and calls again to retrieve the completed result.
214+
const rewound = this.#pendingAsync.has(fileId);
215+
if (rewound || performance.now() - this.#taskTimestamp > MAX_TASK_MILLIS) {
216+
const result = this.handleAsync(async () => {
217+
if (this.handleAsync !== super.handleAsync) {
218+
this.#pendingAsync.add(fileId);
219+
}
220+
await new Promise(resolve => setTimeout(resolve));
221+
222+
const result = this.#xWriteHelper(fileId, pData, iOffset);
223+
this.#taskTimestamp = performance.now();
224+
return result;
225+
});
226+
227+
if (rewound) this.#pendingAsync.delete(fileId);
228+
return result;
229+
}
230+
return this.#xWriteHelper(fileId, pData, iOffset);
231+
}
232+
233+
/**
234+
* @param {number} fileId
235+
* @param {Uint8Array} pData
236+
* @param {number} iOffset
237+
* @returns {number}
238+
*/
239+
#xWriteHelper(fileId, pData, iOffset) {
206240
const file = this.#mapIdToFile.get(fileId);
207241
log(`xWrite ${file.path} ${pData.byteLength} ${iOffset}`);
208242

@@ -275,25 +309,49 @@ export class IDBBatchAtomicVFS extends VFS.Base {
275309

276310
/**
277311
* @param {number} fileId
278-
* @param {*} flags
312+
* @param {number} flags
279313
* @returns {number}
280314
*/
281315
xSync(fileId, flags) {
316+
// Skip IndexedDB sync if durability is relaxed and the last
317+
// sync was recent enough.
318+
const rewound = this.#pendingAsync.has(fileId);
319+
if (rewound || this.#options.durability !== 'relaxed' ||
320+
performance.now() - this.#taskTimestamp > MAX_TASK_MILLIS) {
321+
const result = this.handleAsync(async () => {
322+
if (this.handleAsync !== super.handleAsync) {
323+
this.#pendingAsync.add(fileId);
324+
}
325+
326+
const result = await this.#xSyncHelper(fileId, flags);
327+
this.#taskTimestamp = performance.now();
328+
return result;
329+
});
330+
331+
if (rewound) this.#pendingAsync.delete(fileId);
332+
return result;
333+
}
334+
282335
const file = this.#mapIdToFile.get(fileId);
283336
log(`xSync ${file.path} ${flags}`);
337+
return VFS.SQLITE_OK;
338+
}
284339

340+
/**
341+
* @param {number} fileId
342+
* @param {number} flags
343+
* @returns {Promise<number>}
344+
*/
345+
async #xSyncHelper(fileId, flags) {
346+
const file = this.#mapIdToFile.get(fileId);
347+
log(`xSync ${file.path} ${flags}`);
285348
try {
286-
if (this.#options.durability !== 'relaxed') {
287-
return this.handleAsync(async () => {
288-
await this.#idb.sync();
289-
return VFS.SQLITE_OK;
290-
});
291-
}
292-
return VFS.SQLITE_OK;
349+
await this.#idb.sync();
293350
} catch (e) {
294351
console.error(e);
295352
return VFS.SQLITE_IOERR;
296353
}
354+
return VFS.SQLITE_OK;
297355
}
298356

299357
/**

0 commit comments

Comments
 (0)