- Set up any references they may need to state returned
by the previous step.
- - Call opfvs.doTheThing()
+ - Call opfvs.bindVfs()
*/
opfsUtil.initOptions = function(options, callee){
const urlParams = new URL(globalThis.location.href).searchParams;
capi.sqlite3_vfs instance.
After setting up any local state needed, the caller must
- call theVfs.doTheThing(X,Y), where X is an object containing
+ call theVfs.bindVfs(X,Y), where X is an object containing
the sqlite3_io_methods to override and Y is a callback which
gets triggered if init succeeds, before the final Promise
- decides whether or not to reject. The result of doTheThing()
+ decides whether or not to reject. The result of bindVfs()
must be returned from their main installation function.
This object must, when it's passed to the async part, contain
sqlite3.config.log
];
const logImpl = (level,...args)=>{
- if(options.verbose>level) loggers[level]("OPFS syncer:",...args);
+ if(state.verbose>level) loggers[level]("OPFS syncer:",...args);
};
const log = (...args)=>logImpl(2, ...args),
warn = (...args)=>logImpl(1, ...args),
state.opIds.xUnlock = i++;
state.opIds.xWrite = i++;
state.opIds.mkdir = i++;
- state.opIds.lockControl = i++ /* opfs-wl signals the intent to lock here */;
/** Internal signals which are used only during development and
testing via the dev console. */
state.opIds['opfs-async-metrics'] = i++;
semantics. Though we could hypothetically use the xSleep slot
for that, doing so might lead to undesired side effects. */
state.opIds.retry = i++;
-
- state.lock = util.nu({
- /* Slots for submitting the lock type and receiving its
- acknowledgement. Only used by "opfs-wl". */
- type: i++ /* SQLITE_LOCK_xyz value */,
- atomicsHandshake: i++ /* 0=pending, 1=release, 2=granted */
- });
state.sabOP = new SharedArrayBuffer(
i * 4/* ==sizeof int32, noting that Atomics.wait() and friends
can only function on Int32Array views of an SAB. */);
where opfsVfs is the sqlite3_vfs object which was set up by
opfsUtil.createVfsState().
*/
- opfsVfs.doTheThing = function(ioMethods, callback){
+ opfsVfs.bindVfs = function(ioMethods, callback){
Object.assign(opfsVfs.ioSyncWrappers, ioMethods);
const thePromise = new Promise(function(promiseResolve_, promiseReject_){
let promiseWasRejected = undefined;
options.proxyUri += '?vfs='+vfsName;
const W = opfsVfs.worker =
//#if target:es6-bundler-friendly
- new Worker(new URL("sqlite3-opfs-async-proxy.js?vfs=opfs", import.meta.url));
+ (()=>{
+ /* _Sigh_... */
+ switch(vfsName){
+ case 'opfs':
+ return new Worker(new URL("sqlite3-opfs-async-proxy.js?vfs=opfs", import.meta.url));
+ case 'opfs-wl':
+ return new Worker(new URL("sqlite3-opfs-async-proxy.js?vfs=opfs-wl", import.meta.url));
+ }
+ })();
//#elif target:es6-module
new Worker(new URL(options.proxyUri, import.meta.url));
//#else
new Worker(options.proxyUri);
//#endif
- setTimeout(()=>{
+ let zombieTimer = setTimeout(()=>{
/* At attempt to work around a browser-specific quirk in which
the Worker load is failing in such a way that we neither
resolve nor reject it. This workaround gives that resolve/reject
}/*sanityCheck()*/;
W.onmessage = function({data}){
- //log("Worker.onmessage:",data);
+ //sqlite3.config.warn(vfsName,"Worker.onmessage:",data);
switch(data.type){
case 'opfs-unavailable':
/* Async proxy has determined that OPFS is unavailable. There's
if(true===promiseWasRejected){
break /* promise was already rejected via timer */;
}
+ clearTimeout(zombieTimer);
+ zombieTimer = null;
try {
sqlite3.vfs.installVfs({
io: {struct: opfsVfs.ioMethods, methods: opfsVfs.ioSyncWrappers},
}/*W.onmessage()*/;
})/*thePromise*/;
return thePromise;
- }/*doTheThing()*/;
+ }/*bindVfs()*/;
return state;
}/*createVfsState()*/;
}
log("Got",opName+"() sync handle for",fh.filenameAbs,
'in',performance.now() - t,'ms');
- if(!isWebLocker && !fh.xLock){
+ if(!fh.xLock){
__implicitLocks.add(fh.fid);
log("Acquired implicit lock for",opName+"()",fh.fid,fh.filenameAbs);
}
}
}/*vfsAsyncImpls*/;
- /**
- Starts a new WebLock request.
- */
- const handleLockRequest = async function(){
- const view = state.sabOPView;
-//#if not nope
- const args = state.s11n.deserialize(true)
- || toss("Expecting a filename argument from the proxy xLock()");
- const slock = state.lock;
- const lockType = Atomics.load(view, slock.type);
- warn("handleLockRequest()", args, lockType, JSON.stringify(slock));
- //hangs warn(JSON.stringify((await navigator.locks.query()).held));
- //warn("Navigator locks:", !!navigator.locks);
- await navigator.locks.request('sqlite3-vfs-opfs:'+args[0], {
- mode: 'exclusive' /*(lockType===state.sq3Codes.SQLITE_LOCK_EXCLUSIVE)
- ? 'exclusive' : 'shared'*/
- }, async (wl)=>{
- warn("handleLockRequest() starting lock", args, lockType);
- /**
- A. Tell the C-side we have the browser lock. We use the same
- handshake slot, but a specific 'Granted' value.
- */
- Atomics.store(view, slock.atomicsHandshake, 2);
- Atomics.notify(view, slock.atomicsHandshake);
- /**
- B. Sit here and keep the lock room occupied until the
- Receptionist receives 'unlockControl'.
- */
- while( 1!==Atomics.load(view, slock.atomicsHandshake) ){
- Atomics.wait(view, slock.atomicsHandshake, 2);
- }
- /* C. Reset the handshake slot. */
- Atomics.store(view, slock.atomicsHandshake, 0);
- Atomics.notify(view, lock.atomicsHandshake);
- });
- warn("handleLockRequest() ending", args, lockType);
- //setTimeout(waitLoop, 0);
-//#else
- warn("handleLockRequest()", ...arguments);
- const [filename] = state.s11n.deserialize(true);
- const lockType = Atomics.load(view, state.lock.type);
- warn("handleLockRequest()", filename, lockType);
- // Use 'exclusive' to ensure we aren't getting a weak shared lock
- navigator.locks.request(filename, { mode: 'exclusive' }, (lock) => {
- warn("handleLockRequest() inside the lock");
- // VIOLENT DEBUGGING: Use globalThis.postMessage to bypass Atomics
- globalThis.postMessage({type: 'debug', msg: 'CALLBACK ENTERED'});
-
- // 1. Signal C-side: "I have it"
- Atomics.store(view, state.lock.atomicsHandshake, 2);
- Atomics.notify(view, state.lock.atomicsHandshake);
-
- // 2. The Guard: This loop must not be async.
- // It must stay synchronous to keep the callback alive.
- while(Atomics.load(view, state.lock.atomicsHandshake) !== 1){
- Atomics.wait(view, state.lock.atomicsHandshake, 2, 100);
- }
-
- // 3. Departure
- Atomics.store(view, state.lock.atomicsHandshake, 0);
- globalThis.postMessage({type: 'debug', msg: 'CALLBACK EXITING'});
- return new Promise(()=>{/* never resolve to keep lock held until Departure */});
- }).catch(e => {
- globalThis.postMessage({type: 'debug', msg: 'LOCK ERROR: ' + e.message});
- });
-//#endif
- };
-
const waitLoop = async function f(){
if( !f.inited ){
f.inited = true;
continue;
}
const opId = Atomics.exchange(state.sabOPView, opIds.whichOp, 0);
- warn("opId =",opId, opIds);
- if( opId===opIds.lockControl ){
- handleLockRequest();
- setTimeout(f, 50);
- return;
- }
const hnd = f.opHandlers[opId] ?? toss("No waitLoop handler for whichOp #",opId);
const args = state.s11n.deserialize(
true /* clear s11n to keep the caller from confusing this with
navigator.storage.getDirectory().then(function(d){
state.rootDir = d;
globalThis.onmessage = function({data}){
+ warn(globalThis.location.href,"onmessage()",data);
switch(data.type){
case 'opfs-async-init':{
/* Receive shared state from synchronous partner */
flagAsyncShutdown = false;
waitLoop();
}
- break;
+ break;
}
};
wPost('opfs-async-loaded');
const installOpfsWlVfs = async function callee(options){
options = opfsUtil.initOptions(options, callee);
if( !options ) return sqlite3;
+ options.verbose = 2;
const capi = sqlite3.capi,
debug = (...args)=>sqlite3.config.warn("opfs-wl:",...args),
state = opfsUtil.createVfsState('opfs-wl', options),
mTimeStart = opfsVfs.mTimeStart,
mTimeEnd = opfsVfs.mTimeEnd,
__openFiles = opfsVfs.__openFiles;
- debug("state",JSON.stringify(state,false,' '));
+ //debug("state",JSON.stringify(state,false,' '));
/* At this point, createVfsState() has populated state and opfsVfs
with any code common to both the "opfs" and "opfs-wl" VFSes. Now
comes the VFS-dependent work... */
- return opfsVfs.doTheThing(util.nu({
- xLock: function(pFile, lockType){
+ return opfsVfs.bindVfs(util.nu({
+ xLock: function(pFile,lockType){
mTimeStart('xLock');
++metrics.xLock.count;
const f = __openFiles[pFile];
- debug("xLock()",f,lockType);
let rc = 0;
- /* See notes in sqlite3-vfs-opfs.c-pp.js. */
+ /* All OPFS locks are exclusive locks. If xLock() has
+ previously succeeded, do nothing except record the lock
+ type. If no lock is active, have the async counterpart
+ lock the file. */
if( f.lockType ) {
f.lockType = lockType;
}else{
- try{
- const view = state.sabOPView;
- /* We need to pass pFile's name to the async proxy so that
- it can create the WebLock name. */
- state.s11n.serialize(f.filename)
- Atomics.store(view, state.lock.atomicsHandshake, 0);
- Atomics.store(view, state.lock.type, lockType);
- Atomics.store(view, state.opIds.whichOp, state.opIds.lockControl);
- Atomics.notify(state.sabOPView, state.opIds.whichOp)
- debug("xLock waiting...");
-//#if not nope
- while( 2 !== Atomics.load(view, state.lock.atomicsHandshake) ){
- Atomics.wait(view, state.lock.atomicsHandshake, 0);
- }
-//#else
- while('not-equal'!==Atomics.wait(view, state.lock.atomicsHandshake, 0)){
- /* Loop is a workaround for environment-specific quirks. See
- notes in similar loops. */
- debug("xLock still waiting...");
- }
-//#endif
- debug("xLock done waiting");
- f.lockType = lockType;
- }catch(e){
- sqlite3.config.error("xLock(",arguments,") failed", e, f);
- rc = capi.SQLITE_IOERR_LOCK;
- }
+ rc = opRun('xLock', pFile, lockType);
+ if( 0===rc ) f.lockType = lockType;
}
mTimeEnd();
return rc;
},
xUnlock: function(pFile,lockType){
- debug("xUnlock()",f,lockType);
mTimeStart('xUnlock');
++metrics.xUnlock.count;
const f = __openFiles[pFile];
let rc = 0;
- if( lockType < f.lockType ){
- try{
- const view = state.sabOPView;
- Atomics.store(view, state.lock.atomicsHandshake, 1);
- Atomics.notify(view, state.lock.atomicsHandshake);
- Atomics.wait(view, state.lock.atomicsHandshake, 1);
- }catch(e){
- sqlite3.config.error("xUnlock(",pFile,lockType,") failed",e, f);
- rc = capi.SQLITE_IOERR_LOCK;
- }
+ if( capi.SQLITE_LOCK_NONE === lockType
+ && f.lockType ){
+ rc = opRun('xUnlock', pFile, lockType);
}
if( 0===rc ) f.lockType = lockType;
mTimeEnd();
return rc;
}
}), function(sqlite3, vfs){
+ //debug("registered VFS");
if(sqlite3.oo1){
const OpfsWlDb = function(...args){
const opt = sqlite3.oo1.DB.dbCtorHelper.normalizeArgs(...args);
- opt.vfs = opfsVfs.$zName;
+ opt.vfs = vfs.$zName;
sqlite3.oo1.DB.dbCtorHelper.call(this, opt);
};
OpfsWlDb.prototype = Object.create(sqlite3.oo1.DB.prototype);
sqlite3.oo1.OpfsWlDb = OpfsWlDb;
OpfsWlDb.importDb = opfsUtil.importDb;
}/*extend sqlite3.oo1*/
- })/*doTheThing()*/;
+ })/*bindVfs()*/;
}/*installOpfsWlVfs()*/;
installOpfsWlVfs.defaultProxyUri = "sqlite3-opfs-async-proxy.js";
globalThis.sqlite3ApiBootstrap.initializersAsync.push(async (sqlite3)=>{
/* At this point, createVfsState() has populated state and
opfsVfs with any code common to both the "opfs" and "opfs-wl"
VFSes. Now comes the VFS-dependent work... */
- return opfsVfs.doTheThing(util.nu({
+ return opfsVfs.bindVfs(util.nu({
xLock: function(pFile,lockType){
mTimeStart('xLock');
++metrics.xLock.count;
}
);
}/*extend sqlite3.oo1*/
- })/*doTheThing()*/;
+ })/*bindVfs()*/;
}/*installOpfsVfs()*/;
installOpfsVfs.defaultProxyUri = "sqlite3-opfs-async-proxy.js";
globalThis.sqlite3ApiBootstrap.initializersAsync.push(async (sqlite3)=>{
const li = [];
for(const k of [
'interval', 'iterations', 'unlock-asap',
- 'verbose', 'vfs', 'workers'
+ 'opfsVerbose', 'vfs', 'workers'
]){
if( obj.hasOwnProperty(k) ) li.push(k+'='+obj[k]);
}
return li.join('&');
};
for(const opt of [
- {interval: 500, workers: 2, iterations: 30, vfs: 'opfs-wl', verbose: 2},
+ {interval: 500, workers: 1, iterations: 5, vfs: 'opfs-wl', opfsVerbose: 2},
+ {interval: 500, workers: 2, iterations: 5, vfs: 'opfs-wl', opfsVerbose: 2},
{interval: 1000, workers: 5, iterations: 30},
{interval: 500, workers: 5, iterations: 30},
{interval: 250, workers: 3, iterations: 30},
importScripts(
(new URL(globalThis.location.href).searchParams).get('sqlite3.dir') + '/sqlite3.js'
);
+//const sqlite3InitModule = (await import("../../../jswasm/sqlite3.mjs", )).default;
globalThis.sqlite3InitModule.__isUnderTest = true;
globalThis.sqlite3InitModule().then(async function(sqlite3){
const urlArgs = new URL(globalThis.location.href).searchParams;
}catch(e){
interval.error = e;
}
- stdout("doWork()",prefix,"error ",interval.error);
+ //stdout("doWork()",prefix,"error ",interval.error);
};
setTimeout(async function timer(){
await doWork();
-C The\sopfs-wl\slock\shang\shas\sbeen\straced\sto\sstarvation\sbetween\sthe\sWebLock\sand\sthe\stight\swait-on-VFS-calls\sAtomics.wait()\sloop.\sThis\scan\sreportedly\sbe\sresolved\swith\sanother\slevel\sof\sindirection\sin\swhich\sthe\sWebLock\stakes\sover\sthe\swait-on-VFS-calls\spart\suntil\sit's\sunlocked,\sreturning\sto\sthe\sglobal\sloop\swhen\sit's\sdone.\sThat\sexceeds\sthis\smorning's\sambitions\sbut\sis\snext\sto\stry\sout.
-D 2026-03-05T06:30:57.097
+C Strip\sthe\sopfs-wl\s"back\sto\sformula",\sremoving\sthe\scurrent\sfalse\sstarts\sso\sthat\sthis\scan\sbe\stried\sagain\swithout\stripping\sover\sany\scruft.\sThe\scurrent\simpl\sis\ssubject,\swith\sno\sobvious\sway\sout\sof\sit,\sto\sstarvation\sin\sthe\sasync\sproxy.
+D 2026-03-05T11:51:27.592
F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F ext/wasm/api/extern-post-js.c-pp.js d9f42ecbedc784c0d086bc37800e52946a14f7a21600b291daa3f963c314f930
F ext/wasm/api/extern-pre-js.js cc61c09c7a24a07dbecb4c352453c3985170cec12b4e7e7e7a4d11d43c5c8f41
F ext/wasm/api/opfs-common-inline.c-pp.js 5be8d6d91963849e218221b48206ae55612630bb2cd7f30b1b6fcf7a9e374b76
-F ext/wasm/api/opfs-common-shared.c-pp.js 404aa8fd676791f0ce71e5c48f5f19e13e7b8e967a9af6a76927d524bbb46158
+F ext/wasm/api/opfs-common-shared.c-pp.js d54054b9b39ceb088692153f400d2d9a147e4a45d039c8a027575bdc7a02ac68
F ext/wasm/api/post-js-footer.js a50c1a2c4d008aede7b2aa1f18891a7ee71437c2f415b8aeb3db237ddce2935b
F ext/wasm/api/post-js-header.js f35d2dcf1ab7f22a93d565f8e0b622a2934fc4e743edf3b708e4dd8140eeff55
F ext/wasm/api/pre-js.c-pp.js 9234ea680a2f6a2a177e8dcd934bdc5811a9f8409165433a252b87f4c07bba6f
F ext/wasm/api/sqlite3-api-prologue.js 98fedc159c9239b226d19567d7172300dee5ffce176e5fa2f62dd1f17d088385
F ext/wasm/api/sqlite3-api-worker1.c-pp.js 1041dd645e8e821c082b628cd8d9acf70c667430f9d45167569633ffc7567938
F ext/wasm/api/sqlite3-license-version-header.js 98d90255a12d02214db634e041c8e7f2f133d9361a8ebf000ba9c9af4c6761cc
-F ext/wasm/api/sqlite3-opfs-async-proxy.c-pp.js 82339eb043af53638b127a4649685da62b6fa33426d2013520eb6aeb114ede46
+F ext/wasm/api/sqlite3-opfs-async-proxy.c-pp.js 81d24de69b43d3c0940dbaa9bc82de193b720107e5a2ddfd31bc50640dd4903d
F ext/wasm/api/sqlite3-vfs-helper.c-pp.js 3f828cc66758acb40e9c5b4dcfd87fd478a14c8fb7f0630264e6c7fa0e57515d
F ext/wasm/api/sqlite3-vfs-kvvfs.c-pp.js a61dd2b4d919d2d5d83c5c7e49b89ecbff2525ff81419f6a6dbaecaf3819c490
F ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js 1575ea6bbcf2da1e6df6892c17521a0c1c1c199a672e9090176ea0b88de48bd9
-F ext/wasm/api/sqlite3-vfs-opfs-wl.c-pp.js 88add6362468df4ecbbd00878c7a83cd81e555c677103ead51c34bd2be1a3963
-F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 50a955ef393722d498177ad09c9e2d05bbe8dccae4c40c501482a860ca30017d
+F ext/wasm/api/sqlite3-vfs-opfs-wl.c-pp.js 8233c5f9021b0213134e2adbaf6036b8f1dffd4747083a4087c1c19ae107f962
+F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js f3b7296480984bcc6050fe9724a8b215c405977dd69daea7145ece25751e4b33
F ext/wasm/api/sqlite3-vtab-helper.c-pp.js 366596d8ff73d4cefb938bbe95bc839d503c3fab6c8335ce4bf52f0d8a7dee81
F ext/wasm/api/sqlite3-wasm.c 45bb20e19b245136711f9b78584371233975811b6560c29ed9b650e225417e29
F ext/wasm/api/sqlite3-worker1-promiser.c-pp.js aa9715f661fb700459a5a6cb1c32a4d6a770723b47aa9ac0e16c2cf87d622a66
F ext/wasm/tester1.c-pp.html 52d88fe2c6f21a046030a36410b4839b632f4424028197a45a3d5669ea724ddb
F ext/wasm/tester1.c-pp.js 6b946cd6d4da130dbae4a401057716d27117ca02cad2ea8c29ae9c46c675d618
F ext/wasm/tests/opfs/concurrency/index.html 657578a6e9ce1e9b8be951549ed93a6a471f4520a99e5b545928668f4285fb5e
-F ext/wasm/tests/opfs/concurrency/test.js b80e17488a8e4d5500deaf68f73a7298d7d351f74bb952691a4e7b6c02a5643b
-F ext/wasm/tests/opfs/concurrency/worker.js de0be3be41da20e6639cdea10467fb5d9cdf540e711b403728a08d99bbb82ee6
+F ext/wasm/tests/opfs/concurrency/test.js 74f4ef9a827d081e6bb0ffb1d124bb54015dab8f7ae47abd5b5f26d71633331a
+F ext/wasm/tests/opfs/concurrency/worker.js 3425e6dad755a1c69a6efc63a47a3ade4e7f0a9a138994ba37f996571fb46288
F ext/wasm/tests/opfs/sahpool/digest-worker.js b0ab6218588f1f0a6d15a363b493ceaf29bfb87804d9e0165915a9996377cf79
F ext/wasm/tests/opfs/sahpool/digest.html 206d08a34dc8bd570b2581d3d9ab3ecad3201b516a598dd096dcf3cf8cd81df8
F ext/wasm/tests/opfs/sahpool/index.html be736567fd92d3ecb9754c145755037cbbd2bca01385e2732294b53f4c842328
F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c
-P 62fc8b35aeec75f5648b3daa24162c638999d447aa874bdfcbac1431c5c97b6f
-R 4e4429a4d6f21ed4af3f33058413d5c8
+P 113bd910e12fea17f9f4a0a3baf706f15627c08cfa6b47a960a83eee761ef4dd
+R 4ea42d5818e8a1a0aa1e2fe935a1308b
U stephan
-Z 5eeca3e1560bb544ee40d011df9232c0
+Z 71a80b89b42b2027ae01cf15e67091dc
# Remove this line to create a well-formed Fossil manifest.
-113bd910e12fea17f9f4a0a3baf706f15627c08cfa6b47a960a83eee761ef4dd
+d022f1f5e74dedae044801330eb099022498f359f408e69b3574885641b312f5