Skip to content

Commit 0477ca5

Browse files
authored
Merge pull request rhashimoto#146 from rhashimoto/bug-143
Fix rhashimoto#143. Handle detached buffers in IDBBatchAtomicVFS.
2 parents c1f7e35 + dbd6a6d commit 0477ca5

File tree

2 files changed

+64
-6
lines changed

2 files changed

+64
-6
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.9",
3+
"version": "0.9.10",
44
"type": "module",
55
"main": "src/sqlite-api.js",
66
"types": "src/types/index.d.ts",

src/examples/IDBBatchAtomicVFS.js

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ export class IDBBatchAtomicVFS extends VFS.Base {
5858
#taskTimestamp = performance.now();
5959
#pendingAsync = new Set();
6060

61+
// Asyncify can grow WebAssembly memory during an asynchronous call.
62+
// If this happens, then any array buffer arguments will be detached.
63+
// The workaround is when finding a detached buffer, set this handler
64+
// function to process the new buffer outside handlerAsync().
65+
#growthHandler = null;
66+
6167
constructor(idbDatabaseName = 'wa-sqlite', options = DEFAULT_OPTIONS) {
6268
super();
6369
this.name = idbDatabaseName;
@@ -84,7 +90,7 @@ export class IDBBatchAtomicVFS extends VFS.Base {
8490
* @returns {number}
8591
*/
8692
xOpen(name, fileId, flags, pOutFlags) {
87-
return this.handleAsync(async () => {
93+
const result = this.handleAsync(async () => {
8894
if (name === null) name = `null_${fileId}`;
8995
log(`xOpen ${name} 0x${fileId.toString(16)} 0x${flags.toString(16)}`);
9096

@@ -118,13 +124,25 @@ export class IDBBatchAtomicVFS extends VFS.Base {
118124
}
119125
}
120126
});
127+
128+
// @ts-ignore
129+
if (pOutFlags.buffer.detached) {
130+
pOutFlags = new DataView(new ArrayBuffer(4));
131+
this.#growthHandler = (pOutFlagsNew) => {
132+
pOutFlagsNew.setInt32(0, pOutFlags.getInt32(0, true), true);
133+
};
134+
}
121135
pOutFlags.setInt32(0, flags & VFS.SQLITE_OPEN_READONLY, true);
122136
return VFS.SQLITE_OK;
123137
} catch (e) {
124138
console.error(e);
125139
return VFS.SQLITE_CANTOPEN;
126140
}
127141
});
142+
143+
this.#growthHandler?.(pOutFlags);
144+
this.#growthHandler = null;
145+
return result;
128146
}
129147

130148
/**
@@ -160,7 +178,8 @@ export class IDBBatchAtomicVFS extends VFS.Base {
160178
* @returns {number}
161179
*/
162180
xRead(fileId, pData, iOffset) {
163-
return this.handleAsync(async () => {
181+
const byteLength = pData.byteLength;
182+
const result = this.handleAsync(async () => {
164183
const file = this.#mapIdToFile.get(fileId);
165184
log(`xRead ${file.path} ${pData.byteLength} ${iOffset}`);
166185

@@ -170,6 +189,15 @@ export class IDBBatchAtomicVFS extends VFS.Base {
170189
// one case - rollback after journal spill - where reads cross
171190
// write boundaries so we have to allow for that.
172191
const result = await this.#idb.run('readonly', async ({blocks}) => {
192+
// @ts-ignore
193+
if (pData.buffer.detached) {
194+
// WebAssembly memory has grown, invalidating our buffer. Use
195+
// a temporary buffer and copy after this asynchronous call
196+
// completes.
197+
pData = new Uint8Array(byteLength);
198+
this.#growthHandler = (pDataNew) => pDataNew.set(pData);
199+
}
200+
173201
let pDataOffset = 0;
174202
while (pDataOffset < pData.byteLength) {
175203
// Fetch the IndexedDB block for this file location.
@@ -200,6 +228,10 @@ export class IDBBatchAtomicVFS extends VFS.Base {
200228
return VFS.SQLITE_IOERR;
201229
}
202230
});
231+
232+
this.#growthHandler?.(pData);
233+
this.#growthHandler = null;
234+
return result;
203235
}
204236

205237
/**
@@ -221,7 +253,7 @@ export class IDBBatchAtomicVFS extends VFS.Base {
221253
}
222254
await new Promise(resolve => setTimeout(resolve));
223255

224-
const result = this.#xWriteHelper(fileId, pData, iOffset);
256+
const result = this.#xWriteHelper(fileId, pData.slice(), iOffset);
225257
this.#taskTimestamp = performance.now();
226258
return result;
227259
});
@@ -436,14 +468,28 @@ export class IDBBatchAtomicVFS extends VFS.Base {
436468
* @returns {number}
437469
*/
438470
xCheckReservedLock(fileId, pResOut) {
439-
return this.handleAsync(async () => {
471+
const result = this.handleAsync(async () => {
440472
const file = this.#mapIdToFile.get(fileId);
441473
log(`xCheckReservedLock ${file.path}`);
442474

443475
const isReserved = await file.locks.isSomewhereReserved();
476+
function setOutput(pResOut) {
477+
};
478+
479+
// @ts-ignore
480+
if (pResOut.buffer.detached) {
481+
pResOut = new DataView(new ArrayBuffer(4));
482+
this.#growthHandler = (pResOutNew) => {
483+
pResOutNew.setInt32(0, pResOut.getInt32(0, true), true);
484+
};
485+
}
444486
pResOut.setInt32(0, isReserved ? 1 : 0, true);
445487
return VFS.SQLITE_OK;
446488
});
489+
490+
this.#growthHandler?.(pResOut);
491+
this.#growthHandler = null;
492+
return result;
447493
}
448494

449495
/**
@@ -611,7 +657,7 @@ export class IDBBatchAtomicVFS extends VFS.Base {
611657
* @returns {number}
612658
*/
613659
xAccess(name, flags, pResOut) {
614-
return this.handleAsync(async () => {
660+
const result = this.handleAsync(async () => {
615661
try {
616662
const path = new URL(name, 'file://localhost/').pathname;
617663
log(`xAccess ${path} ${flags}`);
@@ -620,13 +666,25 @@ export class IDBBatchAtomicVFS extends VFS.Base {
620666
const key = await this.#idb.run('readonly', ({blocks}) => {
621667
return blocks.getKey(this.#bound({path}, 0));
622668
});
669+
670+
// @ts-ignore
671+
if (pResOut.buffer.detached) {
672+
pResOut = new DataView(new ArrayBuffer(4));
673+
this.#growthHandler = (pResOutNew) => {
674+
pResOutNew.setInt32(0, pResOut.getInt32(0, true), true);
675+
}
676+
}
623677
pResOut.setInt32(0, key ? 1 : 0, true);
624678
return VFS.SQLITE_OK;
625679
} catch (e) {
626680
console.error(e);
627681
return VFS.SQLITE_IOERR;
628682
}
629683
});
684+
685+
this.#growthHandler?.(pResOut);
686+
this.#growthHandler = null;
687+
return result;
630688
}
631689

632690
/**

0 commit comments

Comments
 (0)