diff --git a/src/examples/IDBBatchAtomicVFS.js b/src/examples/IDBBatchAtomicVFS.js index 9b5d6319..24cd0e61 100644 --- a/src/examples/IDBBatchAtomicVFS.js +++ b/src/examples/IDBBatchAtomicVFS.js @@ -58,6 +58,12 @@ export class IDBBatchAtomicVFS extends VFS.Base { #taskTimestamp = performance.now(); #pendingAsync = new Set(); + // Asyncify can grow WebAssembly memory during an asynchronous call. + // If this happens, then any array buffer arguments will be detached. + // The workaround is when finding a detached buffer, set this handler + // function to process the new buffer outside handlerAsync(). + #growthHandler = null; + constructor(idbDatabaseName = 'wa-sqlite', options = DEFAULT_OPTIONS) { super(); this.name = idbDatabaseName; @@ -84,7 +90,7 @@ export class IDBBatchAtomicVFS extends VFS.Base { * @returns {number} */ xOpen(name, fileId, flags, pOutFlags) { - return this.handleAsync(async () => { + const result = this.handleAsync(async () => { if (name === null) name = `null_${fileId}`; log(`xOpen ${name} 0x${fileId.toString(16)} 0x${flags.toString(16)}`); @@ -118,6 +124,14 @@ export class IDBBatchAtomicVFS extends VFS.Base { } } }); + + // @ts-ignore + if (pOutFlags.buffer.detached) { + pOutFlags = new DataView(new ArrayBuffer(4)); + this.#growthHandler = (pOutFlagsNew) => { + pOutFlagsNew.setInt32(0, pOutFlags.getInt32(0, true), true); + }; + } pOutFlags.setInt32(0, flags & VFS.SQLITE_OPEN_READONLY, true); return VFS.SQLITE_OK; } catch (e) { @@ -125,6 +139,10 @@ export class IDBBatchAtomicVFS extends VFS.Base { return VFS.SQLITE_CANTOPEN; } }); + + this.#growthHandler?.(pOutFlags); + this.#growthHandler = null; + return result; } /** @@ -160,7 +178,8 @@ export class IDBBatchAtomicVFS extends VFS.Base { * @returns {number} */ xRead(fileId, pData, iOffset) { - return this.handleAsync(async () => { + const byteLength = pData.byteLength; + const result = this.handleAsync(async () => { const file = this.#mapIdToFile.get(fileId); log(`xRead ${file.path} ${pData.byteLength} ${iOffset}`); @@ -170,6 +189,15 @@ export class IDBBatchAtomicVFS extends VFS.Base { // one case - rollback after journal spill - where reads cross // write boundaries so we have to allow for that. const result = await this.#idb.run('readonly', async ({blocks}) => { + // @ts-ignore + if (pData.buffer.detached) { + // WebAssembly memory has grown, invalidating our buffer. Use + // a temporary buffer and copy after this asynchronous call + // completes. + pData = new Uint8Array(byteLength); + this.#growthHandler = (pDataNew) => pDataNew.set(pData); + } + let pDataOffset = 0; while (pDataOffset < pData.byteLength) { // Fetch the IndexedDB block for this file location. @@ -200,6 +228,10 @@ export class IDBBatchAtomicVFS extends VFS.Base { return VFS.SQLITE_IOERR; } }); + + this.#growthHandler?.(pData); + this.#growthHandler = null; + return result; } /** @@ -221,7 +253,7 @@ export class IDBBatchAtomicVFS extends VFS.Base { } await new Promise(resolve => setTimeout(resolve)); - const result = this.#xWriteHelper(fileId, pData, iOffset); + const result = this.#xWriteHelper(fileId, pData.slice(), iOffset); this.#taskTimestamp = performance.now(); return result; }); @@ -436,14 +468,28 @@ export class IDBBatchAtomicVFS extends VFS.Base { * @returns {number} */ xCheckReservedLock(fileId, pResOut) { - return this.handleAsync(async () => { + const result = this.handleAsync(async () => { const file = this.#mapIdToFile.get(fileId); log(`xCheckReservedLock ${file.path}`); const isReserved = await file.locks.isSomewhereReserved(); + function setOutput(pResOut) { + }; + + // @ts-ignore + if (pResOut.buffer.detached) { + pResOut = new DataView(new ArrayBuffer(4)); + this.#growthHandler = (pResOutNew) => { + pResOutNew.setInt32(0, pResOut.getInt32(0, true), true); + }; + } pResOut.setInt32(0, isReserved ? 1 : 0, true); return VFS.SQLITE_OK; }); + + this.#growthHandler?.(pResOut); + this.#growthHandler = null; + return result; } /** @@ -611,7 +657,7 @@ export class IDBBatchAtomicVFS extends VFS.Base { * @returns {number} */ xAccess(name, flags, pResOut) { - return this.handleAsync(async () => { + const result = this.handleAsync(async () => { try { const path = new URL(name, 'file://localhost/').pathname; log(`xAccess ${path} ${flags}`); @@ -620,6 +666,14 @@ export class IDBBatchAtomicVFS extends VFS.Base { const key = await this.#idb.run('readonly', ({blocks}) => { return blocks.getKey(this.#bound({path}, 0)); }); + + // @ts-ignore + if (pResOut.buffer.detached) { + pResOut = new DataView(new ArrayBuffer(4)); + this.#growthHandler = (pResOutNew) => { + pResOutNew.setInt32(0, pResOut.getInt32(0, true), true); + } + } pResOut.setInt32(0, key ? 1 : 0, true); return VFS.SQLITE_OK; } catch (e) { @@ -627,6 +681,10 @@ export class IDBBatchAtomicVFS extends VFS.Base { return VFS.SQLITE_IOERR; } }); + + this.#growthHandler?.(pResOut); + this.#growthHandler = null; + return result; } /**