Automatically relinquish implicitly-acquired OPFS file locks during VFS idle time in an attempt to help alleviate cross-tab locking contention like that described in [forum:58a377083cd24a|forum post 58a377083cd24a].

FossilOrigin-Name: 8daf24ff73dd9928057412e0e4c2e2b2e47e1dca66acfb6b07c846e8d97582cc
This commit is contained in:
stephan 2022-11-10 13:22:35 +00:00
commit 2c4d62f013
6 changed files with 93 additions and 30 deletions

View File

@ -467,9 +467,11 @@ const installOpfsVfs = function callee(options){
/**
Returns an array of the deserialized state stored by the most
recent serialize() operation (from from this thread or the
counterpart thread), or null if the serialization buffer is empty.
counterpart thread), or null if the serialization buffer is
empty. If passed a truthy argument, the serialization buffer
is cleared after deserialization.
*/
state.s11n.deserialize = function(){
state.s11n.deserialize = function(clear=false){
++metrics.s11n.deserialize.count;
const t = performance.now();
const argc = viewU8[0];
@ -494,6 +496,7 @@ const installOpfsVfs = function callee(options){
rc.push(v);
}
}
if(clear) viewU8[0] = 0;
//log("deserialize:",argc, rc);
metrics.s11n.deserialize.time += performance.now() - t;
return rc;

View File

@ -44,6 +44,7 @@ if(self.window === self){
this API.
*/
const state = Object.create(null);
/**
verbose:
@ -96,13 +97,27 @@ metrics.dump = ()=>{
};
/**
Map of sqlite3_file pointers (integers) to metadata related to a
given OPFS file handles. The pointers are, in this side of the
interface, opaque file handle IDs provided by the synchronous
part of this constellation. Each value is an object with a structure
demonstrated in the xOpen() impl.
__openFiles is a map of sqlite3_file pointers (integers) to
metadata related to a given OPFS file handles. The pointers are, in
this side of the interface, opaque file handle IDs provided by the
synchronous part of this constellation. Each value is an object
with a structure demonstrated in the xOpen() impl.
*/
const __openFiles = Object.create(null);
/**
__autoLocks is a Set of sqlite3_file pointers (integers) which were
"auto-locked". i.e. those for which we obtained a sync access
handle without an explicit xLock() call. Such locks will be
released during db connection idle time, whereas a sync access
handle obtained via xLock(), or subsequently xLock()'d after
auto-acquisition, will not be released until xUnlock() is called.
Maintenance reminder: if we relinquish auto-locks at the end of the
operation which acquires them, we pay a massive performance
penalty: speedtest1 benchmarks take up to 4x as long. By delaying
the lock release until idle time, the hit is negligible.
*/
const __autoLocks = new Set();
/**
Expects an OPFS file path. It gets resolved, such that ".."
@ -191,6 +206,10 @@ const getSyncHandle = async (fh)=>{
}
}
log("Got sync handle for",fh.filenameAbs,'in',performance.now() - t,'ms');
if(!fh.xLock){
__autoLocks.add(fh.fid);
log("Auto-locked",fh.fid,fh.filenameAbs);
}
}
return fh.syncHandle;
};
@ -210,10 +229,30 @@ const closeSyncHandle = async (fh)=>{
log("Closing sync handle for",fh.filenameAbs);
const h = fh.syncHandle;
delete fh.syncHandle;
delete fh.xLock;
__autoLocks.delete(fh.fid);
return h.close();
}
};
/**
A proxy for closeSyncHandle() which is guaranteed to not throw.
This function is part of a lock/unlock step in functions which
require a sync access handle but may be called without xLock()
having been called first. Such calls need to release that
handle to avoid locking the file for all of time. This is an
_attempt_ at reducing cross-tab contention but it may prove
to be more of a problem than a solution and may need to be
removed.
*/
const closeSyncHandleNoThrow = async (fh)=>{
try{await closeSyncHandle(fh)}
catch(e){
warn("closeSyncHandleNoThrow() ignoring:",e,fh);
}
};
/**
Stores the given value at state.sabOPView[state.opIds.rc] and then
Atomics.notify()'s it.
@ -342,9 +381,10 @@ const vfsAsyncImpls = {
xClose: async function(fid/*sqlite3_file pointer*/){
const opName = 'xClose';
mTimeStart(opName);
__autoLocks.delete(fid);
const fh = __openFiles[fid];
let rc = 0;
wTimeStart('xClose');
wTimeStart(opName);
if(fh){
delete __openFiles[fid];
await closeSyncHandle(fh);
@ -422,12 +462,17 @@ const vfsAsyncImpls = {
mTimeStart('xLock');
const fh = __openFiles[fid];
let rc = 0;
const oldLockType = fh.xLock;
fh.xLock = lockType;
if( !fh.syncHandle ){
wTimeStart('xLock');
try { await getSyncHandle(fh) }
catch(e){
try {
await getSyncHandle(fh);
__autoLocks.delete(fid);
}catch(e){
state.s11n.storeException(1,e);
rc = state.sq3Codes.SQLITE_IOERR_LOCK;
fh.xLock = oldLockType;
}
wTimeEnd();
}
@ -461,6 +506,7 @@ const vfsAsyncImpls = {
*/
wTimeEnd();
__openFiles[fid] = Object.assign(Object.create(null),{
fid: fid,
filenameAbs: filename,
filenamePart: filenamePart,
dirHandle: hDir,
@ -610,7 +656,7 @@ const initS11n = ()=>{
default: toss("Invalid type ID:",tid);
}
};
state.s11n.deserialize = function(){
state.s11n.deserialize = function(clear=false){
++metrics.s11n.deserialize.count;
const t = performance.now();
const argc = viewU8[0];
@ -635,6 +681,7 @@ const initS11n = ()=>{
rc.push(v);
}
}
if(clear) viewU8[0] = 0;
//log("deserialize:",argc, rc);
metrics.s11n.deserialize.time += performance.now() - t;
return rc;
@ -701,21 +748,30 @@ const waitLoop = async function f(){
We need to wake up periodically to give the thread a chance
to do other things.
*/
const waitTime = 1000;
const waitTime = 500;
while(!flagAsyncShutdown){
try {
if('timed-out'===Atomics.wait(
state.sabOPView, state.opIds.whichOp, 0, waitTime
)){
if(__autoLocks.size){
/* Release all auto-locks. */
for(const fid of __autoLocks){
const fh = __openFiles[fid];
await closeSyncHandleNoThrow(fh);
log("Auto-unlocked",fid,fh.filenameAbs);
}
}
continue;
}
const opId = Atomics.load(state.sabOPView, state.opIds.whichOp);
Atomics.store(state.sabOPView, state.opIds.whichOp, 0);
const hnd = opHandlers[opId] ?? toss("No waitLoop handler for whichOp #",opId);
const args = state.s11n.deserialize() || [];
state.s11n.serialize(/* clear s11n to keep the caller from
confusing this with an exception string
written by the upcoming operation */);
const args = state.s11n.deserialize(
true /* clear s11n to keep the caller from confusing this with
an exception string written by the upcoming
operation */
) || [];
//warn("waitLoop() whichOp =",opId, hnd, args);
if(hnd.f) await hnd.f(...args);
else error("Missing callback for opId",opId);

View File

@ -189,7 +189,7 @@
<div id='list-compile-options' class='pseudolist wide2'></div>
</div><!-- .initially-hidden -->
<script src="jswasm/sqlite3.js">/* This tag MUST be in side the
<script src="jswasm/sqlite3.js">/* This tag MUST be inside the
fossil-doc block so that this part can work without modification in
the wasm docs repo. */</script>
<script>(async function(){

View File

@ -169,7 +169,11 @@
return str+a.join('&nbsp;');
};
const W = new Worker("speedtest1-worker.js?sqlite3.dir=jswasm");
const urlParams = new URL(self.location.href).searchParams;
const W = new Worker(
"speedtest1-worker.js?sqlite3.dir=jswasm"+
(urlParams.has('opfs-verbose') ? '&opfs-verbose' : '')
);
const mPost = function(msgType,payload){
W.postMessage({type: msgType, data: payload});
};
@ -179,7 +183,6 @@
const eLinkMainThread = E('#link-main-thread');
const eLinkWasmfs = E('#link-wasmfs');
const eLinkKvvfs = E('#link-kvvfs');
const urlParams = new URL(self.location.href).searchParams;
const getSelectedFlags = ()=>{
const f = Array.prototype.map.call(eFlags.selectedOptions, (v)=>v.value);
[

View File

@ -1,5 +1,5 @@
C Better\shandle\san\serror\sin\sthe\sfts5\sintegrity-check\scode.\sdbsqlfuzz\se87c62f9b67ea21aebdc36ab71cab7cc3eda8dc3.
D 2022-11-09T11:17:57.759
C Automatically\srelinquish\simplicitly-acquired\sOPFS\sfile\slocks\sduring\sVFS\sidle\stime\sin\san\sattempt\sto\shelp\salleviate\scross-tab\slocking\scontention\slike\sthat\sdescribed\sin\s[forum:58a377083cd24a|forum\spost\s58a377083cd24a].
D 2022-11-10T13:22:35.705
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -502,11 +502,11 @@ F ext/wasm/api/pre-js.js 287e462f969342b032c03900e668099fa1471d852df7a472de5bc34
F ext/wasm/api/sqlite3-api-cleanup.js ecdc69dbfccfe26146f04799fcfd4a6f5790d46e7e3b9b6e9b0491f92ed8ae34
F ext/wasm/api/sqlite3-api-glue.js 056f44b82c126358a0175e08a892d56fadfce177b0d7a0012502a6acf67ea6d5
F ext/wasm/api/sqlite3-api-oo1.js e9a83489bbb4838ce0aee46eaaa9350e0e25a5b926b565e4f5ae8e840e4fbaed
F ext/wasm/api/sqlite3-api-opfs.js cdcbb57acc66f4569ac9e18f9d13d5a3657d8aae195725c6324943da56c1005d
F ext/wasm/api/sqlite3-api-opfs.js 5c6d0903e100b2918920d8c596ab037cd9a67df19e388ebde4fe085218780af0
F ext/wasm/api/sqlite3-api-prologue.js fd526fa017fa2578673ca18158354515c719e719a5d93f2f6d0e43f39170430e
F ext/wasm/api/sqlite3-api-worker1.js e94ba98e44afccfa482874cd9acb325883ade50ed1f9f9526beb9de1711f182f
F ext/wasm/api/sqlite3-license-version-header.js a661182fc93fc2cf212dfd0b987f8e138a3ac98f850b1112e29b5fbdaecc87c3
F ext/wasm/api/sqlite3-opfs-async-proxy.js ab7d2888ad9b3dd24bb782bd882fcada2a20cb88eb78c8f36e7bfe708857dbd1
F ext/wasm/api/sqlite3-opfs-async-proxy.js 24d1c1982a012d998907105a4ff1ff6881bf462395e90c06326817701e69f093
F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9
F ext/wasm/api/sqlite3-wasm.c 778409e00fb25a4d6989be17fc856c84460198fd3b05ba2ef8289e60c50157ca
F ext/wasm/api/sqlite3-worker1-promiser.js 0c7a9826dbf82a5ed4e4f7bf7816e825a52aff253afbf3350431f5773faf0e4b
@ -536,11 +536,11 @@ F ext/wasm/index-dist.html cb0da16cba0f21cda2c25724c5869102d48eb0af04446acd3cd0c
F ext/wasm/index.html ce6a68a75532b47e3c0adb83381a06d15de8c0ac0331fb7bf31d33f8e7c77dc4
F ext/wasm/jaccwabyt/jaccwabyt.js 95f573de1826474c9605dda620ee622fcb1673ae74f191eb324c0853aa4dcb66
F ext/wasm/jaccwabyt/jaccwabyt.md 9aa6951b529a8b29f578ec8f0355713c39584c92cf1708f63ba0cf917cb5b68e
F ext/wasm/module-symbols.html eca884ef4380612145ee550213be57478ee2b9cd9a9c2b27530cc23359c99682
F ext/wasm/module-symbols.html b8eebafef8e536624bbe5f7a3da40c07a9062b843dfd3161a0bb72cbb6763dc5
F ext/wasm/scratchpad-wasmfs-main.html 20cf6f1a8f368e70d01e8c17200e3eaa90f1c8e1029186d836d14b83845fbe06
F ext/wasm/scratchpad-wasmfs-main.js 4c140457f4d6da9d646a49addd91edb6e9ad1643c6c48e3258b5bce24725dc18
F ext/wasm/speedtest1-wasmfs.html bc28eb29b69a73864b8d7aae428448f8b7e1de81d8bfb9bba99541322054dbd0
F ext/wasm/speedtest1-worker.html 94246488e10af9daa1ebd0979b1b8d7a22a579e5a983fa2a5ad94591ecf55f2c
F ext/wasm/speedtest1-worker.html d75d9de9347c6f2f3278b73ccb6bceb559080644aef385fe496fa389f58bcd66
F ext/wasm/speedtest1-worker.js 13b57c4a41729678a1194014afec2bd5b94435dcfc8d1039dfa9a533ac819ee1
F ext/wasm/speedtest1.html e4c4e5c1c8ec1ad13c995e346e4216a1df152fd2c5cd17e0793b865b2f3c5000
F ext/wasm/split-speedtest1-script.sh a3e271938d4d14ee49105eb05567c6a69ba4c1f1293583ad5af0cd3a3779e205 x
@ -2055,8 +2055,9 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P 2e70d1e5c9b2c9e068be3ccf8a6062edf7bdde2e46d60ba8ce54eda851af6008
R 5b3228ef5a47231a8a5df1bba248e63f
U dan
Z 85fe5476882fbe08da2a9ca03d046c66
P ae43e97087a3207a5ca3ffae75fbe7a33c01f4a38ce0d1d7eed8591ae3083617 a7fe91afca473fe55c983bc81d214df4ef3699863c7423fa4b6b9cde23d6a3b4
R 4034304670d7219ca07133486e9bbed5
T +closed a7fe91afca473fe55c983bc81d214df4ef3699863c7423fa4b6b9cde23d6a3b4 Closed\sby\sintegrate-merge.
U stephan
Z 9ded40c19ad046a311beff933b79ea4d
# Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
ae43e97087a3207a5ca3ffae75fbe7a33c01f4a38ce0d1d7eed8591ae3083617
8daf24ff73dd9928057412e0e4c2e2b2e47e1dca66acfb6b07c846e8d97582cc