From: stephan Date: Thu, 5 Mar 2026 06:30:57 +0000 (+0000) Subject: The opfs-wl lock hang has been traced to starvation between the WebLock and the tight... X-Git-Tag: major-release~100^2~18 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=cef3ff74cd89cecc1ee88068f1f8cc8fbce9f51c;p=thirdparty%2Fsqlite.git The opfs-wl lock hang has been traced to starvation between the WebLock and the tight wait-on-VFS-calls Atomics.wait() loop. This can reportedly be resolved with another level of indirection in which the WebLock takes over the wait-on-VFS-calls part until it's unlocked, returning to the global loop when it's done. That exceeds this morning's ambitions but is next to try out. FossilOrigin-Name: 113bd910e12fea17f9f4a0a3baf706f15627c08cfa6b47a960a83eee761ef4dd --- diff --git a/ext/wasm/api/opfs-common-shared.c-pp.js b/ext/wasm/api/opfs-common-shared.c-pp.js index 72c366adab..43a1b05cdc 100644 --- a/ext/wasm/api/opfs-common-shared.c-pp.js +++ b/ext/wasm/api/opfs-common-shared.c-pp.js @@ -585,7 +585,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ of this value is also used for determining how long to wait on lock contention to free up. */ - state.asyncIdleWaitTime = isWebLocker ? 300 : 150; + state.asyncIdleWaitTime = isWebLocker ? 100 : 150; /** Whether the async counterpart should log exceptions to @@ -1262,6 +1262,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){ } break; } + case 'debug': + warn("debug message from worker:",data); + break; default: { const errMsg = ( "Unexpected message from the OPFS async worker: " + diff --git a/ext/wasm/api/sqlite3-opfs-async-proxy.c-pp.js b/ext/wasm/api/sqlite3-opfs-async-proxy.c-pp.js index f682c4ce42..8338b96c05 100644 --- a/ext/wasm/api/sqlite3-opfs-async-proxy.c-pp.js +++ b/ext/wasm/api/sqlite3-opfs-async-proxy.c-pp.js @@ -615,15 +615,18 @@ const installAsyncProxy = function(){ 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 view = state.sabOPView; const slock = state.lock; const lockType = Atomics.load(view, slock.type); - warn("handleLockRequest()", args, lockType); - navigator.locks.request('sqlite3-vfs-opfs:'+args[0], { - mode: (lockType===state.sq3Codes.SQLITE_LOCK_EXCLUSIVE) - ? 'exclusive' : 'shared' + 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); /** @@ -637,24 +640,57 @@ const installAsyncProxy = function(){ Receptionist receives 'unlockControl'. */ while( 1!==Atomics.load(view, slock.atomicsHandshake) ){ - Atomics.wait(view. slock.atomicsHandshake, 2); + 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(){ - const opHandlers = Object.create(null); - for(let k of Object.keys(state.opIds)){ - const vi = vfsAsyncImpls[k]; - if(!vi) continue; - const o = Object.create(null); - opHandlers[state.opIds[k]] = o; - o.key = k; - o.f = vi; + if( !f.inited ){ + f.inited = true; + f.opHandlers = Object.create(null); + for(let k of Object.keys(state.opIds)){ + const vi = vfsAsyncImpls[k]; + if(!vi) continue; + const o = Object.create(null); + f.opHandlers[state.opIds[k]] = o; + o.key = k; + o.f = vi; + } } const opIds = state.opIds; while(!flagAsyncShutdown){ @@ -684,9 +720,10 @@ const installAsyncProxy = function(){ warn("opId =",opId, opIds); if( opId===opIds.lockControl ){ handleLockRequest(); - continue; + setTimeout(f, 50); + return; } - const hnd = opHandlers[opId] ?? toss("No waitLoop handler for whichOp #",opId); + 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 an exception string written by the upcoming diff --git a/ext/wasm/api/sqlite3-vfs-opfs-wl.c-pp.js b/ext/wasm/api/sqlite3-vfs-opfs-wl.c-pp.js index 6b18a38691..b2d25b85e4 100644 --- a/ext/wasm/api/sqlite3-vfs-opfs-wl.c-pp.js +++ b/ext/wasm/api/sqlite3-vfs-opfs-wl.c-pp.js @@ -92,11 +92,17 @@ const installOpfsWlVfs = async function callee(options){ 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){ diff --git a/manifest b/manifest index 68ce7cc015..a92a7a8886 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\ssome\sdebugging\soutput\sto\sopfs\sand\strack\sdown\sthe\sbreakage\sto\sthe\sinitial\sWebLock\srequest,\swhich\sis\snever\sreaching\sits\scallback.\sStill\sbroken,\sbut\sthis\sis\sprogress. -D 2026-03-04T21:16:36.068 +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 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -585,7 +585,7 @@ F ext/wasm/api/README.md a905d5c6bfc3e2df875bd391d6d6b7b48d41b43bdee02ad115b4724 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 1218fb9e72f3a208e62a4caafcf1fb0a41b66ca46df27601e5bfb06220822f24 +F ext/wasm/api/opfs-common-shared.c-pp.js 404aa8fd676791f0ce71e5c48f5f19e13e7b8e967a9af6a76927d524bbb46158 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 @@ -594,11 +594,11 @@ F ext/wasm/api/sqlite3-api-oo1.c-pp.js 45454631265d9ce82685f1a64e1650ee19c8e121c 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 792a18708a6207d9bf274b04dcddd06fd805e93d1b7f716230df3c745f2800ee +F ext/wasm/api/sqlite3-opfs-async-proxy.c-pp.js 82339eb043af53638b127a4649685da62b6fa33426d2013520eb6aeb114ede46 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 1e6e087a89e9cd202d457c84dae29fa4808612ff9f37210c383eb6bbab3dfd34 +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-vtab-helper.c-pp.js 366596d8ff73d4cefb938bbe95bc839d503c3fab6c8335ce4bf52f0d8a7dee81 F ext/wasm/api/sqlite3-wasm.c 45bb20e19b245136711f9b78584371233975811b6560c29ed9b650e225417e29 @@ -2191,8 +2191,8 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c -P 3b27310aa29ea84f459974981a600301abac5c705029a289d3872ecacf231da3 -R 5c13d459ac5b107e6cc5943524da9a40 +P 62fc8b35aeec75f5648b3daa24162c638999d447aa874bdfcbac1431c5c97b6f +R 4e4429a4d6f21ed4af3f33058413d5c8 U stephan -Z 9fafbc035e4ee3a2d442f9b59c8a5807 +Z 5eeca3e1560bb544ee40d011df9232c0 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 6c88a8044b..c36b9ce9ac 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -62fc8b35aeec75f5648b3daa24162c638999d447aa874bdfcbac1431c5c97b6f +113bd910e12fea17f9f4a0a3baf706f15627c08cfa6b47a960a83eee761ef4dd