Skip to content

Commit

Permalink
Merge pull request #146 from rhashimoto/bug-143
Browse files Browse the repository at this point in the history
Fix #143. Handle detached buffers in IDBBatchAtomicVFS.
  • Loading branch information
rhashimoto authored Jan 18, 2024
2 parents c1f7e35 + dbd6a6d commit 0477ca5
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 6 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "wa-sqlite",
"version": "0.9.9",
"version": "0.9.10",
"type": "module",
"main": "src/sqlite-api.js",
"types": "src/types/index.d.ts",
Expand Down
68 changes: 63 additions & 5 deletions src/examples/IDBBatchAtomicVFS.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)}`);

Expand Down Expand Up @@ -118,13 +124,25 @@ 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) {
console.error(e);
return VFS.SQLITE_CANTOPEN;
}
});

this.#growthHandler?.(pOutFlags);
this.#growthHandler = null;
return result;
}

/**
Expand Down Expand Up @@ -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}`);

Expand All @@ -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.
Expand Down Expand Up @@ -200,6 +228,10 @@ export class IDBBatchAtomicVFS extends VFS.Base {
return VFS.SQLITE_IOERR;
}
});

this.#growthHandler?.(pData);
this.#growthHandler = null;
return result;
}

/**
Expand All @@ -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;
});
Expand Down Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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}`);
Expand All @@ -620,13 +666,25 @@ 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) {
console.error(e);
return VFS.SQLITE_IOERR;
}
});

this.#growthHandler?.(pResOut);
this.#growthHandler = null;
return result;
}

/**
Expand Down

0 comments on commit 0477ca5

Please sign in to comment.