1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-27 20:41:58 +03:00

Correct opfs-sahpool VFS after the pebkac involving the previous speedtest1 runs. Make that VFS explicitly opt-in to avoid certain unfortunate locking situations.

FossilOrigin-Name: 41bf1fe31f2f3d0daa2bac25dc57262a4b90f22fed6fa97e4e92467c32ae02dc
This commit is contained in:
stephan
2023-07-15 19:08:58 +00:00
parent eadabc6513
commit 38d1db9b79
5 changed files with 698 additions and 620 deletions

View File

@ -53,16 +53,43 @@
*/
'use strict';
globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
const installOpfsVfs = async function(sqlite3){
const toss = sqlite3.util.toss;
let vfsRegisterResult = undefined;
/**
installOpfsSAHPoolVfs() asynchronously initializes the
OPFS SyncAccessHandle Pool VFS. It returns a Promise
which either resolves to the sqlite3 object or rejects
with an Error value.
Initialization of this VFS is not automatic because its
registration requires that it lock all resources it
will potentially use, even if client code does not want
to use them. That, in turn, can lead to locking errors
when, for example, one page in a given origin has loaded
this VFS but does not use it, then another page in that
origin tries to use the VFS. If the VFS were automatically
registered, the second page would fail to load the VFS
due to OPFS locking errors.
On calls after the first this function immediately returns a
resolved or rejected Promise. If called while the first call is
still pending resolution, a rejected promise with a descriptive
error is returned.
*/
sqlite3.installOpfsSAHPoolVfs = async function(){
if(sqlite3===vfsRegisterResult) return Promise.resolve(sqlite3);
else if(undefined!==vfsRegisterResult){
return Promise.reject(vfsRegisterResult);
}
if(!globalThis.FileSystemHandle ||
!globalThis.FileSystemDirectoryHandle ||
!globalThis.FileSystemFileHandle ||
!globalThis.FileSystemFileHandle.prototype.createSyncAccessHandle ||
!navigator?.storage?.getDirectory){
return Promise.reject(new Error("Missing required OPFS APIs."));
return Promise.reject(vfsRegisterResult = new Error("Missing required OPFS APIs."));
}
return new Promise(async function(promiseResolve, promiseReject_){
const verbosity = 2;
vfsRegisterResult = new Error("VFS initialization still underway.");
const verbosity = 2 /*3+ == everything*/;
const loggers = [
sqlite3.config.error,
sqlite3.config.warn,
@ -74,17 +101,19 @@ const installOpfsVfs = async function(sqlite3){
const log = (...args)=>logImpl(2, ...args);
const warn = (...args)=>logImpl(1, ...args);
const error = (...args)=>logImpl(0, ...args);
const toss = sqlite3.util.toss;
const capi = sqlite3.capi;
const wasm = sqlite3.wasm;
const opfsIoMethods = new capi.sqlite3_io_methods();
const opfsVfs = new capi.sqlite3_vfs()
.addOnDispose(()=>opfsIoMethods.dispose());
const promiseReject = (err)=>{
opfsVfs.dispose();
return promiseReject_(err);
error("rejecting promise:",err);
//opfsVfs.dispose();
vfsRegisterResult = err;
return Promise.reject(err);
};
const promiseResolve =
()=>Promise.resolve(vfsRegisterResult = sqlite3);
// Config opts for the VFS...
const SECTOR_SIZE = 4096;
const HEADER_MAX_PATH_SIZE = 512;
@ -113,6 +142,7 @@ const installOpfsVfs = async function(sqlite3){
? new capi.sqlite3_vfs(pDVfs)
: null /* dVfs will be null when sqlite3 is built with
SQLITE_OS_OTHER. */;
opfsIoMethods.$iVersion = 1;
opfsVfs.$iVersion = 2/*yes, two*/;
opfsVfs.$szOsFile = capi.sqlite3_file.structInfo.sizeof;
opfsVfs.$mxPathname = HEADER_MAX_PATH_SIZE;
@ -358,13 +388,14 @@ const installOpfsVfs = async function(sqlite3){
(or no) value to clear it.
*/
storeErr: function(e){
if(e) error(e);
return this.$error = e;
},
/**
Pops this object's Error object and returns
it (a falsy value if no error is set).
*/
popErr: function(e){
popErr: function(){
const rc = this.$error;
this.$error = undefined;
return rc;
@ -377,7 +408,9 @@ const installOpfsVfs = async function(sqlite3){
*/
const ioSyncWrappers = {
xCheckReservedLock: function(pFile,pOut){
log('xCheckReservedLock');
SAHPool.storeErr();
wasm.poke32(pOut, 1);
return 0;
},
xClose: function(pFile){
@ -385,15 +418,15 @@ const installOpfsVfs = async function(sqlite3){
const file = SAHPool.mapIdToFile.get(pFile);
if(file) {
try{
log(`xClose ${file.path}`);
log(`xClose ${file}`);
if(file.sq3File) file.sq3File.dispose();
file.sah.flush();
SAHPool.mapIdToFile.delete(pFIle);
SAHPool.mapIdToFile.delete(pFile);
if(file.flags & capi.SQLITE_OPEN_DELETEONCLOSE){
SAHPool.deletePath(file.path);
}
}catch(e){
SAHPool.storeErr(e);
error("xClose() failed:",e.message);
return capi.SQLITE_IOERR;
}
}
@ -406,24 +439,29 @@ const installOpfsVfs = async function(sqlite3){
return capi.SQLITE_NOTFOUND;
},
xFileSize: function(pFile,pSz64){
const file = SAHPool.mapIdToFile(pFile);
log(`xFileSize`);
const file = SAHPool.mapIdToFile.get(pFile);
const size = file.sah.getSize() - HEADER_OFFSET_DATA;
//log(`xFileSize ${file.path} ${size}`);
wasm.poke64(pSz64, BigInt(size));
return 0;
},
xLock: function(pFile,lockType){
log(`xLock ${lockType}`);
SAHPool.storeErr();
let rc = capi.SQLITE_IOERR;
return rc;
const file = SAHPool.mapIdToFile.get(pFile);
file.lockType = lockType;
return 0;
},
xRead: function(pFile,pDest,n,offset64){
log(`xRead ${n}@${offset64}`);
SAHPool.storeErr();
const file = SAHPool.mapIdToFile.get(pFile);
log(`xRead ${file.path} ${n} ${offset64}`);
try {
const nRead = file.sah.read(
pDest, {at: HEADER_OFFSET_DATA + offset64}
wasm.heap8u().subarray(pDest, pDest+n),
{at: HEADER_OFFSET_DATA + Number(offset64)}
);
if(nRead < n){
wasm.heap8u().fill(0, pDest + nRead, pDest + n);
@ -436,10 +474,11 @@ const installOpfsVfs = async function(sqlite3){
return capi.SQLITE_IOERR;
}
},
xSectorSize: function(pFile){
/*xSectorSize: function(pFile){
return SECTOR_SIZE;
},
},*/
xSync: function(pFile,flags){
log(`xSync ${flags}`);
SAHPool.storeErr();
const file = SAHPool.mapIdToFile.get(pFile);
//log(`xSync ${file.path} ${flags}`);
@ -453,6 +492,7 @@ const installOpfsVfs = async function(sqlite3){
}
},
xTruncate: function(pFile,sz64){
log(`xTruncate ${sz64}`);
SAHPool.storeErr();
const file = SAHPool.mapIdToFile.get(pFile);
//log(`xTruncate ${file.path} ${iSize}`);
@ -465,16 +505,20 @@ const installOpfsVfs = async function(sqlite3){
return capi.SQLITE_IOERR;
}
},
/**xUnlock: function(pFile,lockType){
return capi.SQLITE_IOERR;
},*/
xUnlock: function(pFile,lockType){
log('xUnlock');
const file = SAHPool.mapIdToFile.get(pFile);
file.lockType = lockType;
return 0;
},
xWrite: function(pFile,pSrc,n,offset64){
SAHPool.storeErr();
const file = SAHPool.mapIdToFile(pFile);
//log(`xWrite ${file.path} ${n} ${offset64}`);
const file = SAHPool.mapIdToFile.get(pFile);
log(`xWrite ${file.path} ${n} ${offset64}`);
try{
const nBytes = file.sah.write(
pSrc, { at: HEADER_OFFSET_DATA + Number(offset64) }
wasm.heap8u().subarray(pSrc, pSrc+n),
{ at: HEADER_OFFSET_DATA + Number(offset64) }
);
return nBytes === n ? 0 : capi.SQLITE_IOERR;
}catch(e){
@ -491,6 +535,7 @@ const installOpfsVfs = async function(sqlite3){
*/
const vfsSyncWrappers = {
xAccess: function(pVfs,zName,flags,pOut){
log(`xAccess ${wasm.cstrToJs(zName)}`);
SAHPool.storeErr();
try{
const name = this.getPath(zName);
@ -511,6 +556,7 @@ const installOpfsVfs = async function(sqlite3){
return 0;
},
xDelete: function(pVfs, zName, doSyncDir){
log(`xDelete ${wasm.cstrToJs(zName)}`);
SAHPool.storeErr();
try{
SAHPool.deletePath(SAHPool.getPath(zName));
@ -522,10 +568,12 @@ const installOpfsVfs = async function(sqlite3){
}
},
xFullPathname: function(pVfs,zName,nOut,pOut){
log(`xFullPathname ${wasm.cstrToJs(zName)}`);
const i = wasm.cstrncpy(pOut, zName, nOut);
return i<nOut ? 0 : capi.SQLITE_CANTOPEN;
},
xGetLastError: function(pVfs,nOut,pOut){
log(`xGetLastError ${nOut}`);
const e = SAHPool.popErr();
if(e){
const scope = wasm.scopedAllocPush();
@ -543,31 +591,35 @@ const installOpfsVfs = async function(sqlite3){
},
//xSleep is optionally defined below
xOpen: function f(pVfs, zName, pFile, flags, pOutFlags){
log(`xOpen ${wasm.cstrToJs(zName)} ${flags}`);
try{
// First try to open a path that already exists in the file system.
const path = (zName && wasm.peek8(zName))
? SAHPool.getPath(name)
? SAHPool.getPath(zName)
: getRandomName();
let ah = SAHPool.mapPathToSAH.get(path);
if(!ah && (flags & capi.SQLITE_OPEN_CREATE)) {
let sah = SAHPool.mapPathToSAH.get(path);
if(!sah && (flags & capi.SQLITE_OPEN_CREATE)) {
// File not found so try to create it.
if(SAHPool.getFileCount() < SAHPool.getCapacity()) {
// Choose an unassociated OPFS file from the pool.
ah = SAHPool.availableSAH.keys()[0];
SAHPool.setAssociatedPath(ah, path, flags);
[sah] = SAHPool.availableSAH.keys();
SAHPool.setAssociatedPath(sah, path, flags);
}else{
// File pool is full.
toss('SAH pool is full. Cannot create file',path);
}
}
if(!ah){
if(!sah){
toss('file not found:',path);
}
// Subsequent methods are only passed the file pointer, so
// map the relevant info we need to that pointer.
const file = { path, flags, ah };
const file = {path, flags, sah};
SAHPool.mapIdToFile.set(pFile, file);
wasm.poke32(pOutFlags, flags);
file.sq3File = new capi.sqlite3_file(pFile);
file.sq3File.$pMethods = opfsIoMethods.pointer;
file.lockType = capi.SQLITE_LOCK_NONE;
return 0;
}catch(e){
SAHPool.storeErr(e);
@ -592,17 +644,16 @@ const installOpfsVfs = async function(sqlite3){
};
}
if(!opfsVfs.$xSleep){
vfsSyncWrappers.xSleep = function(pVfs,ms){
return 0;
};
vfsSyncWrappers.xSleep = (pVfs,ms)=>0;
}
/**
Ensure that the client has a "fully-sync" SAH impl,
else reject the promise. Returns true on success,
else false.
else a value intended to be returned via the containing
function's Promise result.
*/
if(!(async ()=>{
const apiVersionCheck = await (async ()=>{
try {
const dh = await navigator.storage.getDirectory();
const fn = '.opfs-sahpool-sync-check-'+getRandomName();
@ -617,14 +668,13 @@ const installOpfsVfs = async function(sqlite3){
}
return true;
}catch(e){
promiseReject(e);
return false;
return e;
}
})()){
return;
})();
if(true!==apiVersionCheck){
return promiseReject(apiVersionCheck);
}
SAHPool.isReady = SAHPool.reset().then(async ()=>{
return SAHPool.isReady = SAHPool.reset().then(async ()=>{
if(SAHPool.$error){
throw SAHPool.$error;
}
@ -634,9 +684,11 @@ const installOpfsVfs = async function(sqlite3){
//log("vfs list:",capi.sqlite3_js_vfs_list());
sqlite3.vfs.installVfs({
io: {struct: opfsIoMethods, methods: ioSyncWrappers},
vfs: {struct: opfsVfs, methods: vfsSyncWrappers}
vfs: {struct: opfsVfs, methods: vfsSyncWrappers},
applyArgcCheck: true
});
//log("vfs list:",capi.sqlite3_js_vfs_list());
log("opfsVfs",opfsVfs,"opfsIoMethods",opfsIoMethods);
log("vfs list:",capi.sqlite3_js_vfs_list());
if(sqlite3.oo1){
const OpfsSAHPoolDb = function(...args){
const opt = sqlite3.oo1.DB.dbCtorHelper.normalizeArgs(...args);
@ -683,15 +735,7 @@ const installOpfsVfs = async function(sqlite3){
);
}/*extend sqlite3.oo1*/
log("VFS initialized.");
promiseResolve(sqlite3);
return promiseResolve();
}).catch(promiseReject);
})/*return Promise*/;
}/*installOpfsVfs()*/;
globalThis.sqlite3ApiBootstrap.initializersAsync.push(async (sqlite3)=>{
return installOpfsVfs(sqlite3).catch((e)=>{
sqlite3.config.warn("Ignoring inability to install opfs-sahpool sqlite3_vfs:",
e.message, e);
});
}/*sqlite3ApiBootstrap.initializersAsync*/);
}/*installOpfsSAHPoolVfs()*/;
}/*sqlite3ApiBootstrap.initializers*/);

View File

@ -236,6 +236,7 @@ const installOpfsVfs = function callee(options){
? new sqlite3_vfs(pDVfs)
: null /* dVfs will be null when sqlite3 is built with
SQLITE_OS_OTHER. */;
opfsIoMethods.$iVersion = 1;
opfsVfs.$iVersion = 2/*yes, two*/;
opfsVfs.$szOsFile = capi.sqlite3_file.structInfo.sizeof;
opfsVfs.$mxPathname = 1024/*sure, why not?*/;

View File

@ -71,20 +71,48 @@
self.onmessage = function(msg){
msg = msg.data;
switch(msg.type){
case 'run': runSpeedtest(msg.data || []); break;
case 'run':
try {
runSpeedtest(msg.data || []);
}catch(e){
mPost('error',e);
}
break;
default:
logErr("Unhandled worker message type:",msg.type);
break;
}
};
const sahpSanityChecks = function(sqlite3){
log("Attempting OpfsSAHPoolDb sanity checks...");
const db = new sqlite3.oo1.OpfsSAHPoolDb('opfs-sahpoool.db');
const fn = db.filename;
db.exec([
'create table t(a);',
'insert into t(a) values(1),(2),(3);'
]);
db.close();
sqlite3.wasm.sqlite3_wasm_vfs_unlink(sqlite3_vfs_find("opfs-sahpool"), fn);
log("SAH sanity checks done.");
};
const EmscriptenModule = {
print: log,
printErr: logErr,
setStatus: (text)=>mPost('load-status',text)
};
self.sqlite3InitModule(EmscriptenModule).then((sqlite3)=>{
const S = sqlite3;
log("Initializing speedtest1 module...");
self.sqlite3InitModule(EmscriptenModule).then(async (sqlite3)=>{
const S = globalThis.S = sqlite3;
log("Loaded speedtest1 module. Setting up...");
if(S.installOpfsSAHPoolVfs){
await S.installOpfsSAHPoolVfs().then(()=>{
log("Loaded SAHPool.");
}).catch(e=>{
logErr("Error setting up SAHPool:",e.message);
});
}
App.vfsUnlink = function(pDb, fname){
const pVfs = S.wasm.sqlite3_wasm_db_vfs(pDb, 0);
if(pVfs) S.wasm.sqlite3_wasm_vfs_unlink(pVfs, fname||0);
@ -95,5 +123,10 @@
//else log("Using transient storage.");
mPost('ready',true);
log("Registered VFSes:", ...S.capi.sqlite3_js_vfs_list());
if(0 && S.installOpfsSAHPoolVfs){
sahpSanityChecks(S);
}
}).catch(e=>{
logErr(e);
});
})();

View File

@ -1,5 +1,5 @@
C speedtest1\sJS:\sonly\sadd\s--memdb\sflag\sby\sdefault\sif\sno\s--vfs\sis\sprovided.
D 2023-07-15T16:30:46.383
C Correct\sopfs-sahpool\sVFS\safter\sthe\spebkac\sinvolving\sthe\sprevious\sspeedtest1\sruns.\sMake\sthat\sVFS\sexplicitly\sopt-in\sto\savoid\scertain\sunfortunate\slocking\ssituations.
D 2023-07-15T19:08:58.138
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -502,8 +502,8 @@ F ext/wasm/api/sqlite3-api-worker1.js 9f32af64df1a031071912eea7a201557fe39b17386
F ext/wasm/api/sqlite3-license-version-header.js 0c807a421f0187e778dc1078f10d2994b915123c1223fe752b60afdcd1263f89
F ext/wasm/api/sqlite3-opfs-async-proxy.js 961bbc3ccc1fa4e91d6519a96e8811ad7ae60173bd969fee7775dacb6eee1da2
F ext/wasm/api/sqlite3-v-helper.js e5c202a9ecde9ef818536d3f5faf26c03a1a9f5192b1ddea8bdabf30d75ef487
F ext/wasm/api/sqlite3-vfs-opfs-sahpool.js ad6ec4e87f47152a871a23bf90b64709094bf04e8ee76671fc6cedd1ce45086d
F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 891f3a18d9ac9b0422b32fd975319dfcd0af5a8ca392f0cce850524e51b49c87
F ext/wasm/api/sqlite3-vfs-opfs-sahpool.js 83388ead4bfc489bee008298ab51948ccb75227795ce8d1634f2eec8e02548f1
F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js a5c3195203e6085d7aa89fae4b84cf3f3eec4ff4f928c6d0e5d3ef8b14cbc1c0
F ext/wasm/api/sqlite3-wasm.c 12a096d8e58a0af0589142bae5a3c27a0c7e19846755a1a37d2c206352fbedda
F ext/wasm/api/sqlite3-worker1-promiser.c-pp.js bc06df0d599e625bde6a10a394e326dc68da9ff07fa5404354580f81566e591f
F ext/wasm/api/sqlite3-worker1.c-pp.js da509469755035e919c015deea41b4514b5e84c12a1332e6cc8d42cb2cc1fb75
@ -540,7 +540,7 @@ F ext/wasm/scratchpad-wasmfs.mjs 66034b9256b218de59248aad796760a1584c1dd84223150
F ext/wasm/speedtest1-wasmfs.html 0e9d335a9b5b5fafe6e1bc8dc0f0ca7e22e6eb916682a2d7c36218bb7d67379d
F ext/wasm/speedtest1-wasmfs.mjs ac5cadbf4ffe69e9eaac8b45e8523f030521e02bb67d654c6eb5236d9c456cbe
F ext/wasm/speedtest1-worker.html bbcf1e7fd79541040c1a7ca2ebf1cb7793ddaf9900d6bde1784148f11b807c34
F ext/wasm/speedtest1-worker.js 13b57c4a41729678a1194014afec2bd5b94435dcfc8d1039dfa9a533ac819ee1
F ext/wasm/speedtest1-worker.js 4de92e4e6718b8bd1cdecb75af62739d1115fa66656a700b0b51822c848948f5
F ext/wasm/speedtest1.html ff048b4a623aa192e83e143e48f1ce2a899846dd42c023fdedc8772b6e3f07da
F ext/wasm/split-speedtest1-script.sh a3e271938d4d14ee49105eb05567c6a69ba4c1f1293583ad5af0cd3a3779e205 x
F ext/wasm/sql/000-mandelbrot.sql 775337a4b80938ac8146aedf88808282f04d02d983d82675bd63d9c2d97a15f0
@ -2044,8 +2044,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P fff68e9f25a57045e9d636b02ffa073cf1b984b2587d4fce10f6e35c9988469c
R 30945b34df9134e0f98668ba08cfc13f
P 676ffe6280c1ce787b04d0cdb4a0664229c6125c601af4b18d1bfa125aac3675
R bca4913f68935c8abed9e461aac753fd
U stephan
Z 32f9fd4e9e8f1a70cce170b39e2a4458
Z 2546a1c8fd9c0ca0c4fd392086704b47
# Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
676ffe6280c1ce787b04d0cdb4a0664229c6125c601af4b18d1bfa125aac3675
41bf1fe31f2f3d0daa2bac25dc57262a4b90f22fed6fa97e4e92467c32ae02dc