***********************************************************************
- 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:
/**
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(
/**
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);
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);
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 ()=>{
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();
},
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)}
);
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;
await fh.syncHandle.flush();
}catch(e){
state.s11n.storeException(2,e);
- }finally{
- wTimeEnd();
}
+ wTimeEnd();
}
storeAndNotify('xSync',rc);
mTimeEnd();
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();
error("xWrite():",e,fh);
state.s11n.storeException(1,e);
rc = state.sq3Codes.SQLITE_IOERR_WRITE;
- }finally{
- wTimeEnd();
}
+ wTimeEnd();
storeAndNotify('xWrite',rc);
mTimeEnd();
}
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().
to do other things.
*/
const waitTime = 1000;
- let lastOpTime = performance.now();
- let now;
while(!flagAsyncShutdown){
try {
if('timed-out'===Atomics.wait(
)){
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);
}catch(e){
error('in waitLoop():',e);
}
- };
+ }
};
navigator.storage.getDirectory().then(function(d){
-C Fix\sa\sproblem\sin\sthe\sLIKE\sand\sGLOB\soperators\sthat\smay\soccur\swhen\sthe\scharacter\simmediately\sfollowing\sa\s"%"\sor\s"*"\swildcard\sis\sU+80.\sReported\sby\s[forum:61bf7ccbdf].
-D 2022-10-14T15:10:36.387
+C Generic\sminor\scleanups\sand\sdocs\sin\sthe\sOPFS\sasync\sproxy.
+D 2022-10-14T15:52:29.231
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
F ext/wasm/split-speedtest1-script.sh a3e271938d4d14ee49105eb05567c6a69ba4c1f1293583ad5af0cd3a3779e205 x
F ext/wasm/sql/000-mandelbrot.sql 775337a4b80938ac8146aedf88808282f04d02d983d82675bd63d9c2d97a15f0
F ext/wasm/sql/001-sudoku.sql 35b7cb7239ba5d5f193bc05ec379bcf66891bce6f2a5b3879f2f78d0917299b5
-F ext/wasm/sqlite3-opfs-async-proxy.js e552d9290509644929cd7bfde3381a0eac85d17b4d82dbb11e86fae5336215b6
+F ext/wasm/sqlite3-opfs-async-proxy.js 206ce6bbc3c30ad51a37d9c25e3a2712e70b586e0f9a2cf8cb0b9619017c2671
F ext/wasm/sqlite3-worker1-promiser.js 307d7837420ca6a9d3780dfc81194f1c0715637e6d9540e935514086b96913d8
F ext/wasm/sqlite3-worker1.js 466e9bd39409ab03f3e00999887aaffc11e95b416e2689596e3d7f1516673fdf
F ext/wasm/test-opfs-vfs.html eb69dda21eb414b8f5e3f7c1cc0f774103cc9c0f87b2d28a33419e778abfbab5
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 32fd4ac382f774189ac34f6fff80e55e6e56dd2aa67b0db88d5a88324f17f6ff
-R ad7e356fc9072ccedd7e8855b564b667
-U dan
-Z 16dbeccb3b1fdf0272ff90de38793516
+P 2da677c45b643482eec39e4db7079c772760bc966dc71bf6c01658cc468f5823
+R bd5efaaf882b3ae0c03e0e5a6693b37e
+U stephan
+Z c8a3d40e488aded9faa8a7e4d2f0c359
# Remove this line to create a well-formed Fossil manifest.