mirror of
https://github.com/sqlite/sqlite.git
synced 2025-07-29 08:01:23 +03:00
An alternative messaging strategy for the OPFS VFS proxy which uses only SharedArrayBuffer and Atomics, instead of worker messages, for communication (only the initial one-time handshake during initialization uses worker messages). It runs speedtest1 approx. 15-20% faster but still 20-ish% slower than WASMFS.
FossilOrigin-Name: a83ee3082d89439ea3ad5737e63e25bebb0f91895aca006ce5fecf5b93a2651a
This commit is contained in:
@ -65,7 +65,7 @@ SQLITE_OPT = \
|
||||
|
||||
.PHONY: release
|
||||
release:
|
||||
$(MAKE) 'emcc_opt=-Os -g3 -flto'
|
||||
$(MAKE) "emcc_opt=-Os -g3 -flto" fiddle_opt=-Os
|
||||
# ^^^^^ target-specific vars, e.g.:
|
||||
# release: emcc_opt=...
|
||||
# apparently only work for file targets, not PHONY targets?
|
||||
@ -432,19 +432,20 @@ push-fiddle: $(fiddle_files)
|
||||
# experimentation shows -O2 to be the clear winner in terms of speed.
|
||||
# Note that build times with anything higher than -O0 are somewhat
|
||||
# painful.
|
||||
|
||||
.PHONY: o0 o1 o2 o3 os oz
|
||||
o0:
|
||||
$(MAKE) clean; $(MAKE) -e emcc_opt=-O0
|
||||
$((MAKE) clean; $(MAKE) -e "emcc_opt=-O0 -flto" fiddle_opt=-O0
|
||||
o1:
|
||||
$(MAKE) clean; $(MAKE) -e emcc_opt=-O1
|
||||
$(MAKE) clean; $(MAKE) -e "emcc_opt=-O1 -flto" fiddle_opt=-O1
|
||||
o2:
|
||||
$(MAKE) clean; $(MAKE) -e emcc_opt=-O2
|
||||
$(MAKE) clean; $(MAKE) -e "emcc_opt=-O2 -flto" fiddle_opt=-O2
|
||||
o3:
|
||||
$(MAKE) clean; $(MAKE) -e emcc_opt=-O3
|
||||
$(MAKE) clean; $(MAKE) -e "emcc_opt=-O3 -flto" fiddle_opt=-O3
|
||||
os:
|
||||
$(MAKE) clean; $(MAKE) -e emcc_opt=-Os
|
||||
$(MAKE) clean; $(MAKE) -e "emcc_opt=-Os -flto" fiddle_opt=-Os
|
||||
oz:
|
||||
$(MAKE) clean; $(MAKE) -e emcc_opt=-Oz
|
||||
$(MAKE) clean; $(MAKE) -e "emcc_opt=-Oz -flto" fiddle_opt=-Oz
|
||||
|
||||
########################################################################
|
||||
# Sub-makes...
|
||||
|
@ -128,7 +128,6 @@ sqlite3.installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri)
|
||||
// failure is, e.g., that the remote script is 404.
|
||||
promiseReject(new Error("Loading OPFS async Worker failed for unknown reasons."));
|
||||
};
|
||||
const wMsg = (type,args)=>W.postMessage({type,args});
|
||||
/**
|
||||
Generic utilities for working with OPFS. This will get filled out
|
||||
by the Promise setup and, on success, installed as sqlite3.opfs.
|
||||
@ -203,7 +202,6 @@ sqlite3.installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri)
|
||||
{
|
||||
let i = 0;
|
||||
state.opIds.whichOp = i++;
|
||||
state.opIds.nothing = i++;
|
||||
state.opIds.xAccess = i++;
|
||||
state.rcIds.xAccess = i++;
|
||||
state.opIds.xClose = i++;
|
||||
@ -228,8 +226,9 @@ sqlite3.installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri)
|
||||
state.rcIds.xWrite = i++;
|
||||
state.opIds.mkdir = i++;
|
||||
state.rcIds.mkdir = i++;
|
||||
state.opIds.xFileControl = i++;
|
||||
state.rcIds.xFileControl = i++;
|
||||
state.sabOP = new SharedArrayBuffer(i * 4/*sizeof int32*/);
|
||||
state.opIds.xFileControl = state.opIds.xSync /* special case */;
|
||||
opfsUtil.metrics.reset();
|
||||
}
|
||||
|
||||
@ -260,12 +259,17 @@ sqlite3.installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri)
|
||||
given operation's signature in the async API counterpart.
|
||||
*/
|
||||
const opRun = (op,...args)=>{
|
||||
const rcNdx = state.rcIds[op] || toss("Invalid rc ID:",op);
|
||||
const opNdx = state.opIds[op] || toss("Invalid op ID:",op);
|
||||
state.s11n.serialize(...args);
|
||||
Atomics.store(state.sabOPView, rcNdx, -1);
|
||||
Atomics.store(state.sabOPView, state.opIds.whichOp, opNdx);
|
||||
Atomics.notify(state.sabOPView, state.opIds.whichOp) /* async thread will take over here */;
|
||||
const t = performance.now();
|
||||
Atomics.store(state.sabOPView, state.opIds[op], -1);
|
||||
wMsg(op, args);
|
||||
Atomics.wait(state.sabOPView, state.opIds[op], -1);
|
||||
Atomics.wait(state.sabOPView, rcNdx, -1);
|
||||
const rc = Atomics.load(state.sabOPView, rcNdx);
|
||||
metrics[op].wait += performance.now() - t;
|
||||
return Atomics.load(state.sabOPView, state.opIds[op]);
|
||||
return rc;
|
||||
};
|
||||
|
||||
const initS11n = ()=>{
|
||||
@ -297,11 +301,25 @@ sqlite3.installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri)
|
||||
serialization for simplicy of implementation, but if that
|
||||
proves imperformant then a lower-level approach will be
|
||||
created.
|
||||
|
||||
If passed "too much data" (more that the shared buffer size
|
||||
it will either throw or truncate the data (not certain
|
||||
which)). This routine is only intended for serializing OPFS
|
||||
VFS arguments and (in at least one special case) result
|
||||
values, and the buffer is sized to be able to comfortably
|
||||
handle those.
|
||||
|
||||
If passed no arguments then it zeroes out the serialization
|
||||
state.
|
||||
*/
|
||||
state.s11n.serialize = function(...args){
|
||||
const json = jsonEncoder.encode(JSON.stringify(args));
|
||||
viewSz.setInt32(0, json.byteLength, state.littleEndian);
|
||||
viewJson.set(json);
|
||||
if(args.length){
|
||||
const json = jsonEncoder.encode(JSON.stringify(args));
|
||||
viewSz.setInt32(0, json.byteLength, state.littleEndian);
|
||||
viewJson.set(json);
|
||||
}else{
|
||||
viewSz.setInt32(0, 0, state.littleEndian);
|
||||
}
|
||||
};
|
||||
return state.s11n;
|
||||
};
|
||||
@ -552,9 +570,8 @@ sqlite3.installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri)
|
||||
const vfsSyncWrappers = {
|
||||
xAccess: function(pVfs,zName,flags,pOut){
|
||||
mTimeStart('xAccess');
|
||||
wasm.setMemValue(
|
||||
pOut, (opRun('xAccess', wasm.cstringToJs(zName)) ? 0 : 1), 'i32'
|
||||
);
|
||||
const rc = opRun('xAccess', wasm.cstringToJs(zName));
|
||||
wasm.setMemValue( pOut, (rc ? 0 : 1), 'i32' );
|
||||
mTimeEnd();
|
||||
return 0;
|
||||
},
|
||||
@ -686,22 +703,12 @@ sqlite3.installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri)
|
||||
opfsUtil.deleteEntry = function(fsEntryName,recursive=false){
|
||||
return 0===opRun('xDelete', fsEntryName, 0, recursive);
|
||||
};
|
||||
/**
|
||||
Exactly like deleteEntry() but runs asynchronously. This is a
|
||||
"fire and forget" operation: it does not return a promise
|
||||
because the counterpart operation happens in another thread and
|
||||
waiting on that result in a Promise would block the OPFS VFS
|
||||
from acting until it completed.
|
||||
*/
|
||||
opfsUtil.deleteEntryAsync = function(fsEntryName,recursive=false){
|
||||
wMsg('xDeleteNoWait', [fsEntryName, 0, recursive]);
|
||||
};
|
||||
/**
|
||||
Synchronously creates the given directory name, recursively, in
|
||||
the OPFS filesystem. Returns true if it succeeds or the
|
||||
directory already exists, else false.
|
||||
*/
|
||||
opfsUtil.mkdir = async function(absDirName){
|
||||
opfsUtil.mkdir = function(absDirName){
|
||||
return 0===opRun('mkdir', absDirName);
|
||||
};
|
||||
/**
|
||||
@ -736,7 +743,7 @@ sqlite3.installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri)
|
||||
features like getting a directory listing.
|
||||
*/
|
||||
|
||||
const sanityCheck = async function(){
|
||||
const sanityCheck = function(){
|
||||
const scope = wasm.scopedAllocPush();
|
||||
const sq3File = new sqlite3_file();
|
||||
try{
|
||||
@ -791,6 +798,7 @@ sqlite3.installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri)
|
||||
vfsSyncWrappers.xAccess(opfsVfs.pointer, zDbFile, 0, pOut);
|
||||
rc = wasm.getMemValue(pOut,'i32');
|
||||
if(rc) toss("Expecting 0 from xAccess(",dbFile,") after xDelete().");
|
||||
log("End of OPFS sanity checks.");
|
||||
}finally{
|
||||
sq3File.dispose();
|
||||
wasm.scopedAllocPop(scope);
|
||||
@ -803,7 +811,7 @@ sqlite3.installOpfsVfs = function callee(asyncProxyUri = callee.defaultProxyUri)
|
||||
switch(data.type){
|
||||
case 'opfs-async-loaded':
|
||||
/*Pass our config and shared state on to the async worker.*/
|
||||
wMsg('opfs-async-init',state);
|
||||
W.postMessage({type: 'opfs-async-init',args: state});
|
||||
break;
|
||||
case 'opfs-async-inited':{
|
||||
/*Indicates that the async partner has received the 'init',
|
||||
|
@ -212,70 +212,71 @@
|
||||
/* TODO? Flags which require values need custom UI
|
||||
controls and some of them make little sense here
|
||||
(e.g. --script FILE). */
|
||||
flags["autovacuum"] = "Enable AUTOVACUUM mode";
|
||||
flags["big-transactions"] = "Important for tests 410 and 510!";
|
||||
//flags["cachesize"] = "N Set the cache size to N";
|
||||
flags["checkpoint"] = "Run PRAGMA wal_checkpoint after each test case";
|
||||
flags["exclusive"] = "Enable locking_mode=EXCLUSIVE";
|
||||
flags["explain"] = "Like --sqlonly but with added EXPLAIN keywords";
|
||||
//flags["heap"] = "SZ MIN Memory allocator uses SZ bytes & min allocation MIN";
|
||||
flags["incrvacuum"] = "Enable incremenatal vacuum mode";
|
||||
//flags["journal"] = "M Set the journal_mode to M";
|
||||
//flags["key"] = "KEY Set the encryption key to KEY";
|
||||
//flags["lookaside"] = "N SZ Configure lookaside for N slots of SZ bytes each";
|
||||
flags["memdb"] = "Use an in-memory database";
|
||||
//flags["mmap"] = "SZ MMAP the first SZ bytes of the database file";
|
||||
flags["multithread"] = "Set multithreaded mode";
|
||||
flags["nomemstat"] = "Disable memory statistics";
|
||||
flags["nomutex"] = "Open db with SQLITE_OPEN_NOMUTEX";
|
||||
flags["nosync"] = "Set PRAGMA synchronous=OFF";
|
||||
flags["notnull"] = "Add NOT NULL constraints to table columns";
|
||||
//flags["output"] = "FILE Store SQL output in FILE";
|
||||
//flags["pagesize"] = "N Set the page size to N";
|
||||
//flags["pcache"] = "N SZ Configure N pages of pagecache each of size SZ bytes";
|
||||
//flags["primarykey"] = "Use PRIMARY KEY instead of UNIQUE where appropriate";
|
||||
//flags["repeat"] = "N Repeat each SELECT N times (default: 1)";
|
||||
flags["reprepare"] = "Reprepare each statement upon every invocation";
|
||||
//flags["reserve"] = "N Reserve N bytes on each database page";
|
||||
//flags["script"] = "FILE Write an SQL script for the test into FILE";
|
||||
flags["serialized"] = "Set serialized threading mode";
|
||||
flags["singlethread"] = "Set single-threaded mode - disables all mutexing";
|
||||
flags["sqlonly"] = "No-op. Only show the SQL that would have been run.";
|
||||
flags["shrink"] = "memory Invoke sqlite3_db_release_memory() frequently.";
|
||||
//flags["size"] = "N Relative test size. Default=100";
|
||||
flags["strict"] = "Use STRICT table where appropriate";
|
||||
flags["stats"] = "Show statistics at the end";
|
||||
//flags["temp"] = "N N from 0 to 9. 0: no temp table. 9: all temp tables";
|
||||
//flags["testset"] = "T Run test-set T (main, cte, rtree, orm, fp, debug)";
|
||||
flags["trace"] = "Turn on SQL tracing";
|
||||
//flags["threads"] = "N Use up to N threads for sorting";
|
||||
flags["--autovacuum"] = "Enable AUTOVACUUM mode";
|
||||
flags["--big-transactions"] = "Important for tests 410 and 510!";
|
||||
//flags["--cachesize"] = "N Set the cache size to N";
|
||||
flags["--checkpoint"] = "Run PRAGMA wal_checkpoint after each test case";
|
||||
flags["--exclusive"] = "Enable locking_mode=EXCLUSIVE";
|
||||
flags["--explain"] = "Like --sqlonly but with added EXPLAIN keywords";
|
||||
//flags["--heap"] = "SZ MIN Memory allocator uses SZ bytes & min allocation MIN";
|
||||
flags["--incrvacuum"] = "Enable incremenatal vacuum mode";
|
||||
//flags["--journal"] = "M Set the journal_mode to M";
|
||||
//flags["--key"] = "KEY Set the encryption key to KEY";
|
||||
//flags["--lookaside"] = "N SZ Configure lookaside for N slots of SZ bytes each";
|
||||
flags["--memdb"] = "Use an in-memory database";
|
||||
//flags["--mmap"] = "SZ MMAP the first SZ bytes of the database file";
|
||||
flags["--multithread"] = "Set multithreaded mode";
|
||||
flags["--nomemstat"] = "Disable memory statistics";
|
||||
flags["--nomutex"] = "Open db with SQLITE_OPEN_NOMUTEX";
|
||||
flags["--nosync"] = "Set PRAGMA synchronous=OFF";
|
||||
flags["--notnull"] = "Add NOT NULL constraints to table columns";
|
||||
//flags["--output"] = "FILE Store SQL output in FILE";
|
||||
//flags["--pagesize"] = "N Set the page size to N";
|
||||
//flags["--pcache"] = "N SZ Configure N pages of pagecache each of size SZ bytes";
|
||||
//flags["--primarykey"] = "Use PRIMARY KEY instead of UNIQUE where appropriate";
|
||||
//flags["--repeat"] = "N Repeat each SELECT N times (default: 1)";
|
||||
flags["--reprepare"] = "Reprepare each statement upon every invocation";
|
||||
//flags["--reserve"] = "N Reserve N bytes on each database page";
|
||||
//flags["--script"] = "FILE Write an SQL script for the test into FILE";
|
||||
flags["--serialized"] = "Set serialized threading mode";
|
||||
flags["--singlethread"] = "Set single-threaded mode - disables all mutexing";
|
||||
flags["--sqlonly"] = "No-op. Only show the SQL that would have been run.";
|
||||
flags["--shrink"] = "memory Invoke sqlite3_db_release_memory() frequently.";
|
||||
//flags["--size"] = "N Relative test size. Default=100";
|
||||
flags["--strict"] = "Use STRICT table where appropriate";
|
||||
flags["--stats"] = "Show statistics at the end";
|
||||
//flags["--temp"] = "N N from 0 to 9. 0: no temp table. 9: all temp tables";
|
||||
//flags["--testset"] = "T Run test-set T (main, cte, rtree, orm, fp, debug)";
|
||||
flags["--trace"] = "Turn on SQL tracing";
|
||||
//flags["--threads"] = "N Use up to N threads for sorting";
|
||||
/*
|
||||
The core API's WASM build does not support UTF16, but in
|
||||
this app it's not an issue because the data are not crossing
|
||||
JS/WASM boundaries.
|
||||
*/
|
||||
flags["utf16be"] = "Set text encoding to UTF-16BE";
|
||||
flags["utf16le"] = "Set text encoding to UTF-16LE";
|
||||
flags["verify"] = "Run additional verification steps.";
|
||||
flags["without"] = "rowid Use WITHOUT ROWID where appropriate";
|
||||
flags["--utf16be"] = "Set text encoding to UTF-16BE";
|
||||
flags["--utf16le"] = "Set text encoding to UTF-16LE";
|
||||
flags["--verify"] = "Run additional verification steps.";
|
||||
flags["--without"] = "rowid Use WITHOUT ROWID where appropriate";
|
||||
const preselectedFlags = [
|
||||
'big-transactions',
|
||||
'singlethread'
|
||||
'--big-transactions',
|
||||
'--singlethread'
|
||||
];
|
||||
if('opfs'!==urlParams.get('vfs')){
|
||||
preselectedFlags.push('memdb');
|
||||
if(urlParams.has('flags')){
|
||||
preselectedFlags.push(...urlParams.get('flags').split(','));
|
||||
}
|
||||
if('opfs'!==urlParams.get('vfs')){
|
||||
preselectedFlags.push('--memdb');
|
||||
}
|
||||
|
||||
Object.keys(flags).sort().forEach(function(f){
|
||||
const opt = document.createElement('option');
|
||||
eFlags.appendChild(opt);
|
||||
const lbl = nbspPad('--'+f)+flags[f];
|
||||
const lbl = nbspPad(f)+flags[f];
|
||||
//opt.innerText = lbl;
|
||||
opt.innerHTML = lbl;
|
||||
opt.value = '--'+f;
|
||||
opt.value = f;
|
||||
if(preselectedFlags.indexOf(f) >= 0) opt.selected = true;
|
||||
});
|
||||
|
||||
});
|
||||
const cbReverseLog = E('#cb-reverse-log-order');
|
||||
const lblReverseLog = E('#lbl-reverse-log-order');
|
||||
if(cbReverseLog.checked){
|
||||
|
@ -63,7 +63,7 @@ const error = (...args)=>logImpl(0, ...args);
|
||||
const metrics = Object.create(null);
|
||||
metrics.reset = ()=>{
|
||||
let k;
|
||||
const r = (m)=>(m.count = m.time = 0);
|
||||
const r = (m)=>(m.count = m.time = m.wait = 0);
|
||||
for(k in state.opIds){
|
||||
r(metrics[k] = Object.create(null));
|
||||
}
|
||||
@ -74,11 +74,15 @@ metrics.dump = ()=>{
|
||||
const m = metrics[k];
|
||||
n += m.count;
|
||||
t += m.time;
|
||||
w += m.wait;
|
||||
m.avgTime = (m.count && m.time) ? (m.time / m.count) : 0;
|
||||
}
|
||||
console.log(self.location.href,
|
||||
"metrics for",self.location.href,":",metrics,
|
||||
"\nTotal of",n,"op(s) for",t,"ms");
|
||||
"metrics for",self.location.href,":\n",
|
||||
JSON.stringify(metrics,0,2)
|
||||
/*dev console can't expand this object!*/,
|
||||
"\nTotal of",n,"op(s) for",t,"ms",
|
||||
"approx",w,"ms spent waiting on OPFS APIs.");
|
||||
};
|
||||
|
||||
warn("This file is very much experimental and under construction.",
|
||||
@ -130,9 +134,9 @@ const getDirForPath = async function f(absFilename, createDirs = false){
|
||||
and then Atomics.notify()'s it.
|
||||
*/
|
||||
const storeAndNotify = (opName, value)=>{
|
||||
log(opName+"() is notify()ing w/ value:",value);
|
||||
Atomics.store(state.sabOPView, state.opIds[opName], value);
|
||||
Atomics.notify(state.sabOPView, state.opIds[opName]);
|
||||
log(opName+"() => notify(",state.rcIds[opName],",",value,")");
|
||||
Atomics.store(state.sabOPView, state.rcIds[opName], value);
|
||||
Atomics.notify(state.sabOPView, state.rcIds[opName]);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -155,6 +159,17 @@ const mTimeStart = (op)=>{
|
||||
const mTimeEnd = ()=>(
|
||||
metrics[opTimer.op].time += performance.now() - opTimer.start
|
||||
);
|
||||
const waitTimer = Object.create(null);
|
||||
waitTimer.op = undefined;
|
||||
waitTimer.start = undefined;
|
||||
const wTimeStart = (op)=>{
|
||||
waitTimer.start = performance.now();
|
||||
waitTimer.op = op;
|
||||
//metrics[op] || toss("Maintenance required: missing metrics for",op);
|
||||
};
|
||||
const wTimeEnd = ()=>(
|
||||
metrics[waitTimer.op].wait += performance.now() - waitTimer.start
|
||||
);
|
||||
|
||||
/**
|
||||
Asynchronous wrappers for sqlite3_vfs and sqlite3_io_methods
|
||||
@ -163,17 +178,20 @@ const mTimeEnd = ()=>(
|
||||
*/
|
||||
const vfsAsyncImpls = {
|
||||
mkdir: async function(dirname){
|
||||
mTimeStart('mkdir');
|
||||
let rc = 0;
|
||||
wTimeStart('mkdir');
|
||||
try {
|
||||
await getDirForPath(dirname+"/filepart", true);
|
||||
}catch(e){
|
||||
//error("mkdir failed",filename, e.message);
|
||||
rc = state.sq3Codes.SQLITE_IOERR;
|
||||
}
|
||||
wTimeEnd();
|
||||
storeAndNotify('mkdir', rc);
|
||||
mTimeEnd();
|
||||
},
|
||||
xAccess: async function(filename){
|
||||
log("xAccess(",arguments[0],")");
|
||||
mTimeStart('xAccess');
|
||||
/* OPFS cannot support the full range of xAccess() queries sqlite3
|
||||
calls for. We can essentially just tell if the file is
|
||||
@ -187,20 +205,23 @@ const vfsAsyncImpls = {
|
||||
accessible, non-0 means not accessible.
|
||||
*/
|
||||
let rc = 0;
|
||||
wTimeStart('xAccess');
|
||||
try{
|
||||
const [dh, fn] = await getDirForPath(filename);
|
||||
await dh.getFileHandle(fn);
|
||||
}catch(e){
|
||||
rc = state.sq3Codes.SQLITE_IOERR;
|
||||
}
|
||||
wTimeEnd();
|
||||
storeAndNotify('xAccess', rc);
|
||||
mTimeEnd();
|
||||
},
|
||||
xClose: async function(fid){
|
||||
const opName = 'xClose';
|
||||
mTimeStart(opName);
|
||||
log(opName+"(",arguments[0],")");
|
||||
const fh = __openFiles[fid];
|
||||
let rc = 0;
|
||||
wTimeStart('xClose');
|
||||
if(fh){
|
||||
delete __openFiles[fid];
|
||||
if(fh.accessHandle) await fh.accessHandle.close();
|
||||
@ -208,10 +229,11 @@ const vfsAsyncImpls = {
|
||||
try{ await fh.dirHandle.removeEntry(fh.filenamePart) }
|
||||
catch(e){ warn("Ignoring dirHandle.removeEntry() failure of",fh,e) }
|
||||
}
|
||||
storeAndNotify(opName, 0);
|
||||
}else{
|
||||
storeAndNotify(opName, state.sq3Codes.SQLITE_NOFOUND);
|
||||
rc = state.sq3Codes.SQLITE_NOTFOUND;
|
||||
}
|
||||
wTimeEnd();
|
||||
storeAndNotify(opName, rc);
|
||||
mTimeEnd();
|
||||
},
|
||||
xDelete: async function(...args){
|
||||
@ -233,12 +255,11 @@ const vfsAsyncImpls = {
|
||||
presumably it will fail if the dir is not empty and that flag
|
||||
is false.
|
||||
*/
|
||||
log("xDelete(",arguments[0],")");
|
||||
let rc = 0;
|
||||
wTimeStart('xDelete');
|
||||
try {
|
||||
while(filename){
|
||||
const [hDir, filenamePart] = await getDirForPath(filename, false);
|
||||
//log("Removing:",hDir, filenamePart);
|
||||
if(!filenamePart) break;
|
||||
await hDir.removeEntry(filenamePart, {recursive});
|
||||
if(0x1234 !== syncDir) break;
|
||||
@ -252,13 +273,14 @@ const vfsAsyncImpls = {
|
||||
//error("Delete failed",filename, e.message);
|
||||
rc = state.sq3Codes.SQLITE_IOERR_DELETE;
|
||||
}
|
||||
wTimeEnd();
|
||||
return rc;
|
||||
},
|
||||
xFileSize: async function(fid){
|
||||
mTimeStart('xFileSize');
|
||||
log("xFileSize(",arguments,")");
|
||||
const fh = __openFiles[fid];
|
||||
let sz;
|
||||
wTimeStart('xFileSize');
|
||||
try{
|
||||
sz = await fh.accessHandle.getSize();
|
||||
state.s11n.serialize(Number(sz));
|
||||
@ -267,15 +289,16 @@ const vfsAsyncImpls = {
|
||||
error("xFileSize():",e, fh);
|
||||
sz = state.sq3Codes.SQLITE_IOERR;
|
||||
}
|
||||
wTimeEnd();
|
||||
storeAndNotify('xFileSize', sz);
|
||||
mTimeEnd();
|
||||
},
|
||||
xOpen: async function(fid/*sqlite3_file pointer*/, filename, flags){
|
||||
const opName = 'xOpen';
|
||||
mTimeStart(opName);
|
||||
log(opName+"(",arguments[0],")");
|
||||
const deleteOnClose = (state.sq3Codes.SQLITE_OPEN_DELETEONCLOSE & flags);
|
||||
const create = (state.sq3Codes.SQLITE_OPEN_CREATE & flags);
|
||||
wTimeStart('xOpen');
|
||||
try{
|
||||
let hDir, filenamePart;
|
||||
try {
|
||||
@ -283,6 +306,7 @@ const vfsAsyncImpls = {
|
||||
}catch(e){
|
||||
storeAndNotify(opName, state.sql3Codes.SQLITE_NOTFOUND);
|
||||
mTimeEnd();
|
||||
wTimeEnd();
|
||||
return;
|
||||
}
|
||||
const hFile = await hDir.getFileHandle(filenamePart, {create});
|
||||
@ -294,6 +318,7 @@ const vfsAsyncImpls = {
|
||||
places that limitation on it.
|
||||
*/
|
||||
fobj.accessHandle = await hFile.createSyncAccessHandle();
|
||||
wTimeEnd();
|
||||
__openFiles[fid] = fobj;
|
||||
fobj.filenameAbs = filename;
|
||||
fobj.filenamePart = filenamePart;
|
||||
@ -304,6 +329,7 @@ const vfsAsyncImpls = {
|
||||
fobj.deleteOnClose = deleteOnClose;
|
||||
storeAndNotify(opName, 0);
|
||||
}catch(e){
|
||||
wTimeEnd();
|
||||
error(opName,e);
|
||||
storeAndNotify(opName, state.sq3Codes.SQLITE_IOERR);
|
||||
}
|
||||
@ -311,14 +337,15 @@ const vfsAsyncImpls = {
|
||||
},
|
||||
xRead: async function(fid,n,offset){
|
||||
mTimeStart('xRead');
|
||||
log("xRead(",arguments[0],")");
|
||||
let rc = 0;
|
||||
try{
|
||||
const fh = __openFiles[fid];
|
||||
wTimeStart('xRead');
|
||||
const nRead = fh.accessHandle.read(
|
||||
fh.sabView.subarray(0, n),
|
||||
{at: Number(offset)}
|
||||
);
|
||||
wTimeEnd();
|
||||
if(nRead < n){/* Zero-fill remaining bytes */
|
||||
fh.sabView.fill(0, nRead, n);
|
||||
rc = state.sq3Codes.SQLITE_IOERR_SHORT_READ;
|
||||
@ -332,17 +359,20 @@ const vfsAsyncImpls = {
|
||||
},
|
||||
xSync: async function(fid,flags/*ignored*/){
|
||||
mTimeStart('xSync');
|
||||
log("xSync(",arguments[0],")");
|
||||
const fh = __openFiles[fid];
|
||||
if(!fh.readOnly && fh.accessHandle) await fh.accessHandle.flush();
|
||||
if(!fh.readOnly && fh.accessHandle){
|
||||
wTimeStart('xSync');
|
||||
await fh.accessHandle.flush();
|
||||
wTimeEnd();
|
||||
}
|
||||
storeAndNotify('xSync',0);
|
||||
mTimeEnd();
|
||||
},
|
||||
xTruncate: async function(fid,size){
|
||||
mTimeStart('xTruncate');
|
||||
log("xTruncate(",arguments[0],")");
|
||||
let rc = 0;
|
||||
const fh = __openFiles[fid];
|
||||
wTimeStart('xTruncate');
|
||||
try{
|
||||
affirmNotRO('xTruncate', fh);
|
||||
await fh.accessHandle.truncate(size);
|
||||
@ -350,13 +380,14 @@ const vfsAsyncImpls = {
|
||||
error("xTruncate():",e,fh);
|
||||
rc = state.sq3Codes.SQLITE_IOERR_TRUNCATE;
|
||||
}
|
||||
wTimeEnd();
|
||||
storeAndNotify('xTruncate',rc);
|
||||
mTimeEnd();
|
||||
},
|
||||
xWrite: async function(fid,n,offset){
|
||||
mTimeStart('xWrite');
|
||||
log("xWrite(",arguments[0],")");
|
||||
let rc;
|
||||
wTimeStart('xWrite');
|
||||
try{
|
||||
const fh = __openFiles[fid];
|
||||
affirmNotRO('xWrite', fh);
|
||||
@ -367,13 +398,14 @@ const vfsAsyncImpls = {
|
||||
}catch(e){
|
||||
error("xWrite():",e,fh);
|
||||
rc = state.sq3Codes.SQLITE_IOERR_WRITE;
|
||||
}finally{
|
||||
wTimeEnd();
|
||||
}
|
||||
storeAndNotify('xWrite',rc);
|
||||
mTimeEnd();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const initS11n = ()=>{
|
||||
// Achtung: this code is 100% duplicated in the other half of this proxy!
|
||||
if(state.s11n) return state.s11n;
|
||||
@ -403,46 +435,69 @@ const initS11n = ()=>{
|
||||
serialization for simplicy of implementation, but if that
|
||||
proves imperformant then a lower-level approach will be
|
||||
created.
|
||||
|
||||
If passed "too much data" (more that the shared buffer size
|
||||
it will either throw or truncate the data (not certain
|
||||
which)). This routine is only intended for serializing OPFS
|
||||
VFS arguments and (in at least one special case) result
|
||||
values, and the buffer is sized to be able to comfortably
|
||||
handle those.
|
||||
|
||||
If passed no arguments then it zeroes out the serialization
|
||||
state.
|
||||
*/
|
||||
state.s11n.serialize = function(...args){
|
||||
const json = jsonEncoder.encode(JSON.stringify(args));
|
||||
viewSz.setInt32(0, json.byteLength, state.littleEndian);
|
||||
viewJson.set(json);
|
||||
if(args.length){
|
||||
const json = jsonEncoder.encode(JSON.stringify(args));
|
||||
viewSz.setInt32(0, json.byteLength, state.littleEndian);
|
||||
viewJson.set(json);
|
||||
}else{
|
||||
viewSz.setInt32(0, 0, state.littleEndian);
|
||||
}
|
||||
};
|
||||
return state.s11n;
|
||||
};
|
||||
|
||||
const waitLoop = function(){
|
||||
const waitLoop = async function f(){
|
||||
const opHandlers = Object.create(null);
|
||||
for(let k of Object.keys(state.opIds)){
|
||||
for(let k of Object.keys(state.rcIds)){
|
||||
const o = Object.create(null);
|
||||
opHandlers[state.opIds[k]] = o;
|
||||
o.key = k;
|
||||
o.f = vfsAsyncImpls[k];// || toss("No vfsAsyncImpls[",k,"]");
|
||||
}
|
||||
const sabOP = state.sabOP;
|
||||
for(;;){
|
||||
let metricsTimer = self.location.port>=1024 ? performance.now() : 0;
|
||||
// ^^^ in dev environment, dump out these metrics one time after a delay.
|
||||
while(true){
|
||||
try {
|
||||
Atomics.store(sabOP, state.opIds.whichOp, 0);
|
||||
Atomic.wait(sabOP, state.opIds.whichOp);
|
||||
const opId = Atomics.load(sabOP, state.opIds.whichOp);
|
||||
if('timed-out'===Atomics.wait(state.sabOPView, state.opIds.whichOp, 0, 150)){
|
||||
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();
|
||||
log("whichOp =",opId,hnd,args);
|
||||
const rc = 0/*TODO: run op*/;
|
||||
Atomics.store(sabOP, state.rcIds[hnd.key], rc);
|
||||
Atomics.notify(sabOP, state.rcIds[hnd.key]);
|
||||
//warn("waitLoop() whichOp =",opId, hnd, args);
|
||||
if(hnd.f) await hnd.f(...args);
|
||||
else error("Missing callback for opId",opId);
|
||||
}catch(e){
|
||||
error('in waitLoop():',e.message);
|
||||
}finally{
|
||||
// We can't call metrics.dump() from the dev console because this
|
||||
// thread is continually tied up in Atomics.wait(), so let's
|
||||
// do, for dev purposes only, a dump one time after 60 seconds.
|
||||
if(metricsTimer && (performance.now() > metricsTimer + 60000)){
|
||||
metrics.dump();
|
||||
metricsTimer = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
navigator.storage.getDirectory().then(function(d){
|
||||
const wMsg = (type)=>postMessage({type});
|
||||
state.rootDir = d;
|
||||
log("state.rootDir =",state.rootDir);
|
||||
self.onmessage = async function({data}){
|
||||
log("self.onmessage()",data);
|
||||
self.onmessage = function({data}){
|
||||
switch(data.type){
|
||||
case 'opfs-async-init':{
|
||||
/* Receive shared state from synchronous partner */
|
||||
@ -469,20 +524,7 @@ navigator.storage.getDirectory().then(function(d){
|
||||
metrics.reset();
|
||||
log("init state",state);
|
||||
wMsg('opfs-async-inited');
|
||||
break;
|
||||
}
|
||||
default:{
|
||||
let err;
|
||||
const m = vfsAsyncImpls[data.type] || toss("Unknown message type:",data.type);
|
||||
try {
|
||||
await m(...data.args).catch((e)=>err=e);
|
||||
}catch(e){
|
||||
err = e;
|
||||
}
|
||||
if(err){
|
||||
error("Error handling",data.type+"():",e);
|
||||
storeAndNotify(data.type, state.sq3Codes.SQLITE_ERROR);
|
||||
}
|
||||
waitLoop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user