]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Merge kv-vfs branch into fiddle-opfs branch for [21915af560b1|synchronous=off fix...
authorstephan <stephan@noemail.net>
Fri, 16 Sep 2022 11:45:09 +0000 (11:45 +0000)
committerstephan <stephan@noemail.net>
Fri, 16 Sep 2022 11:45:09 +0000 (11:45 +0000)
FossilOrigin-Name: 13899bb98c80525276d2484598b94e4206358f243f06d45c02700024f7e226fd

1  2 
ext/wasm/scratchpad-opfs-worker2.js
ext/wasm/speedtest1-kvvfs.html
manifest
manifest.uuid

index 64e5266db2499c5e5a13c577e9c194e296f44f4a,0000000000000000000000000000000000000000..47ace63de468a4249221ad4308c09dfd497b3072
mode 100644,000000..100644
--- /dev/null
@@@ -1,493 -1,0 +1,494 @@@
-         'FileSystemHandle', 'FileSystemFileHandle', 'FileSystemDirectoryHandle'
 +/*
 +  2022-05-22
 +
 +  The author disclaims copyright to this source code.  In place of a
 +  legal notice, here is a blessing:
 +
 +  *   May you do good and not evil.
 +  *   May you find forgiveness for yourself and forgive others.
 +  *   May you share freely, never taking more than you give.
 +
 +  ***********************************************************************
 +
 +  An experiment for wasmfs/opfs. This file MUST be in the same dir as
 +  the sqlite3.js emscripten module or that module won't be able to
 +  resolve the relative URIs (importScript()'s relative URI handling
 +  is, quite frankly, broken).
 +*/
 +'use strict';
 +
 +const toss = function(...args){throw new Error(args.join(' '))};
 +/**
 +   Posts a message in the form {type,data} unless passed more than 2
 +   args, in which case it posts {type, data:[arg1...argN]}.
 +*/
 +const wMsg = function(type,data){
 +  postMessage({
 +    type,
 +    data: arguments.length<3
 +      ? data
 +      : Array.prototype.slice.call(arguments,1)
 +  });
 +};
 +
 +const stdout = function(...args){
 +  wMsg('stdout',args);
 +  console.log(...args);
 +};
 +const stderr = function(...args){
 +  wMsg('stderr',args);
 +  console.error(...args);
 +};
 +
 +const log = console.log.bind(console);
 +const warn = console.warn.bind(console);
 +const error = console.error.bind(console);
 +
 +
 +const initOpfsBits = async function(sqlite3){
 +  if(!self.importScripts || !self.FileSystemFileHandle){
 +    //|| !self.FileSystemFileHandle.prototype.createSyncAccessHandle){
 +    // ^^^ sync API is not required with WASMFS/OPFS backend.
 +    warn("OPFS is not available in this environment.");
 +    return;
 +  }else if(!sqlite3.capi.wasm.bigIntEnabled){
 +    error("OPFS requires BigInt support but sqlite3.capi.wasm.bigIntEnabled is false.");
 +    return;
 +  }
 +  //warn('self.FileSystemFileHandle =',self.FileSystemFileHandle);
 +  //warn('self.FileSystemFileHandle.prototype =',self.FileSystemFileHandle.prototype);
 +  const capi = sqlite3.capi,
 +        wasm = capi.wasm;
 +  const sqlite3_vfs = capi.sqlite3_vfs
 +        || toss("Missing sqlite3.capi.sqlite3_vfs object.");
 +  const sqlite3_file = capi.sqlite3_file
 +        || toss("Missing sqlite3.capi.sqlite3_file object.");
 +  const sqlite3_io_methods = capi.sqlite3_io_methods
 +        || toss("Missing sqlite3.capi.sqlite3_io_methods object.");
 +  const StructBinder = sqlite3.StructBinder || toss("Missing sqlite3.StructBinder.");
 +  const debug = console.debug.bind(console),
 +        log = console.log.bind(console);
 +  warn("UNDER CONSTRUCTION: setting up OPFS VFS...");
 +
 +  const pDVfs = capi.sqlite3_vfs_find(null)/*pointer to default VFS*/;
 +  const dVfs = pDVfs
 +        ? new sqlite3_vfs(pDVfs)
 +        : null /* dVfs will be null when sqlite3 is built with
 +                  SQLITE_OS_OTHER. Though we cannot currently handle
 +                  that case, the hope is to eventually be able to. */;
 +  const oVfs = new sqlite3_vfs();
 +  const oIom = new sqlite3_io_methods();
 +  oVfs.$iVersion = 2/*yes, two*/;
 +  oVfs.$szOsFile = capi.sqlite3_file.structInfo.sizeof;
 +  oVfs.$mxPathname = 1024/*sure, why not?*/;
 +  oVfs.$zName = wasm.allocCString("opfs");
 +  oVfs.ondispose = [
 +    '$zName', oVfs.$zName,
 +    'cleanup dVfs', ()=>(dVfs ? dVfs.dispose() : null)
 +  ];
 +  if(dVfs){
 +    oVfs.$xSleep = dVfs.$xSleep;
 +    oVfs.$xRandomness = dVfs.$xRandomness;
 +  }
 +  // All C-side memory of oVfs is zeroed out, but just to be explicit:
 +  oVfs.$xDlOpen = oVfs.$xDlError = oVfs.$xDlSym = oVfs.$xDlClose = null;
 +
 +  /**
 +     Pedantic sidebar about oVfs.ondispose: the entries in that array
 +     are items to clean up when oVfs.dispose() is called, but in this
 +     environment it will never be called. The VFS instance simply
 +     hangs around until the WASM module instance is cleaned up. We
 +     "could" _hypothetically_ clean it up by "importing" an
 +     sqlite3_os_end() impl into the wasm build, but the shutdown order
 +     of the wasm engine and the JS one are undefined so there is no
 +     guaranty that the oVfs instance would be available in one
 +     environment or the other when sqlite3_os_end() is called (_if_ it
 +     gets called at all in a wasm build, which is undefined).
 +  */
 +
 +  /**
 +     Installs a StructBinder-bound function pointer member of the
 +     given name and function in the given StructType target object.
 +     It creates a WASM proxy for the given function and arranges for
 +     that proxy to be cleaned up when tgt.dispose() is called.  Throws
 +     on the slightest hint of error (e.g. tgt is-not-a StructType,
 +     name does not map to a struct-bound member, etc.).
 +
 +     Returns a proxy for this function which is bound to tgt and takes
 +     2 args (name,func). That function returns the same thing,
 +     permitting calls to be chained.
 +
 +     If called with only 1 arg, it has no side effects but returns a
 +     func with the same signature as described above.
 +  */
 +  const installMethod = function callee(tgt, name, func){
 +    if(!(tgt instanceof StructBinder.StructType)){
 +      toss("Usage error: target object is-not-a StructType.");
 +    }
 +    if(1===arguments.length){
 +      return (n,f)=>callee(tgt,n,f);
 +    }
 +    if(!callee.argcProxy){
 +      callee.argcProxy = function(func,sig){
 +        return function(...args){
 +          if(func.length!==arguments.length){
 +            toss("Argument mismatch. Native signature is:",sig);
 +          }
 +          return func.apply(this, args);
 +        }
 +      };
 +      callee.removeFuncList = function(){
 +        if(this.ondispose.__removeFuncList){
 +          this.ondispose.__removeFuncList.forEach(
 +            (v,ndx)=>{
 +              if('number'===typeof v){
 +                try{wasm.uninstallFunction(v)}
 +                catch(e){/*ignore*/}
 +              }
 +              /* else it's a descriptive label for the next number in
 +                 the list. */
 +            }
 +          );
 +          delete this.ondispose.__removeFuncList;
 +        }
 +      };
 +    }/*static init*/
 +    const sigN = tgt.memberSignature(name);
 +    if(sigN.length<2){
 +      toss("Member",name," is not a function pointer. Signature =",sigN);
 +    }
 +    const memKey = tgt.memberKey(name);
 +    //log("installMethod",tgt, name, sigN);
 +    const fProxy = 1
 +          // We can remove this proxy middle-man once the VFS is working
 +          ? callee.argcProxy(func, sigN)
 +          : func;
 +    const pFunc = wasm.installFunction(fProxy, tgt.memberSignature(name, true));
 +    tgt[memKey] = pFunc;
 +    if(!tgt.ondispose) tgt.ondispose = [];
 +    if(!tgt.ondispose.__removeFuncList){
 +      tgt.ondispose.push('ondispose.__removeFuncList handler',
 +                         callee.removeFuncList);
 +      tgt.ondispose.__removeFuncList = [];
 +    }
 +    tgt.ondispose.__removeFuncList.push(memKey, pFunc);
 +    return (n,f)=>callee(tgt, n, f);
 +  }/*installMethod*/;
 +
 +  /**
 +     Map of sqlite3_file pointers to OPFS handles.
 +  */
 +  const __opfsHandles = Object.create(null);
 +
 +  /**
 +     Generates a random ASCII string len characters long, intended for
 +     use as a temporary file name.
 +  */
 +  const randomFilename = function f(len=16){
 +    if(!f._chars){
 +      f._chars = "abcdefghijklmnopqrstuvwxyz"+
 +        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"+
 +        "012346789";
 +      f._n = f._chars.length;
 +    }
 +    const a = [];
 +    let i = 0;
 +    for( ; i < len; ++i){
 +      const ndx = Math.random() * (f._n * 64) % f._n | 0;
 +      a[i] = f._chars[ndx];
 +    }
 +    return a.join('');
 +  };
 +
 +  const rootDir = await navigator.storage.getDirectory();
 +  log("rootDir =",rootDir);
 +  
 +  ////////////////////////////////////////////////////////////////////////
 +  // Set up OPFS VFS methods...
 +  let inst = installMethod(oVfs);
 +  inst('xOpen', function(pVfs, zName, pFile, flags, pOutFlags){
 +    const f = new sqlite3_file(pFile);
 +    f.$pMethods = oIom.pointer;
 +    __opfsHandles[pFile] = f;
 +    f.opfsHandle = null /* TODO */;
 +    if(flags & capi.SQLITE_OPEN_DELETEONCLOSE){
 +      f.deleteOnClose = true;
 +    }
 +    f.filename = zName ? wasm.cstringToJs(zName) : 'sqlite3-xOpen-'+randomFilename();
 +    error("OPFS sqlite3_vfs::xOpen is not yet full implemented.");
 +    return capi.SQLITE_IOERR;
 +  })
 +  ('xFullPathname', function(pVfs,zName,nOut,pOut){
 +    /* Until/unless we have some notion of "current dir"
 +       in OPFS, simply copy zName to pOut... */
 +    const i = wasm.cstrncpy(pOut, zName, nOut);
 +    return i<nOut ? 0 : capi.SQLITE_CANTOPEN
 +    /*CANTOPEN is required by the docs but SQLITE_RANGE would be a closer match*/;
 +  })
 +  ('xAccess', function(pVfs,zName,flags,pOut){
 +    error("OPFS sqlite3_vfs::xAccess is not yet implemented.");
 +    let fileExists = 0;
 +    switch(flags){
 +        case capi.SQLITE_ACCESS_EXISTS: break;
 +        case capi.SQLITE_ACCESS_READWRITE: break;
 +        case capi.SQLITE_ACCESS_READ/*docs say this is never used*/:
 +        default:
 +          error("Unexpected flags value for sqlite3_vfs::xAccess():",flags);
 +          return capi.SQLITE_MISUSE;
 +    }
 +    wasm.setMemValue(pOut, fileExists, 'i32');
 +    return 0;
 +  })
 +  ('xDelete', function(pVfs, zName, doSyncDir){
 +    error("OPFS sqlite3_vfs::xDelete is not yet implemented.");
 +    // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/file_system_access/file_system_handle.idl
 +    // ==> remove()
 +    return capi.SQLITE_IOERR;
 +  })
 +  ('xGetLastError', function(pVfs,nOut,pOut){
 +    debug("OPFS sqlite3_vfs::xGetLastError() has nothing sensible to return.");
 +    return 0;
 +  })
 +  ('xCurrentTime', function(pVfs,pOut){
 +    /* If it turns out that we need to adjust for timezone, see:
 +       https://stackoverflow.com/a/11760121/1458521 */
 +    wasm.setMemValue(pOut, 2440587.5 + (new Date().getTime()/86400000),
 +                     'double');
 +    return 0;
 +  })
 +  ('xCurrentTimeInt64',function(pVfs,pOut){
 +    // TODO: confirm that this calculation is correct
 +    wasm.setMemValue(pOut, (2440587.5 * 86400000) + new Date().getTime(),
 +                     'i64');
 +    return 0;
 +  });
 +  if(!oVfs.$xSleep){
 +    inst('xSleep', function(pVfs,ms){
 +      error("sqlite3_vfs::xSleep(",ms,") cannot be implemented from "+
 +           "JS and we have no default VFS to copy the impl from.");
 +      return 0;
 +    });
 +  }
 +  if(!oVfs.$xRandomness){
 +    inst('xRandomness', function(pVfs, nOut, pOut){
 +      const heap = wasm.heap8u();
 +      let i = 0;
 +      for(; i < nOut; ++i) heap[pOut + i] = (Math.random()*255000) & 0xFF;
 +      return i;
 +    });
 +  }
 +
 +  ////////////////////////////////////////////////////////////////////////
 +  // Set up OPFS sqlite3_io_methods...
 +  inst = installMethod(oIom);
 +  inst('xClose', async function(pFile){
 +    warn("xClose(",arguments,") uses await");
 +    const f = __opfsHandles[pFile];
 +    delete __opfsHandles[pFile];
 +    if(f.opfsHandle){
 +      await f.opfsHandle.close();
 +      if(f.deleteOnClose){
 +        // TODO
 +      }
 +    }
 +    f.dispose();
 +    return 0;
 +  })
 +  ('xRead', /*i(ppij)*/function(pFile,pDest,n,offset){
 +    /* int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst) */
 +    try {
 +      const f = __opfsHandles[pFile];
 +      const heap = wasm.heap8u();
 +      const b = new Uint8Array(heap.buffer, pDest, n);
 +      const nRead = f.opfsHandle.read(b, {at: offset});
 +      if(nRead<n){
 +        // MUST zero-fill short reads (per the docs)
 +        heap.fill(0, dest + nRead, n - nRead);
 +      }
 +      return 0;
 +    }catch(e){
 +      error("xRead(",arguments,") failed:",e);
 +      return capi.SQLITE_IOERR_READ;
 +    }
 +  })
 +  ('xWrite', /*i(ppij)*/function(pFile,pSrc,n,offset){
 +    /* int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst) */
 +    try {
 +      const f = __opfsHandles[pFile];
 +      const b = new Uint8Array(wasm.heap8u().buffer, pSrc, n);
 +      const nOut = f.opfsHandle.write(b, {at: offset});
 +      if(nOut<n){
 +        error("xWrite(",arguments,") short write!");
 +        return capi.SQLITE_IOERR_WRITE;
 +      }
 +      return 0;
 +    }catch(e){
 +      error("xWrite(",arguments,") failed:",e);
 +      return capi.SQLITE_IOERR_WRITE;
 +    }
 +  })
 +  ('xTruncate', /*i(pj)*/async function(pFile,sz){
 +    /* int (*xTruncate)(sqlite3_file*, sqlite3_int64 size) */
 +    try{
 +      warn("xTruncate(",arguments,") uses await");
 +      const f = __opfsHandles[pFile];
 +      await f.opfsHandle.truncate(sz);
 +      return 0;
 +    }
 +    catch(e){
 +      error("xTruncate(",arguments,") failed:",e);
 +      return capi.SQLITE_IOERR_TRUNCATE;
 +    }
 +  })
 +  ('xSync', /*i(pi)*/async function(pFile,flags){
 +    /* int (*xSync)(sqlite3_file*, int flags) */
 +    try {
 +      warn("xSync(",arguments,") uses await");
 +      const f = __opfsHandles[pFile];
 +      await f.opfsHandle.flush();
 +      return 0;
 +    }catch(e){
 +      error("xSync(",arguments,") failed:",e);
 +      return capi.SQLITE_IOERR_SYNC;
 +    }
 +  })
 +  ('xFileSize', /*i(pp)*/async function(pFile,pSz){
 +    /* int (*xFileSize)(sqlite3_file*, sqlite3_int64 *pSize) */
 +    try {
 +      warn("xFileSize(",arguments,") uses await");
 +      const f = __opfsHandles[pFile];
 +      const fsz = await f.opfsHandle.getSize();
 +      capi.wasm.setMemValue(pSz, fsz,'i64');
 +      return 0;
 +    }catch(e){
 +      error("xFileSize(",arguments,") failed:",e);
 +      return capi.SQLITE_IOERR_SEEK;
 +    }
 +  })
 +  ('xLock', /*i(pi)*/function(pFile,lockType){
 +    /* int (*xLock)(sqlite3_file*, int) */
 +    // Opening a handle locks it automatically.
 +    warn("xLock(",arguments,") is a no-op");
 +    return 0;
 +  })
 +  ('xUnlock', /*i(pi)*/function(pFile,lockType){
 +    /* int (*xUnlock)(sqlite3_file*, int) */
 +    // Opening a handle locks it automatically.
 +    warn("xUnlock(",arguments,") is a no-op");
 +    return 0;
 +  })
 +  ('xCheckReservedLock', /*i(pp)*/function(pFile,pOut){
 +    /* int (*xCheckReservedLock)(sqlite3_file*, int *pResOut) */
 +    // Exclusive lock is automatically acquired when opened
 +    warn("xCheckReservedLock(",arguments,") is a no-op");
 +    wasm.setMemValue(pOut,1,'i32');
 +    return 0;
 +  })
 +  ('xFileControl', /*i(pip)*/function(pFile,op,pArg){
 +    /* int (*xFileControl)(sqlite3_file*, int op, void *pArg) */
 +    debug("xFileControl(",arguments,") is a no-op");
 +    return capi.SQLITE_NOTFOUND;
 +  })
 +  ('xDeviceCharacteristics',/*i(p)*/function(pFile){
 +    /* int (*xDeviceCharacteristics)(sqlite3_file*) */
 +    debug("xDeviceCharacteristics(",pFile,")");
 +    return capi.SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN;
 +  });
 +  // xSectorSize may be NULL
 +  //('xSectorSize', function(pFile){
 +  //  /* int (*xSectorSize)(sqlite3_file*) */
 +  //  log("xSectorSize(",pFile,")");
 +  //  return 4096 /* ==> SQLITE_DEFAULT_SECTOR_SIZE */;
 +  //})
 +
 +  const rc = capi.sqlite3_vfs_register(oVfs.pointer, 0);
 +  if(rc){
 +    oVfs.dispose();
 +    toss("sqlite3_vfs_register(OPFS) failed with rc",rc);
 +  }
 +  capi.sqlite3_vfs_register.addReference(oVfs, oIom);
 +  warn("End of (very incomplete) OPFS setup.", oVfs);
 +  //oVfs.dispose()/*only because we can't yet do anything with it*/;
 +
 +}/*initOpfsBits()*/;
 +
 +(async function(){
 +  importScripts('sqlite3.js');
 +
 +  const test1 = function(db){
 +    db.exec("create table if not exists t(a);")
 +      .transaction(function(db){
 +        db.prepare("insert into t(a) values(?)")
 +          .bind(new Date().getTime())
 +          .stepFinalize();
 +        stdout("Number of values in table t:",
 +            db.selectValue("select count(*) from t"));
 +      });
 +  };
 +
 +  const runTests = async function(Module){
 +    //stdout("Module",Module);
 +    self._MODULE = Module /* this is only to facilitate testing from the console */;
 +    const sqlite3 = Module.sqlite3,
 +          capi = sqlite3.capi,
 +          oo = sqlite3.oo1,
 +          wasm = capi.wasm;
 +    stdout("Loaded sqlite3:",capi.sqlite3_libversion(), capi.sqlite3_sourceid());
 +
 +    if(1){
 +      let errCount = 0;
 +      [
 +        'FileSystemHandle', 'FileSystemFileHandle', 'FileSystemDirectoryHandle',
++        'FileSystemSyncAccessHandle'
 +      ].forEach(function(n){
 +        const f = self[n];
 +        if(f){
 +          warn(n,f);
 +          warn(n+'.prototype',f.prototype);
 +        }else{
 +          stderr("MISSING",n);
 +          ++errCount;
 +        }
 +      });
 +      if(errCount) return;
 +    }
++    warn('self',self);
 +    await initOpfsBits(sqlite3);
 +
 +    if(1) return;
 +    
 +    let persistentDir;
 +    if(1){
 +      persistentDir = '';
 +    }else{
 +      persistentDir = capi.sqlite3_web_persistent_dir();
 +      if(persistentDir){
 +        stderr("Persistent storage dir:",persistentDir);
 +      }else{
 +        stderr("No persistent storage available.");
 +        return;
 +      }
 +    }
 +    const startTime = performance.now();
 +    let db;
 +    try {
 +      db = new oo.DB(persistentDir+'/foo.db');
 +      stdout("DB filename:",db.filename,db.fileName());
 +      const banner1 = '>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>',
 +            banner2 = '<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<';
 +      [
 +        test1
 +      ].forEach((f)=>{
 +        const n = performance.now();
 +        stdout(banner1,"Running",f.name+"()...");
 +        f(db, sqlite3, Module);
 +        stdout(banner2,f.name+"() took ",(performance.now() - n),"ms");
 +      });
 +    }finally{
 +      if(db) db.close();
 +    }
 +    stdout("Total test time:",(performance.now() - startTime),"ms");
 +  };
 +
 +  sqlite3InitModule(self.sqlite3TestModule).then(runTests);
 +})();
index 080f7e4e18a558c1a620bd520ecbd9aa54298c16,0000000000000000000000000000000000000000..9bb387643b038bc9822aa33a895ccbcb4eb4bdfe
mode 100644,000000..100644
--- /dev/null
@@@ -1,159 -1,0 +1,156 @@@
-           if(argv.indexOf('--nosync')>=0){
-               log2('error',"WARNING: --nosync flag is known to cause this test to fail.");
-           }
 +<!doctype html>
 +<html lang="en-us">
 +  <head>
 +    <meta charset="utf-8">
 +    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
 +    <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon">
 +    <link rel="stylesheet" href="common/emscripten.css"/>
 +    <link rel="stylesheet" href="common/testing.css"/>
 +    <title>speedtest1-kvvfs.wasm</title>
 +  </head>
 +  <body>
 +    <header id='titlebar'><span>speedtest1-kvvfs.wasm</span></header>
 +    <div>See also: <a href='speedtest1-worker.html'>A Worker-thread variant of this page.</a></div>
 +    <!-- emscripten bits -->
 +    <figure id="module-spinner">
 +      <div class="spinner"></div>
 +      <div class='center'><strong>Initializing app...</strong></div>
 +      <div class='center'>
 +        On a slow internet connection this may take a moment.  If this
 +        message displays for "a long time", intialization may have
 +        failed and the JavaScript console may contain clues as to why.
 +      </div>
 +    </figure>
 +    <div class="emscripten" id="module-status">Downloading...</div>
 +    <div class="emscripten">
 +      <progress value="0" max="100" id="module-progress" hidden='1'></progress>  
 +    </div><!-- /emscripten bits -->
 +    <div class='warning'>This page starts running the main exe when it loads, which will
 +      block the UI until it finishes! Adding UI controls to manually configure and start it
 +      are TODO.</div>
 +    </div>
 +    <div class='warning'>Achtung: running it with the dev tools open may
 +      <em>drastically</em> slow it down. For faster results, keep the dev
 +      tools closed when running it!
 +    </div>
 +    <div>Output is delayed/buffered because we cannot update the UI while the
 +      speedtest is running. Output will appear below when ready...
 +    <div id='test-output'></div>
 +    <script src="common/whwasmutil.js"></script>
 +    <script src="common/SqliteTestUtil.js"></script>
 +    <script src="speedtest1-kvvfs.js"></script>
 +    <script>(function(){
 +        const eOut = document.querySelector('#test-output');
 +        const log2 = function(cssClass,...args){
 +          const ln = document.createElement('div');
 +          if(cssClass) ln.classList.add(cssClass);
 +          ln.append(document.createTextNode(args.join(' ')));
 +          eOut.append(ln);
 +        };
 +        const logList = [
 +        ] /* We can't update DOM while speedtest and kvvfs only works
 +             in the main thread, so we queue up main()-time output
 +             into logList. */;
 +        const dumpLogList = function(){
 +          logList.forEach((v)=>log2('',v));
 +          logList.length = 0;
 +        };
 +        const log = (...args)=>{
 +          console.log(...args);
 +          logList.push(args.join(' '));
 +        };
 +        const logErr = function(...args){
 +          console.error(...args);
 +          logList.push('ERROR: '+args.join(' '));
 +        };
 +
 +        const guessStorageSize = function(which=''){
 +          let sz = 0;
 +          const prefix = 'kvvfs-'+which;
 +          [localStorage,sessionStorage].forEach((s)=>{
 +            let i;
 +            for(i = 0; i < s.length; ++i){
 +              const k = s.key(i);
 +              if(k.startsWith(prefix)){
 +                sz += k.length;
 +                sz += s.getItem(k).length;
 +              }
 +            }
 +          });
 +          return sz;
 +        };
 +        const clearStorage = function(){
 +            sessionStorage.clear();
 +            localStorage.clear();
 +        };
 +        const runTests = function(EmscriptenModule){
 +          console.log("Module inited.",EmscriptenModule);
 +            
 +          const wasm = {
 +            exports: EmscriptenModule.asm,
 +            alloc: (n)=>EmscriptenModule._malloc(n),
 +            dealloc: (m)=>EmscriptenModule._free(m),
 +            memory: EmscriptenModule.asm.memory || EmscriptenModule.wasmMemory
 +          };
 +          log2('warning',"Clearing session/local storage before test starts.");
 +          clearStorage();
 +          //console.debug('wasm =',wasm);
 +          self.WhWasmUtilInstaller(wasm);
 +          const scope = wasm.scopedAllocPush();
 +          const dbFile = 0 ? "session" : "local";
 +          const urlArgs = self.SqliteTestUtil.processUrlArgs();
 +          const argv = ["speedtest1", "--size", "5"];
 +          if(urlArgs.flags){
 +            // transform flags=a,b,c to ["--a", "--b", "--c"]
 +            argv.push(...(urlArgs.flags.split(',').map((v)=>'--'+v)));
 +          }else{
 +            argv.push(
 +              "--singlethread",
 +              "--nomutex",
 +              //"--nosync",
 +              "--nomemstat"
 +              //  ,"--sqlonly"
 +            );
 +            //argv.push("--memdb" /* note that memdb trumps the filename arg */);
 +          }
 +          argv.push("--big-transactions"/*important for tests 410 and 510!*/,
 +                    dbFile);
 +          if(argv.indexOf('--memdb')>=0){
 +              log2('error',"WARNING: --memdb flag trumps db filename.");
 +          }
 +          console.log("argv =",argv);
 +          // These log messages are not emitted to the UI until after main() returns. Fixing that
 +          // requires moving the main() call and related cleanup into a timeout handler.
 +          log2('',"Starting native app:\n ",argv.join(' '));
 +          log2('',"This will take a while and the browser might warn about the runaway JS.",
 +               "Give it time...");
 +          logList.length = 0;
 +          new Promise(function(resolve,reject){
 +            setTimeout(function(){
 +              try {
 +                wasm.xCall('__main_argc_argv', argv.length,
 +                           wasm.scopedAllocMainArgv(argv));
 +              }catch(e){
 +                reject(e);
 +              }
 +              resolve();
 +            }, 50);
 +          }).finally(function(){
 +            wasm.scopedAllocPop(scope);
 +            logList.unshift("Done running native main(). Output:");
 +            dumpLogList();
 +            log2('',"Approximate",dbFile,"storage usage:",guessStorageSize(),"bytes");
 +            log2('warning',"Clearing",dbFile,"storage.");
 +            clearStorage(dbFile);
 +          });
 +        }/*runTests()*/;
 +
 +        self.sqlite3TestModule.print = log;
 +        self.sqlite3TestModule.printErr = logErr;
 +        sqlite3Speedtest1InitModule(self.sqlite3TestModule).then(function(M){
 +          setTimeout(()=>runTests(M), 100);
 +        });
 +      })();
 +    </script>
 +  </body>
 +</html>
diff --cc manifest
index 3803dbcdfeccaa0ee858c7f842e1921ed1e1c72d,57b3e2ecada4c1ba700306a48be1b2326d78b087..a4dec5916007ac75a50b286fca83ced233024a70
+++ b/manifest
@@@ -1,5 -1,5 +1,5 @@@
- C Add\sbatch-runner-kvvfs.html,\sa\skvvfs-specific\sbuild\sof\sbatch-runner.html.\sReduce\sthe\sspeedtest1\s--size\sX\svalue\sfor\sthe\sbatch\slist\sgeneration\sto\s5\sso\sthat\sthe\skvvfs\sbatch\srunner\scan\shandle\sit.
- D 2022-09-16T02:30:49.153
 -C Fix\sos_kv.c\sso\sthat\sit\suses\sSQLITE_FCNTL_SYNC\sand\shence\nworks\seven\swith\sPRAGMA\ssynchronous=OFF.
 -D 2022-09-16T11:37:01.212
++C Merge\skv-vfs\sbranch\sinto\sfiddle-opfs\sbranch\sfor\s[21915af560b1|synchronous=off\sfix].\sRemove\ssome\sduplicate\sdebug\soutput\sin\sOPFS\stest\scode.
++D 2022-09-16T11:45:09.066
  F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
  F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
  F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@@ -507,31 -502,13 +507,31 @@@ F ext/wasm/jaccwabyt/jaccwabyt.js 0d7f3
  F ext/wasm/jaccwabyt/jaccwabyt.md 447cc02b598f7792edaa8ae6853a7847b8178a18ed356afacbdbf312b2588106
  F ext/wasm/jaccwabyt/jaccwabyt_test.c 39e4b865a33548f943e2eb9dd0dc8d619a80de05d5300668e9960fff30d0d36f
  F ext/wasm/jaccwabyt/jaccwabyt_test.exports 5ff001ef975c426ffe88d7d8a6e96ec725e568d2c2307c416902059339c06f19
 -F ext/wasm/kvvfs.make 7cc9cf10e744c3ba523c3eaf5c4af47028f3a5bb76db304ea8044a9b2a9d496f
 -F ext/wasm/kvvfs1.html 2acb241a6110a4ec581adbf07a23d5fc2ef9c7142aa9d60856732a102abc5016
 -F ext/wasm/kvvfs1.js 46afaf4faba041bf938355627bc529854295e561f49db3a240c914e75a529338
 -F ext/wasm/testing1.html 0bf3ff224628c1f1e3ed22a2dc1837c6c73722ad8c0ad9c8e6fb9e6047667231
 -F ext/wasm/testing1.js cba7134901a965743fa9289d82447ab71de4690b1ee5d06f6cb83e8b569d7943
 -F ext/wasm/testing2.html 73e5048e666fd6fb28b6e635677a9810e1e139c599ddcf28d687c982134b92b8
 -F ext/wasm/testing2.js d37433c601f88ed275712c1cfc92d3fb36c7c22e1ed8c7396fb2359e42238ebc
 +F ext/wasm/kvvfs.make 4b2ba6d061f3a52da9f5812f86f4faa80fb4d9456a152f6b0585dccd667a4e22
 +F ext/wasm/kvvfs1.html 13bb24190bfb276a57b228499519badcc1bf39ed07e4b37bc2a425ce6418fed1
 +F ext/wasm/kvvfs1.js ec1c1d071bb055711f9151df05616111432cf3e6bf7ac7f8dcbcfb56c9d9ed48
 +F ext/wasm/scratchpad-opfs-worker.html 5fdda167571264300f388847d34f00b77dd48984a8dba2ee9c099c3ffa05db66
 +F ext/wasm/scratchpad-opfs-worker.js cf6c4554d3b099c1a50013e50d19b3dc60e183511b4b4dbe7fabc2b9d3360567
- F ext/wasm/scratchpad-opfs-worker2.js 2424d7d7b8801fc143f6540fbdd8a96f3f2e5b811f0f545714d06147ccce58bf
++F ext/wasm/scratchpad-opfs-worker2.js 8c980370bbd5a262d96af8627c443936e11b87d0263a02123769d5953fc146da
 +F ext/wasm/scratchpad-wasmfs-main.html 20cf6f1a8f368e70d01e8c17200e3eaa90f1c8e1029186d836d14b83845fbe06
 +F ext/wasm/scratchpad-wasmfs-main.js 69e960e9161f6412fd0c30f355d4112f1894d6609eb431e2d16d207d1380518e
- F ext/wasm/speedtest1-kvvfs.html ad08e2018c67cde7c459f692122a7d675b54a3709726c094ecbeed230f36abd3
++F ext/wasm/speedtest1-kvvfs.html 6e6e918d44b819a9ea1a146f260bd69022bdccd854439ee2d8c646f5073c2ea8
 +F ext/wasm/speedtest1-wasmfs.html 6a67a6812f03a2058eb5c6ad0c8dea4bf749d0160ed9d6b826dabe7b766c3cf7
 +F ext/wasm/speedtest1-worker.html d8881ae802d15fb8adb94049265173e99f350e07e1d4e6f9e1cbd8969fe63a04
 +F ext/wasm/speedtest1-worker.js fb5d282c0b8aed18daf41c57f768cbf434f8137dbff707d53dcedcd7d4cb60ef
 +F ext/wasm/speedtest1.html fbb8e4d1639028443f3687a683be660beca6927920545cf6b1fdf503104591c0
 +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-worker1-promiser.js 92b8da5f38439ffec459a8215775d30fa498bc0f1ab929ff341fc3dd479660b9
 +F ext/wasm/sqlite3-worker1.js 0c1e7626304543969c3846573e080c082bf43bcaa47e87d416458af84f340a9e
 +F ext/wasm/testing-worker1-promiser.html 6eaec6e04a56cf24cf4fa8ef49d78ce8905dde1354235c9125dca6885f7ce893
 +F ext/wasm/testing-worker1-promiser.js 63448fddfd3b8c89ff667d17c8b31c6c2259dd4647ebbbd28f3a921c48e924da
 +F ext/wasm/testing1.html 50575755e43232dbe4c2f97c9086b3118eb91ec2ee1fae931e6d7669fb17fcae
 +F ext/wasm/testing1.js 7cd8ab255c238b030d928755ae8e91e7d90a12f2ae601b1b8f7827aaa4fb258e
 +F ext/wasm/testing2.html a66951c38137ff1d687df79466351f3c734fa9c6d9cce71d3cf97c291b2167e3
 +F ext/wasm/testing2.js 25584bcc30f19673ce13a6f301f89f8820a59dfe044e0c4f2913941f4097fe3c
 +F ext/wasm/wasmfs.make 21a5cf297954a689e0dc2a95299ae158f681cae5e90c10b99d986097815fd42d
  F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
  F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
  F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
@@@ -597,10 -574,10 +597,10 @@@ F src/notify.c 89a97dc854c3aa62ad5f384e
  F src/os.c 0eb831ba3575af5277e47f4edd14fdfc90025c67eb25ce5cda634518d308d4e9
  F src/os.h 1ff5ae51d339d0e30d8a9d814f4b8f8e448169304d83a7ed9db66a65732f3e63
  F src/os_common.h b2f4707a603e36811d9b1a13278bffd757857b85
- F src/os_kv.c d4909b439043183f9b6aec65528a7433cee49d41cca95b9a3a4c8fb6bbedc0c2
+ F src/os_kv.c 554a2c109f8810b743af2eed4ba732d18dfdbc4d073e3a9bd8b8e828215a9692
  F src/os_setup.h 0711dbc4678f3ac52d7fe736951b6384a0615387c4ba5135a4764e4e31f4b6a6
  F src/os_unix.c 0fa91925f0b8831fc0156a9c04d39d86f85baf9eef66c98712395e1715cb75cc
 -F src/os_win.c e9454cb141908e8eef2102180bad353a36480612d5b736e4c2bd5777d9b25a34
 +F src/os_win.c 8d129ae3e59e0fa900e20d0ad789e96f2e08177f0b00b53cdda65c40331e0902
  F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a
  F src/pager.c 6176d9752eb580419e8fef4592dc417a6b00ddfd43ee22f818819bf8840ceee8
  F src/pager.h f82e9844166e1585f5786837ddc7709966138ced17f568c16af7ccf946c2baa3
@@@ -2027,8 -2004,8 +2027,8 @@@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a9
  F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
  F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
  F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
- P ad0677e8abcc077636d1cf1d8485be4943506382581edf832e6b8a2021560040
- R 6eaf6290c7f1da025383bc24b7d47eee
 -P e334449912d5176e355d02024a07ed867741f71c9d10ce6744ca800414bf3eeb
 -R a55522768af3f3b5d07199ad1dd4c789
 -U drh
 -Z d50c7a05ab573e361ead585b4e643b32
++P d8df25920a047d5cf2093cc6233128c5e6057a9104d0c4397e643645bd646fe1 21915af560b111aeeaee751790356151a5f063c2fc703dd4b35b22dc393409fb
++R 9d036c771fee3f8c011441c0aa641532
 +U stephan
- Z 5b7d05db593e0eeb6d33327ff3472ed8
++Z 6c79830061a07791b98ec477a66ca0ad
  # Remove this line to create a well-formed Fossil manifest.
diff --cc manifest.uuid
index 34cc1ee9715abc4f347ae264c7ab05dba76035b2,c9139459b8c9d3defc6539ffebda95ff09463fd7..5aecdc9f0ff6e1fd177ed19a8f7bb3988918c492
@@@ -1,1 -1,1 +1,1 @@@
- d8df25920a047d5cf2093cc6233128c5e6057a9104d0c4397e643645bd646fe1
 -21915af560b111aeeaee751790356151a5f063c2fc703dd4b35b22dc393409fb
++13899bb98c80525276d2484598b94e4206358f243f06d45c02700024f7e226fd