1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-29 08:01:23 +03:00

Internal restructuring of the OPFS sqlite3_vfs in order to facilitate certain experimentation and improve error reporting/hints if it cannot be activated. Deprecate the name sqlite3.opfs.OpfsDb, preferring sqlite3.oo1.OpfsDb for consistency with JsStorageDb and any future DB subclasses.

FossilOrigin-Name: 0c5c51f4fb04a4b90c50ec9704cfea9a3fb7d7d0ee55c1b0d4476129188217a6
This commit is contained in:
stephan
2022-11-29 05:25:08 +00:00
parent e6b0154138
commit 04184761de
6 changed files with 919 additions and 876 deletions

View File

@ -76,15 +76,25 @@ self.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
`opfs` property, containing several OPFS-specific utilities. `opfs` property, containing several OPFS-specific utilities.
*/ */
const installOpfsVfs = function callee(options){ const installOpfsVfs = function callee(options){
if(!self.SharedArrayBuffer || if(!self.SharedArrayBuffer
!self.Atomics || || !self.Atomics){
!self.FileSystemHandle || return Promise.reject(
new Error("Cannot install OPFS: Missing SharedArrayBuffer and/or Atomics. "+
"The server must emit the COOP/COEP response headers to enable those. "+
"See https://sqlite.org/wasm/doc/trunk/persistence.md#coop-coep")
);
}else if(self.window===self && self.document){
return Promise.reject(
new Error("The OPFS sqlite3_vfs cannot run in the main thread "+
"because it requires Atomics.wait().")
);
}else if(!self.FileSystemHandle ||
!self.FileSystemDirectoryHandle || !self.FileSystemDirectoryHandle ||
!self.FileSystemFileHandle || !self.FileSystemFileHandle ||
!self.FileSystemFileHandle.prototype.createSyncAccessHandle || !self.FileSystemFileHandle.prototype.createSyncAccessHandle ||
!navigator.storage.getDirectory){ !navigator.storage.getDirectory){
return Promise.reject( return Promise.reject(
new Error("This environment does not have OPFS support.") new Error("Missing required OPFS APIs.")
); );
} }
if(!options || 'object'!==typeof options){ if(!options || 'object'!==typeof options){
@ -134,6 +144,18 @@ const installOpfsVfs = function callee(options){
OPFS-specific sqlite3_vfs evolves. OPFS-specific sqlite3_vfs evolves.
*/ */
const opfsUtil = Object.create(null); const opfsUtil = Object.create(null);
/**
Returns true if _this_ thread has access to the OPFS APIs.
*/
const thisThreadHasOPFS = ()=>{
return self.FileSystemHandle &&
self.FileSystemDirectoryHandle &&
self.FileSystemFileHandle &&
self.FileSystemFileHandle.prototype.createSyncAccessHandle &&
navigator.storage.getDirectory;
};
/** /**
Not part of the public API. Solely for internal/development Not part of the public API. Solely for internal/development
use. use.
@ -1179,12 +1201,16 @@ const installOpfsVfs = function callee(options){
//consideration. //consideration.
if(sqlite3.oo1){ if(sqlite3.oo1){
opfsUtil.OpfsDb = function(...args){ const OpfsDb = function(...args){
const opt = sqlite3.oo1.DB.dbCtorHelper.normalizeArgs(...args); const opt = sqlite3.oo1.DB.dbCtorHelper.normalizeArgs(...args);
opt.vfs = opfsVfs.$zName; opt.vfs = opfsVfs.$zName;
sqlite3.oo1.DB.dbCtorHelper.call(this, opt); sqlite3.oo1.DB.dbCtorHelper.call(this, opt);
}; };
opfsUtil.OpfsDb.prototype = Object.create(sqlite3.oo1.DB.prototype); sqlite3.oo1.OpfsDb =
opfsUtil.OpfsDb /* sqlite3.opfs.OpfsDb => deprecated name -
will be phased out Real Soon */ =
OpfsDb;
OpfsDb.prototype = Object.create(sqlite3.oo1.DB.prototype);
sqlite3.oo1.DB.dbCtorHelper.setVfsPostOpenSql( sqlite3.oo1.DB.dbCtorHelper.setVfsPostOpenSql(
opfsVfs.pointer, opfsVfs.pointer,
[ [
@ -1206,13 +1232,6 @@ const installOpfsVfs = function callee(options){
); );
} }
/**
Potential TODOs:
- Expose one or both of the Worker objects via opfsUtil and
publish an interface for proxying the higher-level OPFS
features like getting a directory listing.
*/
const sanityCheck = function(){ const sanityCheck = function(){
const scope = wasm.scopedAllocPush(); const scope = wasm.scopedAllocPush();
const sq3File = new sqlite3_file(); const sq3File = new sqlite3_file();
@ -1282,6 +1301,11 @@ const installOpfsVfs = function callee(options){
W.onmessage = function({data}){ W.onmessage = function({data}){
//log("Worker.onmessage:",data); //log("Worker.onmessage:",data);
switch(data.type){ switch(data.type){
case 'opfs-unavailable':
/* Async proxy has determined that OPFS is unavailable. There's
nothing more for us to do here. */
promiseReject(new Error(data.payload.join(' ')));
break;
case 'opfs-async-loaded': case 'opfs-async-loaded':
/*Arrives as soon as the asyc proxy finishes loading. /*Arrives as soon as the asyc proxy finishes loading.
Pass our config and shared state on to the async worker.*/ Pass our config and shared state on to the async worker.*/
@ -1308,6 +1332,7 @@ const installOpfsVfs = function callee(options){
warn("Running sanity checks because of opfs-sanity-check URL arg..."); warn("Running sanity checks because of opfs-sanity-check URL arg...");
sanityCheck(); sanityCheck();
} }
if(thisThreadHasOPFS()){
navigator.storage.getDirectory().then((d)=>{ navigator.storage.getDirectory().then((d)=>{
W.onerror = W._originalOnError; W.onerror = W._originalOnError;
delete W._originalOnError; delete W._originalOnError;
@ -1316,6 +1341,9 @@ const installOpfsVfs = function callee(options){
log("End of OPFS sqlite3_vfs setup.", opfsVfs); log("End of OPFS sqlite3_vfs setup.", opfsVfs);
promiseResolve(sqlite3); promiseResolve(sqlite3);
}); });
}else{
promiseResolve(sqlite3);
}
}catch(e){ }catch(e){
error(e); error(e);
promiseReject(e); promiseReject(e);
@ -1334,9 +1362,6 @@ const installOpfsVfs = function callee(options){
installOpfsVfs.defaultProxyUri = installOpfsVfs.defaultProxyUri =
"sqlite3-opfs-async-proxy.js"; "sqlite3-opfs-async-proxy.js";
self.sqlite3ApiBootstrap.initializersAsync.push(async (sqlite3)=>{ self.sqlite3ApiBootstrap.initializersAsync.push(async (sqlite3)=>{
if(sqlite3.scriptInfo && !sqlite3.scriptInfo.isWorker){
return;
}
try{ try{
let proxyJs = installOpfsVfs.defaultProxyUri; let proxyJs = installOpfsVfs.defaultProxyUri;
if(sqlite3.scriptInfo.sqlite3Dir){ if(sqlite3.scriptInfo.sqlite3Dir){

View File

@ -47,43 +47,45 @@
usage of those methods to remove the "await". usage of those methods to remove the "await".
*/ */
"use strict"; "use strict";
const toss = function(...args){throw new Error(args.join(' '))}; const wPost = (type,...args)=>postMessage({type, payload:args});
if(self.window === self){ const installAsyncProxy = function(self){
const toss = function(...args){throw new Error(args.join(' '))};
if(self.window === self){
toss("This code cannot run from the main thread.", toss("This code cannot run from the main thread.",
"Load it as a Worker from a separate Worker."); "Load it as a Worker from a separate Worker.");
}else if(!navigator.storage.getDirectory){ }else if(!navigator.storage.getDirectory){
toss("This API requires navigator.storage.getDirectory."); toss("This API requires navigator.storage.getDirectory.");
} }
/** /**
Will hold state copied to this object from the syncronous side of Will hold state copied to this object from the syncronous side of
this API. this API.
*/ */
const state = Object.create(null); const state = Object.create(null);
/** /**
verbose: verbose:
0 = no logging output 0 = no logging output
1 = only errors 1 = only errors
2 = warnings and errors 2 = warnings and errors
3 = debug, warnings, and errors 3 = debug, warnings, and errors
*/ */
state.verbose = 1; state.verbose = 1;
const loggers = { const loggers = {
0:console.error.bind(console), 0:console.error.bind(console),
1:console.warn.bind(console), 1:console.warn.bind(console),
2:console.log.bind(console) 2:console.log.bind(console)
}; };
const logImpl = (level,...args)=>{ const logImpl = (level,...args)=>{
if(state.verbose>level) loggers[level]("OPFS asyncer:",...args); if(state.verbose>level) loggers[level]("OPFS asyncer:",...args);
}; };
const log = (...args)=>logImpl(2, ...args); const log = (...args)=>logImpl(2, ...args);
const warn = (...args)=>logImpl(1, ...args); const warn = (...args)=>logImpl(1, ...args);
const error = (...args)=>logImpl(0, ...args); const error = (...args)=>logImpl(0, ...args);
const metrics = Object.create(null); const metrics = Object.create(null);
metrics.reset = ()=>{ metrics.reset = ()=>{
let k; let k;
const r = (m)=>(m.count = m.time = m.wait = 0); const r = (m)=>(m.count = m.time = m.wait = 0);
for(k in state.opIds){ for(k in state.opIds){
@ -94,8 +96,8 @@ metrics.reset = ()=>{
s.count = s.time = 0; s.count = s.time = 0;
s = metrics.s11n.deserialize = Object.create(null); s = metrics.s11n.deserialize = Object.create(null);
s.count = s.time = 0; s.count = s.time = 0;
}; };
metrics.dump = ()=>{ metrics.dump = ()=>{
let k, n = 0, t = 0, w = 0; let k, n = 0, t = 0, w = 0;
for(k in state.opIds){ for(k in state.opIds){
const m = metrics[k]; const m = metrics[k];
@ -110,17 +112,17 @@ metrics.dump = ()=>{
"\nTotal of",n,"op(s) for",t,"ms", "\nTotal of",n,"op(s) for",t,"ms",
"approx",w,"ms spent waiting on OPFS APIs."); "approx",w,"ms spent waiting on OPFS APIs.");
console.log("Serialization metrics:",metrics.s11n); console.log("Serialization metrics:",metrics.s11n);
}; };
/** /**
__openFiles is a map of sqlite3_file pointers (integers) to __openFiles is a map of sqlite3_file pointers (integers) to
metadata related to a given OPFS file handles. The pointers are, in metadata related to a given OPFS file handles. The pointers are, in
this side of the interface, opaque file handle IDs provided by the this side of the interface, opaque file handle IDs provided by the
synchronous part of this constellation. Each value is an object synchronous part of this constellation. Each value is an object
with a structure demonstrated in the xOpen() impl. with a structure demonstrated in the xOpen() impl.
*/ */
const __openFiles = Object.create(null); const __openFiles = Object.create(null);
/** /**
__implicitLocks is a Set of sqlite3_file pointers (integers) which were __implicitLocks is a Set of sqlite3_file pointers (integers) which were
"auto-locked". i.e. those for which we obtained a sync access "auto-locked". i.e. those for which we obtained a sync access
handle without an explicit xLock() call. Such locks will be handle without an explicit xLock() call. Such locks will be
@ -132,29 +134,29 @@ const __openFiles = Object.create(null);
operation which acquires them, we pay a massive performance operation which acquires them, we pay a massive performance
penalty: speedtest1 benchmarks take up to 4x as long. By delaying penalty: speedtest1 benchmarks take up to 4x as long. By delaying
the lock release until idle time, the hit is negligible. the lock release until idle time, the hit is negligible.
*/ */
const __implicitLocks = new Set(); const __implicitLocks = new Set();
/** /**
Expects an OPFS file path. It gets resolved, such that ".." Expects an OPFS file path. It gets resolved, such that ".."
components are properly expanded, and returned. If the 2nd arg is components are properly expanded, and returned. If the 2nd arg is
true, the result is returned as an array of path elements, else an true, the result is returned as an array of path elements, else an
absolute path string is returned. absolute path string is returned.
*/ */
const getResolvedPath = function(filename,splitIt){ const getResolvedPath = function(filename,splitIt){
const p = new URL( const p = new URL(
filename, 'file://irrelevant' filename, 'file://irrelevant'
).pathname; ).pathname;
return splitIt ? p.split('/').filter((v)=>!!v) : p; return splitIt ? p.split('/').filter((v)=>!!v) : p;
}; };
/** /**
Takes the absolute path to a filesystem element. Returns an array Takes the absolute path to a filesystem element. Returns an array
of [handleOfContainingDir, filename]. If the 2nd argument is truthy of [handleOfContainingDir, filename]. If the 2nd argument is truthy
then each directory element leading to the file is created along then each directory element leading to the file is created along
the way. Throws if any creation or resolution fails. the way. Throws if any creation or resolution fails.
*/ */
const getDirForFilename = async function f(absFilename, createDirs = false){ const getDirForFilename = async function f(absFilename, createDirs = false){
const path = getResolvedPath(absFilename, true); const path = getResolvedPath(absFilename, true);
const filename = path.pop(); const filename = path.pop();
let dh = state.rootDir; let dh = state.rootDir;
@ -164,9 +166,9 @@ const getDirForFilename = async function f(absFilename, createDirs = false){
} }
} }
return [dh, filename]; return [dh, filename];
}; };
/** /**
If the given file-holding object has a sync handle attached to it, If the given file-holding object has a sync handle attached to it,
that handle is remove and asynchronously closed. Though it may that handle is remove and asynchronously closed. Though it may
sound sensible to continue work as soon as the close() returns sound sensible to continue work as soon as the close() returns
@ -175,8 +177,8 @@ const getDirForFilename = async function f(absFilename, createDirs = false){
because they may happen out of order from the close(). OPFS does because they may happen out of order from the close(). OPFS does
not guaranty that the actual order of operations is retained in not guaranty that the actual order of operations is retained in
such cases. i.e. always "await" on the result of this function. such cases. i.e. always "await" on the result of this function.
*/ */
const closeSyncHandle = async (fh)=>{ const closeSyncHandle = async (fh)=>{
if(fh.syncHandle){ if(fh.syncHandle){
log("Closing sync handle for",fh.filenameAbs); log("Closing sync handle for",fh.filenameAbs);
const h = fh.syncHandle; const h = fh.syncHandle;
@ -185,9 +187,9 @@ const closeSyncHandle = async (fh)=>{
__implicitLocks.delete(fh.fid); __implicitLocks.delete(fh.fid);
return h.close(); return h.close();
} }
}; };
/** /**
A proxy for closeSyncHandle() which is guaranteed to not throw. A proxy for closeSyncHandle() which is guaranteed to not throw.
This function is part of a lock/unlock step in functions which This function is part of a lock/unlock step in functions which
@ -197,16 +199,16 @@ const closeSyncHandle = async (fh)=>{
_attempt_ at reducing cross-tab contention but it may prove _attempt_ at reducing cross-tab contention but it may prove
to be more of a problem than a solution and may need to be to be more of a problem than a solution and may need to be
removed. removed.
*/ */
const closeSyncHandleNoThrow = async (fh)=>{ const closeSyncHandleNoThrow = async (fh)=>{
try{await closeSyncHandle(fh)} try{await closeSyncHandle(fh)}
catch(e){ catch(e){
warn("closeSyncHandleNoThrow() ignoring:",e,fh); warn("closeSyncHandleNoThrow() ignoring:",e,fh);
} }
}; };
/* Release all auto-locks. */ /* Release all auto-locks. */
const releaseImplicitLocks = async ()=>{ const releaseImplicitLocks = async ()=>{
if(__implicitLocks.size){ if(__implicitLocks.size){
/* Release all auto-locks. */ /* Release all auto-locks. */
for(const fid of __implicitLocks){ for(const fid of __implicitLocks){
@ -215,30 +217,30 @@ const releaseImplicitLocks = async ()=>{
log("Auto-unlocked",fid,fh.filenameAbs); log("Auto-unlocked",fid,fh.filenameAbs);
} }
} }
}; };
/** /**
An experiment in improving concurrency by freeing up implicit locks An experiment in improving concurrency by freeing up implicit locks
sooner. This is known to impact performance dramatically but it has sooner. This is known to impact performance dramatically but it has
also shown to improve concurrency considerably. also shown to improve concurrency considerably.
If fh.releaseImplicitLocks is truthy and fh is in __implicitLocks, If fh.releaseImplicitLocks is truthy and fh is in __implicitLocks,
this routine returns closeSyncHandleNoThrow(), else it is a no-op. this routine returns closeSyncHandleNoThrow(), else it is a no-op.
*/ */
const releaseImplicitLock = async (fh)=>{ const releaseImplicitLock = async (fh)=>{
if(fh.releaseImplicitLocks && __implicitLocks.has(fh.fid)){ if(fh.releaseImplicitLocks && __implicitLocks.has(fh.fid)){
return closeSyncHandleNoThrow(fh); return closeSyncHandleNoThrow(fh);
} }
}; };
/** /**
An error class specifically for use with getSyncHandle(), the goal An error class specifically for use with getSyncHandle(), the goal
of which is to eventually be able to distinguish unambiguously of which is to eventually be able to distinguish unambiguously
between locking-related failures and other types, noting that we between locking-related failures and other types, noting that we
cannot currently do so because createSyncAccessHandle() does not cannot currently do so because createSyncAccessHandle() does not
define its exceptions in the required level of detail. define its exceptions in the required level of detail.
*/ */
class GetSyncHandleError extends Error { class GetSyncHandleError extends Error {
constructor(errorObject, ...msg){ constructor(errorObject, ...msg){
super(); super();
this.error = errorObject; this.error = errorObject;
@ -248,8 +250,8 @@ class GetSyncHandleError extends Error {
].join(' '); ].join(' ');
this.name = 'GetSyncHandleError'; this.name = 'GetSyncHandleError';
} }
}; };
GetSyncHandleError.convertRc = (e,rc)=>{ GetSyncHandleError.convertRc = (e,rc)=>{
if(0){ if(0){
/* This approach makes the very wild assumption that such a /* This approach makes the very wild assumption that such a
failure _is_ a locking error. In practice that appears to be failure _is_ a locking error. In practice that appears to be
@ -264,8 +266,8 @@ GetSyncHandleError.convertRc = (e,rc)=>{
}else{ }else{
return rc; return rc;
} }
} }
/** /**
Returns the sync access handle associated with the given file Returns the sync access handle associated with the given file
handle object (which must be a valid handle object, as created by handle object (which must be a valid handle object, as created by
xOpen()), lazily opening it if needed. xOpen()), lazily opening it if needed.
@ -275,8 +277,8 @@ GetSyncHandleError.convertRc = (e,rc)=>{
will wait briefly and try again, up to 3 times. If acquisition will wait briefly and try again, up to 3 times. If acquisition
still fails at that point it will give up and propagate the still fails at that point it will give up and propagate the
exception. exception.
*/ */
const getSyncHandle = async (fh,opName)=>{ const getSyncHandle = async (fh,opName)=>{
if(!fh.syncHandle){ if(!fh.syncHandle){
const t = performance.now(); const t = performance.now();
log("Acquiring sync handle for",fh.filenameAbs); log("Acquiring sync handle for",fh.filenameAbs);
@ -310,25 +312,25 @@ const getSyncHandle = async (fh,opName)=>{
} }
} }
return fh.syncHandle; return fh.syncHandle;
}; };
/** /**
Stores the given value at state.sabOPView[state.opIds.rc] and then Stores the given value at state.sabOPView[state.opIds.rc] and then
Atomics.notify()'s it. Atomics.notify()'s it.
*/ */
const storeAndNotify = (opName, value)=>{ const storeAndNotify = (opName, value)=>{
log(opName+"() => notify(",value,")"); log(opName+"() => notify(",value,")");
Atomics.store(state.sabOPView, state.opIds.rc, value); Atomics.store(state.sabOPView, state.opIds.rc, value);
Atomics.notify(state.sabOPView, state.opIds.rc); Atomics.notify(state.sabOPView, state.opIds.rc);
}; };
/** /**
Throws if fh is a file-holding object which is flagged as read-only. Throws if fh is a file-holding object which is flagged as read-only.
*/ */
const affirmNotRO = function(opName,fh){ const affirmNotRO = function(opName,fh){
if(fh.readOnly) toss(opName+"(): File is read-only: "+fh.filenameAbs); if(fh.readOnly) toss(opName+"(): File is read-only: "+fh.filenameAbs);
}; };
const affirmLocked = function(opName,fh){ const affirmLocked = function(opName,fh){
//if(!fh.syncHandle) toss(opName+"(): File does not have a lock: "+fh.filenameAbs); //if(!fh.syncHandle) toss(opName+"(): File does not have a lock: "+fh.filenameAbs);
/** /**
Currently a no-op, as speedtest1 triggers xRead() without a Currently a no-op, as speedtest1 triggers xRead() without a
@ -337,54 +339,54 @@ const affirmLocked = function(opName,fh){
acquisition of a lock but never let it go until xUnlock() is acquisition of a lock but never let it go until xUnlock() is
called (which it likely won't be if xLock() was not called). called (which it likely won't be if xLock() was not called).
*/ */
}; };
/** /**
We track 2 different timers: the "metrics" timer records how much 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 performing work. The "wait" timer records how much
time we spend waiting on the underlying OPFS timer. See the calls time we spend waiting on the underlying OPFS timer. See the calls
to mTimeStart(), mTimeEnd(), wTimeStart(), and wTimeEnd() to mTimeStart(), mTimeEnd(), wTimeStart(), and wTimeEnd()
throughout this file to see how they're used. throughout this file to see how they're used.
*/ */
const __mTimer = Object.create(null); const __mTimer = Object.create(null);
__mTimer.op = undefined; __mTimer.op = undefined;
__mTimer.start = undefined; __mTimer.start = undefined;
const mTimeStart = (op)=>{ const mTimeStart = (op)=>{
__mTimer.start = performance.now(); __mTimer.start = performance.now();
__mTimer.op = op; __mTimer.op = op;
//metrics[op] || toss("Maintenance required: missing metrics for",op); //metrics[op] || toss("Maintenance required: missing metrics for",op);
++metrics[op].count; ++metrics[op].count;
}; };
const mTimeEnd = ()=>( const mTimeEnd = ()=>(
metrics[__mTimer.op].time += performance.now() - __mTimer.start metrics[__mTimer.op].time += performance.now() - __mTimer.start
); );
const __wTimer = Object.create(null); const __wTimer = Object.create(null);
__wTimer.op = undefined; __wTimer.op = undefined;
__wTimer.start = undefined; __wTimer.start = undefined;
const wTimeStart = (op)=>{ const wTimeStart = (op)=>{
__wTimer.start = performance.now(); __wTimer.start = performance.now();
__wTimer.op = op; __wTimer.op = op;
//metrics[op] || toss("Maintenance required: missing metrics for",op); //metrics[op] || toss("Maintenance required: missing metrics for",op);
}; };
const wTimeEnd = ()=>( const wTimeEnd = ()=>(
metrics[__wTimer.op].wait += performance.now() - __wTimer.start metrics[__wTimer.op].wait += performance.now() - __wTimer.start
); );
/** /**
Gets set to true by the 'opfs-async-shutdown' command to quit the Gets set to true by the 'opfs-async-shutdown' command to quit the
wait loop. This is only intended for debugging purposes: we cannot wait loop. This is only intended for debugging purposes: we cannot
inspect this file's state while the tight waitLoop() is running and inspect this file's state while the tight waitLoop() is running and
need a way to stop that loop for introspection purposes. need a way to stop that loop for introspection purposes.
*/ */
let flagAsyncShutdown = false; let flagAsyncShutdown = false;
/** /**
Asynchronous wrappers for sqlite3_vfs and sqlite3_io_methods Asynchronous wrappers for sqlite3_vfs and sqlite3_io_methods
methods, as well as helpers like mkdir(). Maintenance reminder: methods, as well as helpers like mkdir(). Maintenance reminder:
members are in alphabetical order to simplify finding them. members are in alphabetical order to simplify finding them.
*/ */
const vfsAsyncImpls = { const vfsAsyncImpls = {
'opfs-async-metrics': async ()=>{ 'opfs-async-metrics': async ()=>{
mTimeStart('opfs-async-metrics'); mTimeStart('opfs-async-metrics');
metrics.dump(); metrics.dump();
@ -700,9 +702,9 @@ const vfsAsyncImpls = {
storeAndNotify('xWrite',rc); storeAndNotify('xWrite',rc);
mTimeEnd(); mTimeEnd();
} }
}/*vfsAsyncImpls*/; }/*vfsAsyncImpls*/;
const initS11n = ()=>{ const initS11n = ()=>{
/** /**
ACHTUNG: this code is 100% duplicated in the other half of this ACHTUNG: this code is 100% duplicated in the other half of this
proxy! The documentation is maintained in the "synchronous half". proxy! The documentation is maintained in the "synchronous half".
@ -806,9 +808,9 @@ const initS11n = ()=>{
: ()=>{}; : ()=>{};
return state.s11n; return state.s11n;
}/*initS11n()*/; }/*initS11n()*/;
const waitLoop = async function f(){ const waitLoop = async function f(){
const opHandlers = Object.create(null); const opHandlers = Object.create(null);
for(let k of Object.keys(state.opIds)){ for(let k of Object.keys(state.opIds)){
const vi = vfsAsyncImpls[k]; const vi = vfsAsyncImpls[k];
@ -848,10 +850,9 @@ const waitLoop = async function f(){
error('in waitLoop():',e); error('in waitLoop():',e);
} }
} }
}; };
navigator.storage.getDirectory().then(function(d){ navigator.storage.getDirectory().then(function(d){
const wMsg = (type)=>postMessage({type});
state.rootDir = d; state.rootDir = d;
self.onmessage = function({data}){ self.onmessage = function({data}){
switch(data.type){ switch(data.type){
@ -880,7 +881,7 @@ navigator.storage.getDirectory().then(function(d){
initS11n(); initS11n();
metrics.reset(); metrics.reset();
log("init state",state); log("init state",state);
wMsg('opfs-async-inited'); wPost('opfs-async-inited');
waitLoop(); waitLoop();
break; break;
} }
@ -896,5 +897,21 @@ navigator.storage.getDirectory().then(function(d){
break; break;
} }
}; };
wMsg('opfs-async-loaded'); wPost('opfs-async-loaded');
}).catch((e)=>error("error initializing OPFS asyncer:",e)); }).catch((e)=>error("error initializing OPFS asyncer:",e));
}/*installAsyncProxy()*/;
if(!self.SharedArrayBuffer){
wPost('opfs-unavailable', "Missing SharedArrayBuffer API.",
"The server must emit the COOP/COEP response headers to enable that.");
}else if(!self.Atomics){
wPost('opfs-unavailable', "Missing Atomics API.",
"The server must emit the COOP/COEP response headers to enable that.");
}else if(!self.FileSystemHandle ||
!self.FileSystemDirectoryHandle ||
!self.FileSystemFileHandle ||
!self.FileSystemFileHandle.prototype.createSyncAccessHandle ||
!navigator.storage.getDirectory){
wPost('opfs-unavailable',"Missing required OPFS APIs.");
}else{
installAsyncProxy(self);
}

View File

@ -1784,13 +1784,12 @@ self.sqlite3InitModule = sqlite3InitModule;
.t({ .t({
name: 'OPFS sanity checks', name: 'OPFS sanity checks',
test: async function(sqlite3){ test: async function(sqlite3){
const opfs = sqlite3.opfs;
const filename = 'sqlite3-tester1.db'; const filename = 'sqlite3-tester1.db';
const pVfs = capi.sqlite3_vfs_find('opfs'); const pVfs = capi.sqlite3_vfs_find('opfs');
T.assert(pVfs); T.assert(pVfs);
const unlink = (fn=filename)=>wasm.sqlite3_wasm_vfs_unlink(pVfs,fn); const unlink = (fn=filename)=>wasm.sqlite3_wasm_vfs_unlink(pVfs,fn);
unlink(); unlink();
let db = new opfs.OpfsDb(filename); let db = new sqlite3.oo1.OpfsDb(filename);
try { try {
db.exec([ db.exec([
'create table p(a);', 'create table p(a);',
@ -1798,7 +1797,7 @@ self.sqlite3InitModule = sqlite3InitModule;
]); ]);
T.assert(3 === db.selectValue('select count(*) from p')); T.assert(3 === db.selectValue('select count(*) from p'));
db.close(); db.close();
db = new opfs.OpfsDb(filename); db = new sqlite3.oo1.OpfsDb(filename);
db.exec('insert into p(a) values(4),(5),(6)'); db.exec('insert into p(a) values(4),(5),(6)');
T.assert(6 === db.selectValue('select count(*) from p')); T.assert(6 === db.selectValue('select count(*) from p'));
}finally{ }finally{
@ -1806,8 +1805,9 @@ self.sqlite3InitModule = sqlite3InitModule;
unlink(); unlink();
} }
if(1){ if(sqlite3.opfs){
// Sanity-test sqlite3_wasm_vfs_create_file()... // Sanity-test sqlite3_wasm_vfs_create_file()...
const opfs = sqlite3.opfs;
const fSize = 1379; const fSize = 1379;
let sh; let sh;
try{ try{
@ -1824,7 +1824,6 @@ self.sqlite3InitModule = sqlite3InitModule;
if(sh) await sh.close(); if(sh) await sh.close();
unlink(); unlink();
} }
}
// Some sanity checks of the opfs utility functions... // Some sanity checks of the opfs utility functions...
const testDir = '/sqlite3-opfs-'+opfs.randomFilename(12); const testDir = '/sqlite3-opfs-'+opfs.randomFilename(12);
@ -1839,6 +1838,7 @@ self.sqlite3InitModule = sqlite3InitModule;
.assert(!(await opfs.entryExists(testDir)), .assert(!(await opfs.entryExists(testDir)),
"entryExists(",testDir,") should have failed"); "entryExists(",testDir,") should have failed");
} }
}
}/*OPFS sanity checks*/) }/*OPFS sanity checks*/)
;/* end OPFS tests */ ;/* end OPFS tests */

View File

@ -33,9 +33,10 @@
with <code>unlock-asap=0-1</code>. with <code>unlock-asap=0-1</code>.
</p> </p>
<p>Achtung: if it does not start to do anything within a couple of <p>Achtung: if it does not start to do anything within a couple of
seconds, check the dev console: Chrome often fails with "cannot allocate seconds, check the dev console: Chrome sometimes fails to load
WasmMemory" at startup. Closing and re-opening the tab usually resolves the wasm module due to "cannot allocate WasmMemory." Closing and
it. re-opening the tab usually resolves it, but sometimes restarting
the browser is required.
</p> </p>
<div class='input-wrapper'> <div class='input-wrapper'>
<input type='checkbox' id='cb-log-reverse'> <input type='checkbox' id='cb-log-reverse'>

View File

@ -1,5 +1,5 @@
C Add\san\sexplicit\swarning\sabout\sthe\scurrent\sAPI-instability\sof\sthe\ssqlite3.opfs\snamespace,\swhich\smay\sneed\sto\sbe\seliminated\sbased\son\sre-thinking\sof\show\sthe\sOPFS\ssqlite3_vfs\sis\sregistered.\sComment\schanges\sonly\s-\sno\scode. C Internal\srestructuring\sof\sthe\sOPFS\ssqlite3_vfs\sin\sorder\sto\sfacilitate\scertain\sexperimentation\sand\simprove\serror\sreporting/hints\sif\sit\scannot\sbe\sactivated.\sDeprecate\sthe\sname\ssqlite3.opfs.OpfsDb,\spreferring\ssqlite3.oo1.OpfsDb\sfor\sconsistency\swith\sJsStorageDb\sand\sany\sfuture\sDB\ssubclasses.
D 2022-11-29T02:23:12.943 D 2022-11-29T05:25:08.036
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -505,11 +505,11 @@ F ext/wasm/api/pre-js.js b88499dc303c21fc3f55f2c364a0f814f587b60a95784303881169f
F ext/wasm/api/sqlite3-api-cleanup.js ecdc69dbfccfe26146f04799fcfd4a6f5790d46e7e3b9b6e9b0491f92ed8ae34 F ext/wasm/api/sqlite3-api-cleanup.js ecdc69dbfccfe26146f04799fcfd4a6f5790d46e7e3b9b6e9b0491f92ed8ae34
F ext/wasm/api/sqlite3-api-glue.js 056f44b82c126358a0175e08a892d56fadfce177b0d7a0012502a6acf67ea6d5 F ext/wasm/api/sqlite3-api-glue.js 056f44b82c126358a0175e08a892d56fadfce177b0d7a0012502a6acf67ea6d5
F ext/wasm/api/sqlite3-api-oo1.js 06ad2079368e16cb9f182c18cd37bdc3932536856dff4f60582d0ca5f6c491a8 F ext/wasm/api/sqlite3-api-oo1.js 06ad2079368e16cb9f182c18cd37bdc3932536856dff4f60582d0ca5f6c491a8
F ext/wasm/api/sqlite3-api-opfs.js 3cdae7e98c500f89f9468a260e2a0e1b528c845a107bf72d368e5222769214d3 F ext/wasm/api/sqlite3-api-opfs.js 583650ffdc1452496df6b9459d018fa2aede221ae6ea0cbbbe83bd2e1bdba966
F ext/wasm/api/sqlite3-api-prologue.js 7fce4c6a138ec3d7c285b7c125cee809e6b668d2cb0d2328a1b790b7037765bd F ext/wasm/api/sqlite3-api-prologue.js 7fce4c6a138ec3d7c285b7c125cee809e6b668d2cb0d2328a1b790b7037765bd
F ext/wasm/api/sqlite3-api-worker1.js e94ba98e44afccfa482874cd9acb325883ade50ed1f9f9526beb9de1711f182f F ext/wasm/api/sqlite3-api-worker1.js e94ba98e44afccfa482874cd9acb325883ade50ed1f9f9526beb9de1711f182f
F ext/wasm/api/sqlite3-license-version-header.js a661182fc93fc2cf212dfd0b987f8e138a3ac98f850b1112e29b5fbdaecc87c3 F ext/wasm/api/sqlite3-license-version-header.js a661182fc93fc2cf212dfd0b987f8e138a3ac98f850b1112e29b5fbdaecc87c3
F ext/wasm/api/sqlite3-opfs-async-proxy.js 798383f6b46fd5dac122d6e35962d25b10401ddb825b5c66df1d21e6b1d8aacc F ext/wasm/api/sqlite3-opfs-async-proxy.js b5dd7eda8e74e07453457925a0dd793d7785da720954e0e37e847c5c6e4d9526
F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9 F ext/wasm/api/sqlite3-wasi.h 25356084cfe0d40458a902afb465df8c21fc4152c1d0a59b563a3fba59a068f9
F ext/wasm/api/sqlite3-wasm.c 8b32787a3b6bb2990cbaba2304bd5b75a9652acbc8d29909b3279019b6cbaef5 F ext/wasm/api/sqlite3-wasm.c 8b32787a3b6bb2990cbaba2304bd5b75a9652acbc8d29909b3279019b6cbaef5
F ext/wasm/api/sqlite3-worker1-promiser.js 0c7a9826dbf82a5ed4e4f7bf7816e825a52aff253afbf3350431f5773faf0e4b F ext/wasm/api/sqlite3-worker1-promiser.js 0c7a9826dbf82a5ed4e4f7bf7816e825a52aff253afbf3350431f5773faf0e4b
@ -554,8 +554,8 @@ F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555
F ext/wasm/test-opfs-vfs.js 44363db07b2a20e73b0eb1808de4400ca71b703af718d0fa6d962f15e73bf2ac F ext/wasm/test-opfs-vfs.js 44363db07b2a20e73b0eb1808de4400ca71b703af718d0fa6d962f15e73bf2ac
F ext/wasm/tester1-worker.html 5ef353348c37cf2e4fd0b23da562d3275523e036260b510734e9a3239ba8c987 F ext/wasm/tester1-worker.html 5ef353348c37cf2e4fd0b23da562d3275523e036260b510734e9a3239ba8c987
F ext/wasm/tester1.c-pp.html 74aa9b31c75f12490653f814b53c3dd39f40cd3f70d6a53a716f4e8587107399 F ext/wasm/tester1.c-pp.html 74aa9b31c75f12490653f814b53c3dd39f40cd3f70d6a53a716f4e8587107399
F ext/wasm/tester1.c-pp.js 3b91f192c159088004fba6fe3441edea58421a8b88bccf3dd20978a077648d19 F ext/wasm/tester1.c-pp.js a4b6a165aafcd3b86118efaec6b47c70fbb6c64b5ab86d21ca8c250d42617dfa
F ext/wasm/tests/opfs/concurrency/index.html e8fec75ea6eddc600c8a382da7ea2579feece2263a2fb4417f2cf3e9d451744c F ext/wasm/tests/opfs/concurrency/index.html 2b1cda51d6c786102875a28eba22f0da3eecb732a5e677b0d1ecdb53546d1a62
F ext/wasm/tests/opfs/concurrency/test.js bfc3d7e27b207f0827f12568986b8d516a744529550b449314f5c21c9e9faf4a F ext/wasm/tests/opfs/concurrency/test.js bfc3d7e27b207f0827f12568986b8d516a744529550b449314f5c21c9e9faf4a
F ext/wasm/tests/opfs/concurrency/worker.js 0eff027cbd3a495acb2ac94f57ca9e4d21125ab9fda07d45f3701b0efe82d450 F ext/wasm/tests/opfs/concurrency/worker.js 0eff027cbd3a495acb2ac94f57ca9e4d21125ab9fda07d45f3701b0efe82d450
F ext/wasm/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd72273503ae7d5 F ext/wasm/version-info.c 3b36468a90faf1bbd59c65fd0eb66522d9f941eedd364fabccd72273503ae7d5
@ -2064,8 +2064,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P 46cdd3637d6a206ad2bcf8653cc6f2c7a886a16cc7685c45967938609941a755 P 0cb2fd14179397051a25d066256a553fc198656d5668c7010c016f2b8f495bf4
R 3870d04bfd54da096e986662fe29b1c8 R 115b7898d7b2ce79a9261f36a9b959d1
U stephan U stephan
Z ad4a8c5f45a34a10f588c0c6dc455846 Z f4ff31d5e2499971cf67cb62dbdd0ac3
# Remove this line to create a well-formed Fossil manifest. # Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
0cb2fd14179397051a25d066256a553fc198656d5668c7010c016f2b8f495bf4 0c5c51f4fb04a4b90c50ec9704cfea9a3fb7d7d0ee55c1b0d4476129188217a6