1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-30 19:03:16 +03:00

Rework automatically acquired OPFS locks to be released during idle time. This eliminates the performance hit reported in [46304ba057707c].

FossilOrigin-Name: a7fe91afca473fe55c983bc81d214df4ef3699863c7423fa4b6b9cde23d6a3b4
This commit is contained in:
stephan
2022-11-10 13:14:30 +00:00
parent aafa022f5b
commit da2641597a
5 changed files with 71 additions and 38 deletions

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,6 +229,8 @@ 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();
}
};
@ -360,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);
@ -421,7 +443,6 @@ const vfsAsyncImpls = {
mTimeStart('xFileSize');
const fh = __openFiles[fid];
let rc;
const hadLock = !!fh.syncHandle;
wTimeStart('xFileSize');
try{
affirmLocked('xFileSize',fh);
@ -432,7 +453,6 @@ const vfsAsyncImpls = {
state.s11n.storeException(2,e);
rc = state.sq3Codes.SQLITE_IOERR;
}
if(!hadLock) closeSyncHandleNoThrow(fh);
wTimeEnd();
storeAndNotify('xFileSize', rc);
mTimeEnd();
@ -442,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();
}
@ -481,6 +506,7 @@ const vfsAsyncImpls = {
*/
wTimeEnd();
__openFiles[fid] = Object.assign(Object.create(null),{
fid: fid,
filenameAbs: filename,
filenamePart: filenamePart,
dirHandle: hDir,
@ -503,7 +529,6 @@ const vfsAsyncImpls = {
mTimeStart('xRead');
let rc = 0, nRead;
const fh = __openFiles[fid];
const hadLock = !!fh.syncHandle;
try{
affirmLocked('xRead',fh);
wTimeStart('xRead');
@ -522,7 +547,6 @@ const vfsAsyncImpls = {
state.s11n.storeException(1,e);
rc = state.sq3Codes.SQLITE_IOERR_READ;
}
if(!hadLock) closeSyncHandleNoThrow(fh);
storeAndNotify('xRead',rc);
mTimeEnd();
},
@ -547,7 +571,6 @@ const vfsAsyncImpls = {
mTimeStart('xTruncate');
let rc = 0;
const fh = __openFiles[fid];
const hadLock = !!fh.syncHandle;
wTimeStart('xTruncate');
try{
affirmLocked('xTruncate',fh);
@ -558,7 +581,6 @@ const vfsAsyncImpls = {
state.s11n.storeException(2,e);
rc = state.sq3Codes.SQLITE_IOERR_TRUNCATE;
}
if(!hadLock) closeSyncHandleNoThrow(fh);
wTimeEnd();
storeAndNotify('xTruncate',rc);
mTimeEnd();
@ -585,7 +607,6 @@ const vfsAsyncImpls = {
mTimeStart('xWrite');
let rc;
const fh = __openFiles[fid];
const hadLock = !!fh.syncHandle;
wTimeStart('xWrite');
try{
affirmLocked('xWrite',fh);
@ -600,7 +621,6 @@ const vfsAsyncImpls = {
state.s11n.storeException(1,e);
rc = state.sq3Codes.SQLITE_IOERR_WRITE;
}
if(!hadLock) closeSyncHandleNoThrow(fh);
wTimeEnd();
storeAndNotify('xWrite',rc);
mTimeEnd();
@ -636,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];
@ -661,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;
@ -727,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);