aboutsummaryrefslogtreecommitdiff
path: root/ext/wasm/sqlite3-opfs-async-proxy.js
diff options
context:
space:
mode:
Diffstat (limited to 'ext/wasm/sqlite3-opfs-async-proxy.js')
-rw-r--r--ext/wasm/sqlite3-opfs-async-proxy.js97
1 files changed, 56 insertions, 41 deletions
diff --git a/ext/wasm/sqlite3-opfs-async-proxy.js b/ext/wasm/sqlite3-opfs-async-proxy.js
index d261dd354..5b29aa3ea 100644
--- a/ext/wasm/sqlite3-opfs-async-proxy.js
+++ b/ext/wasm/sqlite3-opfs-async-proxy.js
@@ -10,10 +10,10 @@
***********************************************************************
- An INCOMPLETE and UNDER CONSTRUCTION experiment for OPFS: a Worker
- which manages asynchronous OPFS handles on behalf of a synchronous
- API which controls it via a combination of Worker messages,
- SharedArrayBuffer, and Atomics.
+ An Worker which manages asynchronous OPFS handles on behalf of a
+ synchronous API which controls it via a combination of Worker
+ messages, SharedArrayBuffer, and Atomics. It is the asynchronous
+ counterpart of the API defined in sqlite3-api-opfs.js.
Highly indebted to:
@@ -102,9 +102,9 @@ const __openFiles = Object.create(null);
/**
Expects an OPFS file path. It gets resolved, such that ".."
- components are properly expanded, and returned. If the 2nd
- are is true, it's returned as an array of path elements,
- else it's returned as an absolute path string.
+ components are properly expanded, and returned. If the 2nd arg is
+ true, the result is returned as an array of path elements, else an
+ absolute path string is returned.
*/
const getResolvedPath = function(filename,splitIt){
const p = new URL(
@@ -115,9 +115,9 @@ const getResolvedPath = function(filename,splitIt){
/**
Takes the absolute path to a filesystem element. Returns an array
- of [handleOfContainingDir, filename]. If the 2nd argument is
- truthy then each directory element leading to the file is created
- along the way. Throws if any creation or resolution fails.
+ of [handleOfContainingDir, filename]. If the 2nd argument is truthy
+ then each directory element leading to the file is created along
+ the way. Throws if any creation or resolution fails.
*/
const getDirForFilename = async function f(absFilename, createDirs = false){
const path = getResolvedPath(absFilename, true);
@@ -171,6 +171,16 @@ const getSyncHandle = async (fh)=>{
return fh.syncHandle;
};
+/**
+ If the given file-holding object has a sync handle attached to it,
+ that handle is remove and asynchronously closed. Though it may
+ sound sensible to continue work as soon as the close() returns
+ (noting that it's asynchronous), doing so can cause operations
+ performed soon afterwards, e.g. a call to getSyncHandle() to fail
+ because they may happen out of order from the close(). OPFS does
+ not guaranty that the actual order of operations is retained in
+ such cases. i.e. always "await" on the result of this function.
+*/
const closeSyncHandle = async (fh)=>{
if(fh.syncHandle){
log("Closing sync handle for",fh.filenameAbs);
@@ -197,43 +207,50 @@ const affirmNotRO = function(opName,fh){
if(fh.readOnly) toss(opName+"(): File is read-only: "+fh.filenameAbs);
};
-
-const opTimer = Object.create(null);
-opTimer.op = undefined;
-opTimer.start = undefined;
+/**
+ We track 2 different timers: the "metrics" timer records how much
+ time we spend performing work. The "wait" timer records how much
+ time we spend waiting on the underlying OPFS timer. See the calls
+ to mTimeStart(), mTimeEnd(), wTimeStart(), and wTimeEnd()
+ throughout this file to see how they're used.
+*/
+const __mTimer = Object.create(null);
+__mTimer.op = undefined;
+__mTimer.start = undefined;
const mTimeStart = (op)=>{
- opTimer.start = performance.now();
- opTimer.op = op;
+ __mTimer.start = performance.now();
+ __mTimer.op = op;
//metrics[op] || toss("Maintenance required: missing metrics for",op);
++metrics[op].count;
};
const mTimeEnd = ()=>(
- metrics[opTimer.op].time += performance.now() - opTimer.start
+ metrics[__mTimer.op].time += performance.now() - __mTimer.start
);
-const waitTimer = Object.create(null);
-waitTimer.op = undefined;
-waitTimer.start = undefined;
+const __wTimer = Object.create(null);
+__wTimer.op = undefined;
+__wTimer.start = undefined;
const wTimeStart = (op)=>{
- waitTimer.start = performance.now();
- waitTimer.op = op;
+ __wTimer.start = performance.now();
+ __wTimer.op = op;
//metrics[op] || toss("Maintenance required: missing metrics for",op);
};
const wTimeEnd = ()=>(
- metrics[waitTimer.op].wait += performance.now() - waitTimer.start
+ metrics[__wTimer.op].wait += performance.now() - __wTimer.start
);
/**
- Set to true by the 'opfs-async-shutdown' command to quite the wait loop.
- This is only intended for debugging purposes: we cannot inspect this
- file's state while the tight waitLoop() is running.
+ Gets set to true by the 'opfs-async-shutdown' command to quit the
+ wait loop. This is only intended for debugging purposes: we cannot
+ inspect this file's state while the tight waitLoop() is running and
+ need a way to stop that loop for introspection purposes.
*/
let flagAsyncShutdown = false;
/**
Asynchronous wrappers for sqlite3_vfs and sqlite3_io_methods
- methods. Maintenance reminder: members are in alphabetical order
- to simplify finding them.
+ methods, as well as helpers like mkdir(). Maintenance reminder:
+ members are in alphabetical order to simplify finding them.
*/
const vfsAsyncImpls = {
'opfs-async-metrics': async ()=>{
@@ -369,11 +386,13 @@ const vfsAsyncImpls = {
const fh = __openFiles[fid];
let rc = 0;
if( !fh.syncHandle ){
+ wTimeStart('xLock');
try { await getSyncHandle(fh) }
catch(e){
state.s11n.storeException(1,e);
rc = state.sq3Codes.SQLITE_IOERR;
}
+ wTimeEnd();
}
storeAndNotify('xLock',rc);
mTimeEnd();
@@ -423,11 +442,11 @@ const vfsAsyncImpls = {
},
xRead: async function(fid,n,offset){
mTimeStart('xRead');
- let rc = 0;
+ let rc = 0, nRead;
const fh = __openFiles[fid];
try{
wTimeStart('xRead');
- const nRead = (await getSyncHandle(fh)).read(
+ nRead = (await getSyncHandle(fh)).read(
fh.sabView.subarray(0, n),
{at: Number(offset)}
);
@@ -437,6 +456,7 @@ const vfsAsyncImpls = {
rc = state.sq3Codes.SQLITE_IOERR_SHORT_READ;
}
}catch(e){
+ if(undefined===nRead) wTimeEnd();
error("xRead() failed",e,fh);
state.s11n.storeException(1,e);
rc = state.sq3Codes.SQLITE_IOERR_READ;
@@ -454,9 +474,8 @@ const vfsAsyncImpls = {
await fh.syncHandle.flush();
}catch(e){
state.s11n.storeException(2,e);
- }finally{
- wTimeEnd();
}
+ wTimeEnd();
}
storeAndNotify('xSync',rc);
mTimeEnd();
@@ -484,13 +503,13 @@ const vfsAsyncImpls = {
const fh = __openFiles[fid];
if( state.sq3Codes.SQLITE_LOCK_NONE===lockType
&& fh.syncHandle ){
+ wTimeStart('xUnlock');
try { await closeSyncHandle(fh) }
catch(e){
state.s11n.storeException(1,e);
rc = state.sq3Codes.SQLITE_IOERR;
- /* Maybe we want to not report this? "Destructors do not
- throw." */
}
+ wTimeEnd();
}
storeAndNotify('xUnlock',rc);
mTimeEnd();
@@ -511,9 +530,8 @@ const vfsAsyncImpls = {
error("xWrite():",e,fh);
state.s11n.storeException(1,e);
rc = state.sq3Codes.SQLITE_IOERR_WRITE;
- }finally{
- wTimeEnd();
}
+ wTimeEnd();
storeAndNotify('xWrite',rc);
mTimeEnd();
}
@@ -632,7 +650,7 @@ const waitLoop = async function f(){
const o = Object.create(null);
opHandlers[state.opIds[k]] = o;
o.key = k;
- o.f = vi || toss("No vfsAsyncImpls[",k,"]");
+ o.f = vi;
}
/**
waitTime is how long (ms) to wait for each Atomics.wait().
@@ -640,8 +658,6 @@ const waitLoop = async function f(){
to do other things.
*/
const waitTime = 1000;
- let lastOpTime = performance.now();
- let now;
while(!flagAsyncShutdown){
try {
if('timed-out'===Atomics.wait(
@@ -649,7 +665,6 @@ const waitLoop = async function f(){
)){
continue;
}
- lastOpTime = performance.now();
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);
@@ -663,7 +678,7 @@ const waitLoop = async function f(){
}catch(e){
error('in waitLoop():',e);
}
- };
+ }
};
navigator.storage.getDirectory().then(function(d){