1cd0df83cSstephan/*
2cd0df83cSstephan  2022-09-16
3cd0df83cSstephan
4cd0df83cSstephan  The author disclaims copyright to this source code.  In place of a
5cd0df83cSstephan  legal notice, here is a blessing:
6cd0df83cSstephan
7cd0df83cSstephan  *   May you do good and not evil.
8cd0df83cSstephan  *   May you find forgiveness for yourself and forgive others.
9cd0df83cSstephan  *   May you share freely, never taking more than you give.
10cd0df83cSstephan
11cd0df83cSstephan  ***********************************************************************
12cd0df83cSstephan
13f861b36bSstephan  A Worker which manages asynchronous OPFS handles on behalf of a
14cd0df83cSstephan  synchronous API which controls it via a combination of Worker
15cd0df83cSstephan  messages, SharedArrayBuffer, and Atomics. It is the asynchronous
16cd0df83cSstephan  counterpart of the API defined in sqlite3-api-opfs.js.
17cd0df83cSstephan
18cd0df83cSstephan  Highly indebted to:
19cd0df83cSstephan
20cd0df83cSstephan  https://github.com/rhashimoto/wa-sqlite/blob/master/src/examples/OriginPrivateFileSystemVFS.js
21cd0df83cSstephan
22cd0df83cSstephan  for demonstrating how to use the OPFS APIs.
23cd0df83cSstephan
24cd0df83cSstephan  This file is to be loaded as a Worker. It does not have any direct
25cd0df83cSstephan  access to the sqlite3 JS/WASM bits, so any bits which it needs (most
26cd0df83cSstephan  notably SQLITE_xxx integer codes) have to be imported into it via an
27cd0df83cSstephan  initialization process.
28f861b36bSstephan
29f861b36bSstephan  This file represents an implementation detail of a larger piece of
30f861b36bSstephan  code, and not a public interface. Its details may change at any time
31f861b36bSstephan  and are not intended to be used by any client-level code.
32cd0df83cSstephan*/
33f861b36bSstephan"use strict";
34cd0df83cSstephanconst toss = function(...args){throw new Error(args.join(' '))};
35cd0df83cSstephanif(self.window === self){
36cd0df83cSstephan  toss("This code cannot run from the main thread.",
37cd0df83cSstephan       "Load it as a Worker from a separate Worker.");
38cd0df83cSstephan}else if(!navigator.storage.getDirectory){
39cd0df83cSstephan  toss("This API requires navigator.storage.getDirectory.");
40cd0df83cSstephan}
41cd0df83cSstephan
42cd0df83cSstephan/**
43cd0df83cSstephan   Will hold state copied to this object from the syncronous side of
44cd0df83cSstephan   this API.
45cd0df83cSstephan*/
46cd0df83cSstephanconst state = Object.create(null);
47*da264159Sstephan
48cd0df83cSstephan/**
49cd0df83cSstephan   verbose:
50cd0df83cSstephan
51cd0df83cSstephan   0 = no logging output
52cd0df83cSstephan   1 = only errors
53cd0df83cSstephan   2 = warnings and errors
54cd0df83cSstephan   3 = debug, warnings, and errors
55cd0df83cSstephan*/
56cd0df83cSstephanstate.verbose = 2;
57cd0df83cSstephan
58cd0df83cSstephanconst loggers = {
59cd0df83cSstephan  0:console.error.bind(console),
60cd0df83cSstephan  1:console.warn.bind(console),
61cd0df83cSstephan  2:console.log.bind(console)
62cd0df83cSstephan};
63cd0df83cSstephanconst logImpl = (level,...args)=>{
64cd0df83cSstephan  if(state.verbose>level) loggers[level]("OPFS asyncer:",...args);
65cd0df83cSstephan};
66cd0df83cSstephanconst log =    (...args)=>logImpl(2, ...args);
67cd0df83cSstephanconst warn =   (...args)=>logImpl(1, ...args);
68cd0df83cSstephanconst error =  (...args)=>logImpl(0, ...args);
69cd0df83cSstephanconst metrics = Object.create(null);
70cd0df83cSstephanmetrics.reset = ()=>{
71cd0df83cSstephan  let k;
72cd0df83cSstephan  const r = (m)=>(m.count = m.time = m.wait = 0);
73cd0df83cSstephan  for(k in state.opIds){
74cd0df83cSstephan    r(metrics[k] = Object.create(null));
75cd0df83cSstephan  }
76cd0df83cSstephan  let s = metrics.s11n = Object.create(null);
77cd0df83cSstephan  s = s.serialize = Object.create(null);
78cd0df83cSstephan  s.count = s.time = 0;
79cd0df83cSstephan  s = metrics.s11n.deserialize = Object.create(null);
80cd0df83cSstephan  s.count = s.time = 0;
81cd0df83cSstephan};
82cd0df83cSstephanmetrics.dump = ()=>{
83cd0df83cSstephan  let k, n = 0, t = 0, w = 0;
84cd0df83cSstephan  for(k in state.opIds){
85cd0df83cSstephan    const m = metrics[k];
86cd0df83cSstephan    n += m.count;
87cd0df83cSstephan    t += m.time;
88cd0df83cSstephan    w += m.wait;
89cd0df83cSstephan    m.avgTime = (m.count && m.time) ? (m.time / m.count) : 0;
90cd0df83cSstephan  }
91cd0df83cSstephan  console.log(self.location.href,
92cd0df83cSstephan              "metrics for",self.location.href,":\n",
93cd0df83cSstephan              metrics,
94cd0df83cSstephan              "\nTotal of",n,"op(s) for",t,"ms",
95cd0df83cSstephan              "approx",w,"ms spent waiting on OPFS APIs.");
96cd0df83cSstephan  console.log("Serialization metrics:",metrics.s11n);
97cd0df83cSstephan};
98cd0df83cSstephan
99cd0df83cSstephan/**
100*da264159Sstephan   __openFiles is a map of sqlite3_file pointers (integers) to
101*da264159Sstephan   metadata related to a given OPFS file handles. The pointers are, in
102*da264159Sstephan   this side of the interface, opaque file handle IDs provided by the
103*da264159Sstephan   synchronous part of this constellation. Each value is an object
104*da264159Sstephan   with a structure demonstrated in the xOpen() impl.
105cd0df83cSstephan*/
106cd0df83cSstephanconst __openFiles = Object.create(null);
107*da264159Sstephan/**
108*da264159Sstephan   __autoLocks is a Set of sqlite3_file pointers (integers) which were
109*da264159Sstephan   "auto-locked".  i.e. those for which we obtained a sync access
110*da264159Sstephan   handle without an explicit xLock() call. Such locks will be
111*da264159Sstephan   released during db connection idle time, whereas a sync access
112*da264159Sstephan   handle obtained via xLock(), or subsequently xLock()'d after
113*da264159Sstephan   auto-acquisition, will not be released until xUnlock() is called.
114*da264159Sstephan
115*da264159Sstephan   Maintenance reminder: if we relinquish auto-locks at the end of the
116*da264159Sstephan   operation which acquires them, we pay a massive performance
117*da264159Sstephan   penalty: speedtest1 benchmarks take up to 4x as long. By delaying
118*da264159Sstephan   the lock release until idle time, the hit is negligible.
119*da264159Sstephan*/
120*da264159Sstephanconst __autoLocks = new Set();
121cd0df83cSstephan
122cd0df83cSstephan/**
123cd0df83cSstephan   Expects an OPFS file path. It gets resolved, such that ".."
124cd0df83cSstephan   components are properly expanded, and returned. If the 2nd arg is
125cd0df83cSstephan   true, the result is returned as an array of path elements, else an
126cd0df83cSstephan   absolute path string is returned.
127cd0df83cSstephan*/
128cd0df83cSstephanconst getResolvedPath = function(filename,splitIt){
129cd0df83cSstephan  const p = new URL(
130cd0df83cSstephan    filename, 'file://irrelevant'
131cd0df83cSstephan  ).pathname;
132cd0df83cSstephan  return splitIt ? p.split('/').filter((v)=>!!v) : p;
133cd0df83cSstephan};
134cd0df83cSstephan
135cd0df83cSstephan/**
136cd0df83cSstephan   Takes the absolute path to a filesystem element. Returns an array
137cd0df83cSstephan   of [handleOfContainingDir, filename]. If the 2nd argument is truthy
138cd0df83cSstephan   then each directory element leading to the file is created along
139cd0df83cSstephan   the way. Throws if any creation or resolution fails.
140cd0df83cSstephan*/
141cd0df83cSstephanconst getDirForFilename = async function f(absFilename, createDirs = false){
142cd0df83cSstephan  const path = getResolvedPath(absFilename, true);
143cd0df83cSstephan  const filename = path.pop();
144cd0df83cSstephan  let dh = state.rootDir;
145cd0df83cSstephan  for(const dirName of path){
146cd0df83cSstephan    if(dirName){
147cd0df83cSstephan      dh = await dh.getDirectoryHandle(dirName, {create: !!createDirs});
148cd0df83cSstephan    }
149cd0df83cSstephan  }
150cd0df83cSstephan  return [dh, filename];
151cd0df83cSstephan};
152cd0df83cSstephan
153cd0df83cSstephan/**
15443b442a6Sstephan   An error class specifically for use with getSyncHandle(), the goal
15543b442a6Sstephan   of which is to eventually be able to distinguish unambiguously
15643b442a6Sstephan   between locking-related failures and other types, noting that we
15743b442a6Sstephan   cannot currently do so because createSyncAccessHandle() does not
15843b442a6Sstephan   define its exceptions in the required level of detail.
15943b442a6Sstephan*/
16043b442a6Sstephanclass GetSyncHandleError extends Error {
16143b442a6Sstephan  constructor(errorObject, ...msg){
16243b442a6Sstephan    super();
16343b442a6Sstephan    this.error = errorObject;
16443b442a6Sstephan    this.message = [
16543b442a6Sstephan      ...msg, ': Original exception ['+errorObject.name+']:',
16643b442a6Sstephan      errorObject.message
16743b442a6Sstephan    ].join(' ');
16843b442a6Sstephan    this.name = 'GetSyncHandleError';
16943b442a6Sstephan  }
17043b442a6Sstephan};
17143b442a6Sstephan
17243b442a6Sstephan/**
173cd0df83cSstephan   Returns the sync access handle associated with the given file
174f861b36bSstephan   handle object (which must be a valid handle object, as created by
175f861b36bSstephan   xOpen()), lazily opening it if needed.
176cd0df83cSstephan
177cd0df83cSstephan   In order to help alleviate cross-tab contention for a dabase,
178cd0df83cSstephan   if an exception is thrown while acquiring the handle, this routine
179cd0df83cSstephan   will wait briefly and try again, up to 3 times. If acquisition
180cd0df83cSstephan   still fails at that point it will give up and propagate the
181cd0df83cSstephan   exception.
182cd0df83cSstephan*/
183cd0df83cSstephanconst getSyncHandle = async (fh)=>{
184cd0df83cSstephan  if(!fh.syncHandle){
185cd0df83cSstephan    const t = performance.now();
186cd0df83cSstephan    log("Acquiring sync handle for",fh.filenameAbs);
1879163ef1fSstephan    const maxTries = 4, msBase = 300;
1889163ef1fSstephan    let i = 1, ms = msBase;
1899163ef1fSstephan    for(; true; ms = msBase * ++i){
190cd0df83cSstephan      try {
19143b442a6Sstephan        //if(i<3) toss("Just testing getSyncHandle() wait-and-retry.");
192cd0df83cSstephan        //TODO? A config option which tells it to throw here
193cd0df83cSstephan        //randomly every now and then, for testing purposes.
194cd0df83cSstephan        fh.syncHandle = await fh.fileHandle.createSyncAccessHandle();
195cd0df83cSstephan        break;
196cd0df83cSstephan      }catch(e){
197cd0df83cSstephan        if(i === maxTries){
19843b442a6Sstephan          throw new GetSyncHandleError(
19943b442a6Sstephan            e, "Error getting sync handle.",maxTries,
20043b442a6Sstephan            "attempts failed.",fh.filenameAbs
20143b442a6Sstephan          );
202cd0df83cSstephan        }
203cd0df83cSstephan        warn("Error getting sync handle. Waiting",ms,
204cd0df83cSstephan              "ms and trying again.",fh.filenameAbs,e);
205f861b36bSstephan        Atomics.wait(state.sabOPView, state.opIds.retry, 0, ms);
206cd0df83cSstephan      }
207cd0df83cSstephan    }
208cd0df83cSstephan    log("Got sync handle for",fh.filenameAbs,'in',performance.now() - t,'ms');
209*da264159Sstephan    if(!fh.xLock){
210*da264159Sstephan      __autoLocks.add(fh.fid);
211*da264159Sstephan      log("Auto-locked",fh.fid,fh.filenameAbs);
212*da264159Sstephan    }
213cd0df83cSstephan  }
214cd0df83cSstephan  return fh.syncHandle;
215cd0df83cSstephan};
216cd0df83cSstephan
217cd0df83cSstephan/**
218cd0df83cSstephan   If the given file-holding object has a sync handle attached to it,
219cd0df83cSstephan   that handle is remove and asynchronously closed. Though it may
220cd0df83cSstephan   sound sensible to continue work as soon as the close() returns
221cd0df83cSstephan   (noting that it's asynchronous), doing so can cause operations
222cd0df83cSstephan   performed soon afterwards, e.g. a call to getSyncHandle() to fail
223cd0df83cSstephan   because they may happen out of order from the close(). OPFS does
224cd0df83cSstephan   not guaranty that the actual order of operations is retained in
225cd0df83cSstephan   such cases. i.e.  always "await" on the result of this function.
226cd0df83cSstephan*/
227cd0df83cSstephanconst closeSyncHandle = async (fh)=>{
228cd0df83cSstephan  if(fh.syncHandle){
229cd0df83cSstephan    log("Closing sync handle for",fh.filenameAbs);
230cd0df83cSstephan    const h = fh.syncHandle;
231cd0df83cSstephan    delete fh.syncHandle;
232*da264159Sstephan    delete fh.xLock;
233*da264159Sstephan    __autoLocks.delete(fh.fid);
234cd0df83cSstephan    return h.close();
235cd0df83cSstephan  }
236cd0df83cSstephan};
237cd0df83cSstephan
238cd0df83cSstephan/**
239aafa022fSstephan   A proxy for closeSyncHandle() which is guaranteed to not throw.
240aafa022fSstephan
241aafa022fSstephan   This function is part of a lock/unlock step in functions which
242aafa022fSstephan   require a sync access handle but may be called without xLock()
243aafa022fSstephan   having been called first. Such calls need to release that
244aafa022fSstephan   handle to avoid locking the file for all of time. This is an
245aafa022fSstephan   _attempt_ at reducing cross-tab contention but it may prove
246aafa022fSstephan   to be more of a problem than a solution and may need to be
247aafa022fSstephan   removed.
248aafa022fSstephan*/
249aafa022fSstephanconst closeSyncHandleNoThrow = async (fh)=>{
250aafa022fSstephan  try{await closeSyncHandle(fh)}
251aafa022fSstephan  catch(e){
252aafa022fSstephan    warn("closeSyncHandleNoThrow() ignoring:",e,fh);
253aafa022fSstephan  }
254aafa022fSstephan};
255aafa022fSstephan
256aafa022fSstephan/**
257cd0df83cSstephan   Stores the given value at state.sabOPView[state.opIds.rc] and then
258cd0df83cSstephan   Atomics.notify()'s it.
259cd0df83cSstephan*/
260cd0df83cSstephanconst storeAndNotify = (opName, value)=>{
26143b442a6Sstephan  log(opName+"() => notify(",value,")");
262cd0df83cSstephan  Atomics.store(state.sabOPView, state.opIds.rc, value);
263cd0df83cSstephan  Atomics.notify(state.sabOPView, state.opIds.rc);
264cd0df83cSstephan};
265cd0df83cSstephan
266cd0df83cSstephan/**
267cd0df83cSstephan   Throws if fh is a file-holding object which is flagged as read-only.
268cd0df83cSstephan*/
269cd0df83cSstephanconst affirmNotRO = function(opName,fh){
270cd0df83cSstephan  if(fh.readOnly) toss(opName+"(): File is read-only: "+fh.filenameAbs);
271cd0df83cSstephan};
272f45c3370Sstephanconst affirmLocked = function(opName,fh){
273f45c3370Sstephan  //if(!fh.syncHandle) toss(opName+"(): File does not have a lock: "+fh.filenameAbs);
274f45c3370Sstephan  /**
275f45c3370Sstephan     Currently a no-op, as speedtest1 triggers xRead() without a
276f45c3370Sstephan     lock (that seems like a bug but it's currently uninvestigated).
277f45c3370Sstephan     This means, however, that some OPFS VFS routines may trigger
278f45c3370Sstephan     acquisition of a lock but never let it go until xUnlock() is
279f45c3370Sstephan     called (which it likely won't be if xLock() was not called).
280f45c3370Sstephan  */
281f45c3370Sstephan};
282cd0df83cSstephan
283cd0df83cSstephan/**
284cd0df83cSstephan   We track 2 different timers: the "metrics" timer records how much
285cd0df83cSstephan   time we spend performing work. The "wait" timer records how much
286cd0df83cSstephan   time we spend waiting on the underlying OPFS timer. See the calls
287cd0df83cSstephan   to mTimeStart(), mTimeEnd(), wTimeStart(), and wTimeEnd()
288cd0df83cSstephan   throughout this file to see how they're used.
289cd0df83cSstephan*/
290cd0df83cSstephanconst __mTimer = Object.create(null);
291cd0df83cSstephan__mTimer.op = undefined;
292cd0df83cSstephan__mTimer.start = undefined;
293cd0df83cSstephanconst mTimeStart = (op)=>{
294cd0df83cSstephan  __mTimer.start = performance.now();
295cd0df83cSstephan  __mTimer.op = op;
296cd0df83cSstephan  //metrics[op] || toss("Maintenance required: missing metrics for",op);
297cd0df83cSstephan  ++metrics[op].count;
298cd0df83cSstephan};
299cd0df83cSstephanconst mTimeEnd = ()=>(
300cd0df83cSstephan  metrics[__mTimer.op].time += performance.now() - __mTimer.start
301cd0df83cSstephan);
302cd0df83cSstephanconst __wTimer = Object.create(null);
303cd0df83cSstephan__wTimer.op = undefined;
304cd0df83cSstephan__wTimer.start = undefined;
305cd0df83cSstephanconst wTimeStart = (op)=>{
306cd0df83cSstephan  __wTimer.start = performance.now();
307cd0df83cSstephan  __wTimer.op = op;
308cd0df83cSstephan  //metrics[op] || toss("Maintenance required: missing metrics for",op);
309cd0df83cSstephan};
310cd0df83cSstephanconst wTimeEnd = ()=>(
311cd0df83cSstephan  metrics[__wTimer.op].wait += performance.now() - __wTimer.start
312cd0df83cSstephan);
313cd0df83cSstephan
314cd0df83cSstephan/**
315cd0df83cSstephan   Gets set to true by the 'opfs-async-shutdown' command to quit the
316cd0df83cSstephan   wait loop. This is only intended for debugging purposes: we cannot
317cd0df83cSstephan   inspect this file's state while the tight waitLoop() is running and
318cd0df83cSstephan   need a way to stop that loop for introspection purposes.
319cd0df83cSstephan*/
320cd0df83cSstephanlet flagAsyncShutdown = false;
321cd0df83cSstephan
322cd0df83cSstephan
323cd0df83cSstephan/**
324cd0df83cSstephan   Asynchronous wrappers for sqlite3_vfs and sqlite3_io_methods
325cd0df83cSstephan   methods, as well as helpers like mkdir(). Maintenance reminder:
326cd0df83cSstephan   members are in alphabetical order to simplify finding them.
327cd0df83cSstephan*/
328cd0df83cSstephanconst vfsAsyncImpls = {
329cd0df83cSstephan  'opfs-async-metrics': async ()=>{
330cd0df83cSstephan    mTimeStart('opfs-async-metrics');
331cd0df83cSstephan    metrics.dump();
332cd0df83cSstephan    storeAndNotify('opfs-async-metrics', 0);
333cd0df83cSstephan    mTimeEnd();
334cd0df83cSstephan  },
335cd0df83cSstephan  'opfs-async-shutdown': async ()=>{
336cd0df83cSstephan    flagAsyncShutdown = true;
337cd0df83cSstephan    storeAndNotify('opfs-async-shutdown', 0);
338cd0df83cSstephan  },
339cd0df83cSstephan  mkdir: async (dirname)=>{
340cd0df83cSstephan    mTimeStart('mkdir');
341cd0df83cSstephan    let rc = 0;
342cd0df83cSstephan    wTimeStart('mkdir');
343cd0df83cSstephan    try {
344cd0df83cSstephan        await getDirForFilename(dirname+"/filepart", true);
345cd0df83cSstephan    }catch(e){
346cd0df83cSstephan      state.s11n.storeException(2,e);
347cd0df83cSstephan      rc = state.sq3Codes.SQLITE_IOERR;
348cd0df83cSstephan    }finally{
349cd0df83cSstephan      wTimeEnd();
350cd0df83cSstephan    }
351cd0df83cSstephan    storeAndNotify('mkdir', rc);
352cd0df83cSstephan    mTimeEnd();
353cd0df83cSstephan  },
354cd0df83cSstephan  xAccess: async (filename)=>{
355cd0df83cSstephan    mTimeStart('xAccess');
356cd0df83cSstephan    /* OPFS cannot support the full range of xAccess() queries sqlite3
357cd0df83cSstephan       calls for. We can essentially just tell if the file is
358cd0df83cSstephan       accessible, but if it is it's automatically writable (unless
359cd0df83cSstephan       it's locked, which we cannot(?) know without trying to open
360cd0df83cSstephan       it). OPFS does not have the notion of read-only.
361cd0df83cSstephan
362cd0df83cSstephan       The return semantics of this function differ from sqlite3's
363cd0df83cSstephan       xAccess semantics because we are limited in what we can
364cd0df83cSstephan       communicate back to our synchronous communication partner: 0 =
365cd0df83cSstephan       accessible, non-0 means not accessible.
366cd0df83cSstephan    */
367cd0df83cSstephan    let rc = 0;
368cd0df83cSstephan    wTimeStart('xAccess');
369cd0df83cSstephan    try{
370cd0df83cSstephan      const [dh, fn] = await getDirForFilename(filename);
371cd0df83cSstephan      await dh.getFileHandle(fn);
372cd0df83cSstephan    }catch(e){
373cd0df83cSstephan      state.s11n.storeException(2,e);
374cd0df83cSstephan      rc = state.sq3Codes.SQLITE_IOERR;
375cd0df83cSstephan    }finally{
376cd0df83cSstephan      wTimeEnd();
377cd0df83cSstephan    }
378cd0df83cSstephan    storeAndNotify('xAccess', rc);
379cd0df83cSstephan    mTimeEnd();
380cd0df83cSstephan  },
381f861b36bSstephan  xClose: async function(fid/*sqlite3_file pointer*/){
382cd0df83cSstephan    const opName = 'xClose';
383cd0df83cSstephan    mTimeStart(opName);
384*da264159Sstephan    __autoLocks.delete(fid);
385cd0df83cSstephan    const fh = __openFiles[fid];
386cd0df83cSstephan    let rc = 0;
387*da264159Sstephan    wTimeStart(opName);
388cd0df83cSstephan    if(fh){
389cd0df83cSstephan      delete __openFiles[fid];
390cd0df83cSstephan      await closeSyncHandle(fh);
391cd0df83cSstephan      if(fh.deleteOnClose){
392cd0df83cSstephan        try{ await fh.dirHandle.removeEntry(fh.filenamePart) }
393cd0df83cSstephan        catch(e){ warn("Ignoring dirHandle.removeEntry() failure of",fh,e) }
394cd0df83cSstephan      }
395cd0df83cSstephan    }else{
396cd0df83cSstephan      state.s11n.serialize();
397cd0df83cSstephan      rc = state.sq3Codes.SQLITE_NOTFOUND;
398cd0df83cSstephan    }
399cd0df83cSstephan    wTimeEnd();
400cd0df83cSstephan    storeAndNotify(opName, rc);
401cd0df83cSstephan    mTimeEnd();
402cd0df83cSstephan  },
403cd0df83cSstephan  xDelete: async function(...args){
404cd0df83cSstephan    mTimeStart('xDelete');
405cd0df83cSstephan    const rc = await vfsAsyncImpls.xDeleteNoWait(...args);
406cd0df83cSstephan    storeAndNotify('xDelete', rc);
407cd0df83cSstephan    mTimeEnd();
408cd0df83cSstephan  },
409cd0df83cSstephan  xDeleteNoWait: async function(filename, syncDir = 0, recursive = false){
410cd0df83cSstephan    /* The syncDir flag is, for purposes of the VFS API's semantics,
411cd0df83cSstephan       ignored here. However, if it has the value 0x1234 then: after
412cd0df83cSstephan       deleting the given file, recursively try to delete any empty
413cd0df83cSstephan       directories left behind in its wake (ignoring any errors and
414cd0df83cSstephan       stopping at the first failure).
415cd0df83cSstephan
416cd0df83cSstephan       That said: we don't know for sure that removeEntry() fails if
417cd0df83cSstephan       the dir is not empty because the API is not documented. It has,
418cd0df83cSstephan       however, a "recursive" flag which defaults to false, so
419cd0df83cSstephan       presumably it will fail if the dir is not empty and that flag
420cd0df83cSstephan       is false.
421cd0df83cSstephan    */
422cd0df83cSstephan    let rc = 0;
423cd0df83cSstephan    wTimeStart('xDelete');
424cd0df83cSstephan    try {
425cd0df83cSstephan      while(filename){
426cd0df83cSstephan        const [hDir, filenamePart] = await getDirForFilename(filename, false);
427cd0df83cSstephan        if(!filenamePart) break;
428cd0df83cSstephan        await hDir.removeEntry(filenamePart, {recursive});
429cd0df83cSstephan        if(0x1234 !== syncDir) break;
43049048b14Sstephan        recursive = false;
431cd0df83cSstephan        filename = getResolvedPath(filename, true);
432cd0df83cSstephan        filename.pop();
433cd0df83cSstephan        filename = filename.join('/');
434cd0df83cSstephan      }
435cd0df83cSstephan    }catch(e){
436cd0df83cSstephan      state.s11n.storeException(2,e);
437cd0df83cSstephan      rc = state.sq3Codes.SQLITE_IOERR_DELETE;
438cd0df83cSstephan    }
439cd0df83cSstephan    wTimeEnd();
440cd0df83cSstephan    return rc;
441cd0df83cSstephan  },
442f861b36bSstephan  xFileSize: async function(fid/*sqlite3_file pointer*/){
443cd0df83cSstephan    mTimeStart('xFileSize');
444cd0df83cSstephan    const fh = __openFiles[fid];
44543b442a6Sstephan    let rc;
446cd0df83cSstephan    wTimeStart('xFileSize');
447cd0df83cSstephan    try{
448f45c3370Sstephan      affirmLocked('xFileSize',fh);
44943b442a6Sstephan      rc = await (await getSyncHandle(fh)).getSize();
45043b442a6Sstephan      state.s11n.serialize(Number(rc));
45143b442a6Sstephan      rc = 0;
452cd0df83cSstephan    }catch(e){
453cd0df83cSstephan      state.s11n.storeException(2,e);
45443b442a6Sstephan      rc = state.sq3Codes.SQLITE_IOERR;
455cd0df83cSstephan    }
456cd0df83cSstephan    wTimeEnd();
45743b442a6Sstephan    storeAndNotify('xFileSize', rc);
458cd0df83cSstephan    mTimeEnd();
459cd0df83cSstephan  },
460f861b36bSstephan  xLock: async function(fid/*sqlite3_file pointer*/,
461f861b36bSstephan                        lockType/*SQLITE_LOCK_...*/){
462cd0df83cSstephan    mTimeStart('xLock');
463cd0df83cSstephan    const fh = __openFiles[fid];
464cd0df83cSstephan    let rc = 0;
465*da264159Sstephan    const oldLockType = fh.xLock;
466*da264159Sstephan    fh.xLock = lockType;
467cd0df83cSstephan    if( !fh.syncHandle ){
468cd0df83cSstephan      wTimeStart('xLock');
469*da264159Sstephan      try {
470*da264159Sstephan        await getSyncHandle(fh);
471*da264159Sstephan        __autoLocks.delete(fid);
472*da264159Sstephan      }catch(e){
473cd0df83cSstephan        state.s11n.storeException(1,e);
47443b442a6Sstephan        rc = state.sq3Codes.SQLITE_IOERR_LOCK;
475*da264159Sstephan        fh.xLock = oldLockType;
476cd0df83cSstephan      }
477cd0df83cSstephan      wTimeEnd();
478cd0df83cSstephan    }
479cd0df83cSstephan    storeAndNotify('xLock',rc);
480cd0df83cSstephan    mTimeEnd();
481cd0df83cSstephan  },
482f861b36bSstephan  xOpen: async function(fid/*sqlite3_file pointer*/, filename,
483f861b36bSstephan                        flags/*SQLITE_OPEN_...*/){
484cd0df83cSstephan    const opName = 'xOpen';
485cd0df83cSstephan    mTimeStart(opName);
486cd0df83cSstephan    const deleteOnClose = (state.sq3Codes.SQLITE_OPEN_DELETEONCLOSE & flags);
487cd0df83cSstephan    const create = (state.sq3Codes.SQLITE_OPEN_CREATE & flags);
488cd0df83cSstephan    wTimeStart('xOpen');
489cd0df83cSstephan    try{
490cd0df83cSstephan      let hDir, filenamePart;
491cd0df83cSstephan      try {
492cd0df83cSstephan        [hDir, filenamePart] = await getDirForFilename(filename, !!create);
493cd0df83cSstephan      }catch(e){
494c18c8bf9Sstephan        state.s11n.storeException(1,e);
495c18c8bf9Sstephan        storeAndNotify(opName, state.sq3Codes.SQLITE_NOTFOUND);
496cd0df83cSstephan        mTimeEnd();
497cd0df83cSstephan        wTimeEnd();
498cd0df83cSstephan        return;
499cd0df83cSstephan      }
500cd0df83cSstephan      const hFile = await hDir.getFileHandle(filenamePart, {create});
501cd0df83cSstephan      /**
502cd0df83cSstephan         wa-sqlite, at this point, grabs a SyncAccessHandle and
503cd0df83cSstephan         assigns it to the syncHandle prop of the file state
504cd0df83cSstephan         object, but only for certain cases and it's unclear why it
505cd0df83cSstephan         places that limitation on it.
506cd0df83cSstephan      */
507cd0df83cSstephan      wTimeEnd();
508cd0df83cSstephan      __openFiles[fid] = Object.assign(Object.create(null),{
509*da264159Sstephan        fid: fid,
510cd0df83cSstephan        filenameAbs: filename,
511cd0df83cSstephan        filenamePart: filenamePart,
512cd0df83cSstephan        dirHandle: hDir,
513cd0df83cSstephan        fileHandle: hFile,
514cd0df83cSstephan        sabView: state.sabFileBufView,
515cd0df83cSstephan        readOnly: create
516cd0df83cSstephan          ? false : (state.sq3Codes.SQLITE_OPEN_READONLY & flags),
517cd0df83cSstephan        deleteOnClose: deleteOnClose
518cd0df83cSstephan      });
519cd0df83cSstephan      storeAndNotify(opName, 0);
520cd0df83cSstephan    }catch(e){
521cd0df83cSstephan      wTimeEnd();
522cd0df83cSstephan      error(opName,e);
523cd0df83cSstephan      state.s11n.storeException(1,e);
524cd0df83cSstephan      storeAndNotify(opName, state.sq3Codes.SQLITE_IOERR);
525cd0df83cSstephan    }
526cd0df83cSstephan    mTimeEnd();
527cd0df83cSstephan  },
528f861b36bSstephan  xRead: async function(fid/*sqlite3_file pointer*/,n,offset64){
529cd0df83cSstephan    mTimeStart('xRead');
530cd0df83cSstephan    let rc = 0, nRead;
531cd0df83cSstephan    const fh = __openFiles[fid];
532cd0df83cSstephan    try{
533f45c3370Sstephan      affirmLocked('xRead',fh);
534cd0df83cSstephan      wTimeStart('xRead');
535cd0df83cSstephan      nRead = (await getSyncHandle(fh)).read(
536cd0df83cSstephan        fh.sabView.subarray(0, n),
537f861b36bSstephan        {at: Number(offset64)}
538cd0df83cSstephan      );
539cd0df83cSstephan      wTimeEnd();
540cd0df83cSstephan      if(nRead < n){/* Zero-fill remaining bytes */
541cd0df83cSstephan        fh.sabView.fill(0, nRead, n);
542cd0df83cSstephan        rc = state.sq3Codes.SQLITE_IOERR_SHORT_READ;
543cd0df83cSstephan      }
544cd0df83cSstephan    }catch(e){
545cd0df83cSstephan      if(undefined===nRead) wTimeEnd();
546cd0df83cSstephan      error("xRead() failed",e,fh);
547cd0df83cSstephan      state.s11n.storeException(1,e);
548cd0df83cSstephan      rc = state.sq3Codes.SQLITE_IOERR_READ;
549cd0df83cSstephan    }
550cd0df83cSstephan    storeAndNotify('xRead',rc);
551cd0df83cSstephan    mTimeEnd();
552cd0df83cSstephan  },
553f861b36bSstephan  xSync: async function(fid/*sqlite3_file pointer*/,flags/*ignored*/){
554cd0df83cSstephan    mTimeStart('xSync');
555cd0df83cSstephan    const fh = __openFiles[fid];
556cd0df83cSstephan    let rc = 0;
557cd0df83cSstephan    if(!fh.readOnly && fh.syncHandle){
558cd0df83cSstephan      try {
559cd0df83cSstephan        wTimeStart('xSync');
560cd0df83cSstephan        await fh.syncHandle.flush();
561cd0df83cSstephan      }catch(e){
562cd0df83cSstephan        state.s11n.storeException(2,e);
56343b442a6Sstephan        rc = state.sq3Codes.SQLITE_IOERR_FSYNC;
564cd0df83cSstephan      }
565cd0df83cSstephan      wTimeEnd();
566cd0df83cSstephan    }
567cd0df83cSstephan    storeAndNotify('xSync',rc);
568cd0df83cSstephan    mTimeEnd();
569cd0df83cSstephan  },
570f861b36bSstephan  xTruncate: async function(fid/*sqlite3_file pointer*/,size){
571cd0df83cSstephan    mTimeStart('xTruncate');
572cd0df83cSstephan    let rc = 0;
573cd0df83cSstephan    const fh = __openFiles[fid];
574cd0df83cSstephan    wTimeStart('xTruncate');
575cd0df83cSstephan    try{
576f45c3370Sstephan      affirmLocked('xTruncate',fh);
577cd0df83cSstephan      affirmNotRO('xTruncate', fh);
578cd0df83cSstephan      await (await getSyncHandle(fh)).truncate(size);
579cd0df83cSstephan    }catch(e){
580cd0df83cSstephan      error("xTruncate():",e,fh);
581cd0df83cSstephan      state.s11n.storeException(2,e);
582cd0df83cSstephan      rc = state.sq3Codes.SQLITE_IOERR_TRUNCATE;
583cd0df83cSstephan    }
584cd0df83cSstephan    wTimeEnd();
585cd0df83cSstephan    storeAndNotify('xTruncate',rc);
586cd0df83cSstephan    mTimeEnd();
587cd0df83cSstephan  },
588f861b36bSstephan  xUnlock: async function(fid/*sqlite3_file pointer*/,
589f861b36bSstephan                          lockType/*SQLITE_LOCK_...*/){
590cd0df83cSstephan    mTimeStart('xUnlock');
591cd0df83cSstephan    let rc = 0;
592cd0df83cSstephan    const fh = __openFiles[fid];
593cd0df83cSstephan    if( state.sq3Codes.SQLITE_LOCK_NONE===lockType
594cd0df83cSstephan        && fh.syncHandle ){
595cd0df83cSstephan      wTimeStart('xUnlock');
596cd0df83cSstephan      try { await closeSyncHandle(fh) }
597cd0df83cSstephan      catch(e){
598cd0df83cSstephan        state.s11n.storeException(1,e);
59943b442a6Sstephan        rc = state.sq3Codes.SQLITE_IOERR_UNLOCK;
600cd0df83cSstephan      }
601cd0df83cSstephan      wTimeEnd();
602cd0df83cSstephan    }
603cd0df83cSstephan    storeAndNotify('xUnlock',rc);
604cd0df83cSstephan    mTimeEnd();
605cd0df83cSstephan  },
606f861b36bSstephan  xWrite: async function(fid/*sqlite3_file pointer*/,n,offset64){
607cd0df83cSstephan    mTimeStart('xWrite');
608cd0df83cSstephan    let rc;
609f45c3370Sstephan    const fh = __openFiles[fid];
610cd0df83cSstephan    wTimeStart('xWrite');
611cd0df83cSstephan    try{
612f45c3370Sstephan      affirmLocked('xWrite',fh);
613cd0df83cSstephan      affirmNotRO('xWrite', fh);
614cd0df83cSstephan      rc = (
615cd0df83cSstephan        n === (await getSyncHandle(fh))
616cd0df83cSstephan          .write(fh.sabView.subarray(0, n),
617f861b36bSstephan                 {at: Number(offset64)})
618cd0df83cSstephan      ) ? 0 : state.sq3Codes.SQLITE_IOERR_WRITE;
619cd0df83cSstephan    }catch(e){
620cd0df83cSstephan      error("xWrite():",e,fh);
621cd0df83cSstephan      state.s11n.storeException(1,e);
622cd0df83cSstephan      rc = state.sq3Codes.SQLITE_IOERR_WRITE;
623cd0df83cSstephan    }
624cd0df83cSstephan    wTimeEnd();
625cd0df83cSstephan    storeAndNotify('xWrite',rc);
626cd0df83cSstephan    mTimeEnd();
627cd0df83cSstephan  }
628cd0df83cSstephan}/*vfsAsyncImpls*/;
629cd0df83cSstephan
630cd0df83cSstephanconst initS11n = ()=>{
631cd0df83cSstephan  /**
632cd0df83cSstephan     ACHTUNG: this code is 100% duplicated in the other half of this
633cd0df83cSstephan     proxy! The documentation is maintained in the "synchronous half".
634cd0df83cSstephan  */
635cd0df83cSstephan  if(state.s11n) return state.s11n;
636cd0df83cSstephan  const textDecoder = new TextDecoder(),
637cd0df83cSstephan  textEncoder = new TextEncoder('utf-8'),
638cd0df83cSstephan  viewU8 = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize),
639cd0df83cSstephan  viewDV = new DataView(state.sabIO, state.sabS11nOffset, state.sabS11nSize);
640cd0df83cSstephan  state.s11n = Object.create(null);
641cd0df83cSstephan  const TypeIds = Object.create(null);
642cd0df83cSstephan  TypeIds.number  = { id: 1, size: 8, getter: 'getFloat64', setter: 'setFloat64' };
643cd0df83cSstephan  TypeIds.bigint  = { id: 2, size: 8, getter: 'getBigInt64', setter: 'setBigInt64' };
644cd0df83cSstephan  TypeIds.boolean = { id: 3, size: 4, getter: 'getInt32', setter: 'setInt32' };
645cd0df83cSstephan  TypeIds.string =  { id: 4 };
646cd0df83cSstephan  const getTypeId = (v)=>(
647cd0df83cSstephan    TypeIds[typeof v]
648cd0df83cSstephan      || toss("Maintenance required: this value type cannot be serialized.",v)
649cd0df83cSstephan  );
650cd0df83cSstephan  const getTypeIdById = (tid)=>{
651cd0df83cSstephan    switch(tid){
652cd0df83cSstephan      case TypeIds.number.id: return TypeIds.number;
653cd0df83cSstephan      case TypeIds.bigint.id: return TypeIds.bigint;
654cd0df83cSstephan      case TypeIds.boolean.id: return TypeIds.boolean;
655cd0df83cSstephan      case TypeIds.string.id: return TypeIds.string;
656cd0df83cSstephan      default: toss("Invalid type ID:",tid);
657cd0df83cSstephan    }
658cd0df83cSstephan  };
659*da264159Sstephan  state.s11n.deserialize = function(clear=false){
660cd0df83cSstephan    ++metrics.s11n.deserialize.count;
661cd0df83cSstephan    const t = performance.now();
662cd0df83cSstephan    const argc = viewU8[0];
663cd0df83cSstephan    const rc = argc ? [] : null;
664cd0df83cSstephan    if(argc){
665cd0df83cSstephan      const typeIds = [];
666cd0df83cSstephan      let offset = 1, i, n, v;
667cd0df83cSstephan      for(i = 0; i < argc; ++i, ++offset){
668cd0df83cSstephan        typeIds.push(getTypeIdById(viewU8[offset]));
669cd0df83cSstephan      }
670cd0df83cSstephan      for(i = 0; i < argc; ++i){
671cd0df83cSstephan        const t = typeIds[i];
672cd0df83cSstephan        if(t.getter){
673cd0df83cSstephan          v = viewDV[t.getter](offset, state.littleEndian);
674cd0df83cSstephan          offset += t.size;
675cd0df83cSstephan        }else{/*String*/
676cd0df83cSstephan          n = viewDV.getInt32(offset, state.littleEndian);
677cd0df83cSstephan          offset += 4;
678cd0df83cSstephan          v = textDecoder.decode(viewU8.slice(offset, offset+n));
679cd0df83cSstephan          offset += n;
680cd0df83cSstephan        }
681cd0df83cSstephan        rc.push(v);
682cd0df83cSstephan      }
683cd0df83cSstephan    }
684*da264159Sstephan    if(clear) viewU8[0] = 0;
685cd0df83cSstephan    //log("deserialize:",argc, rc);
686cd0df83cSstephan    metrics.s11n.deserialize.time += performance.now() - t;
687cd0df83cSstephan    return rc;
688cd0df83cSstephan  };
689cd0df83cSstephan  state.s11n.serialize = function(...args){
690cd0df83cSstephan    const t = performance.now();
691cd0df83cSstephan    ++metrics.s11n.serialize.count;
692cd0df83cSstephan    if(args.length){
693cd0df83cSstephan      //log("serialize():",args);
694cd0df83cSstephan      const typeIds = [];
695cd0df83cSstephan      let i = 0, offset = 1;
696cd0df83cSstephan      viewU8[0] = args.length & 0xff /* header = # of args */;
697cd0df83cSstephan      for(; i < args.length; ++i, ++offset){
698cd0df83cSstephan        /* Write the TypeIds.id value into the next args.length
699cd0df83cSstephan           bytes. */
700cd0df83cSstephan        typeIds.push(getTypeId(args[i]));
701cd0df83cSstephan        viewU8[offset] = typeIds[i].id;
702cd0df83cSstephan      }
703cd0df83cSstephan      for(i = 0; i < args.length; ++i) {
704cd0df83cSstephan        /* Deserialize the following bytes based on their
705cd0df83cSstephan           corresponding TypeIds.id from the header. */
706cd0df83cSstephan        const t = typeIds[i];
707cd0df83cSstephan        if(t.setter){
708cd0df83cSstephan          viewDV[t.setter](offset, args[i], state.littleEndian);
709cd0df83cSstephan          offset += t.size;
710cd0df83cSstephan        }else{/*String*/
711cd0df83cSstephan          const s = textEncoder.encode(args[i]);
712cd0df83cSstephan          viewDV.setInt32(offset, s.byteLength, state.littleEndian);
713cd0df83cSstephan          offset += 4;
714cd0df83cSstephan          viewU8.set(s, offset);
715cd0df83cSstephan          offset += s.byteLength;
716cd0df83cSstephan        }
717cd0df83cSstephan      }
718cd0df83cSstephan      //log("serialize() result:",viewU8.slice(0,offset));
719cd0df83cSstephan    }else{
720cd0df83cSstephan      viewU8[0] = 0;
721cd0df83cSstephan    }
722cd0df83cSstephan    metrics.s11n.serialize.time += performance.now() - t;
723cd0df83cSstephan  };
724cd0df83cSstephan
725cd0df83cSstephan  state.s11n.storeException = state.asyncS11nExceptions
726cd0df83cSstephan    ? ((priority,e)=>{
727cd0df83cSstephan      if(priority<=state.asyncS11nExceptions){
7284df2ab57Sstephan        state.s11n.serialize([e.name,': ',e.message].join(""));
729cd0df83cSstephan      }
730cd0df83cSstephan    })
731cd0df83cSstephan    : ()=>{};
732cd0df83cSstephan
733cd0df83cSstephan  return state.s11n;
734cd0df83cSstephan}/*initS11n()*/;
735cd0df83cSstephan
736cd0df83cSstephanconst waitLoop = async function f(){
737cd0df83cSstephan  const opHandlers = Object.create(null);
738cd0df83cSstephan  for(let k of Object.keys(state.opIds)){
739cd0df83cSstephan    const vi = vfsAsyncImpls[k];
740cd0df83cSstephan    if(!vi) continue;
741cd0df83cSstephan    const o = Object.create(null);
742cd0df83cSstephan    opHandlers[state.opIds[k]] = o;
743cd0df83cSstephan    o.key = k;
744cd0df83cSstephan    o.f = vi;
745cd0df83cSstephan  }
746cd0df83cSstephan  /**
747cd0df83cSstephan     waitTime is how long (ms) to wait for each Atomics.wait().
748cd0df83cSstephan     We need to wake up periodically to give the thread a chance
749cd0df83cSstephan     to do other things.
750cd0df83cSstephan  */
751*da264159Sstephan  const waitTime = 500;
752cd0df83cSstephan  while(!flagAsyncShutdown){
753cd0df83cSstephan    try {
754cd0df83cSstephan      if('timed-out'===Atomics.wait(
755cd0df83cSstephan        state.sabOPView, state.opIds.whichOp, 0, waitTime
756cd0df83cSstephan      )){
757*da264159Sstephan        if(__autoLocks.size){
758*da264159Sstephan          /* Release all auto-locks. */
759*da264159Sstephan          for(const fid of __autoLocks){
760*da264159Sstephan            const fh = __openFiles[fid];
761*da264159Sstephan            await closeSyncHandleNoThrow(fh);
762*da264159Sstephan            log("Auto-unlocked",fid,fh.filenameAbs);
763*da264159Sstephan          }
764*da264159Sstephan        }
765cd0df83cSstephan        continue;
766cd0df83cSstephan      }
767cd0df83cSstephan      const opId = Atomics.load(state.sabOPView, state.opIds.whichOp);
768cd0df83cSstephan      Atomics.store(state.sabOPView, state.opIds.whichOp, 0);
769cd0df83cSstephan      const hnd = opHandlers[opId] ?? toss("No waitLoop handler for whichOp #",opId);
770*da264159Sstephan      const args = state.s11n.deserialize(
771*da264159Sstephan        true /* clear s11n to keep the caller from confusing this with
772*da264159Sstephan                an exception string written by the upcoming
773*da264159Sstephan                operation */
774*da264159Sstephan      ) || [];
775cd0df83cSstephan      //warn("waitLoop() whichOp =",opId, hnd, args);
776cd0df83cSstephan      if(hnd.f) await hnd.f(...args);
777cd0df83cSstephan      else error("Missing callback for opId",opId);
778cd0df83cSstephan    }catch(e){
779cd0df83cSstephan      error('in waitLoop():',e);
780cd0df83cSstephan    }
781cd0df83cSstephan  }
782cd0df83cSstephan};
783cd0df83cSstephan
784cd0df83cSstephannavigator.storage.getDirectory().then(function(d){
785cd0df83cSstephan  const wMsg = (type)=>postMessage({type});
786cd0df83cSstephan  state.rootDir = d;
787cd0df83cSstephan  self.onmessage = function({data}){
788cd0df83cSstephan    switch(data.type){
789cd0df83cSstephan        case 'opfs-async-init':{
790cd0df83cSstephan          /* Receive shared state from synchronous partner */
791cd0df83cSstephan          const opt = data.args;
792cd0df83cSstephan          state.littleEndian = opt.littleEndian;
793cd0df83cSstephan          state.asyncS11nExceptions = opt.asyncS11nExceptions;
794cd0df83cSstephan          state.verbose = opt.verbose ?? 2;
795cd0df83cSstephan          state.fileBufferSize = opt.fileBufferSize;
796cd0df83cSstephan          state.sabS11nOffset = opt.sabS11nOffset;
797cd0df83cSstephan          state.sabS11nSize = opt.sabS11nSize;
798cd0df83cSstephan          state.sabOP = opt.sabOP;
799cd0df83cSstephan          state.sabOPView = new Int32Array(state.sabOP);
800cd0df83cSstephan          state.sabIO = opt.sabIO;
801cd0df83cSstephan          state.sabFileBufView = new Uint8Array(state.sabIO, 0, state.fileBufferSize);
802cd0df83cSstephan          state.sabS11nView = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize);
803cd0df83cSstephan          state.opIds = opt.opIds;
804cd0df83cSstephan          state.sq3Codes = opt.sq3Codes;
805cd0df83cSstephan          Object.keys(vfsAsyncImpls).forEach((k)=>{
806cd0df83cSstephan            if(!Number.isFinite(state.opIds[k])){
807cd0df83cSstephan              toss("Maintenance required: missing state.opIds[",k,"]");
808cd0df83cSstephan            }
809cd0df83cSstephan          });
810cd0df83cSstephan          initS11n();
811cd0df83cSstephan          metrics.reset();
812cd0df83cSstephan          log("init state",state);
813cd0df83cSstephan          wMsg('opfs-async-inited');
814cd0df83cSstephan          waitLoop();
815cd0df83cSstephan          break;
816cd0df83cSstephan        }
817cd0df83cSstephan        case 'opfs-async-restart':
818cd0df83cSstephan          if(flagAsyncShutdown){
819cd0df83cSstephan            warn("Restarting after opfs-async-shutdown. Might or might not work.");
820cd0df83cSstephan            flagAsyncShutdown = false;
821cd0df83cSstephan            waitLoop();
822cd0df83cSstephan          }
823cd0df83cSstephan          break;
824cd0df83cSstephan        case 'opfs-async-metrics':
825cd0df83cSstephan          metrics.dump();
826cd0df83cSstephan          break;
827cd0df83cSstephan    }
828cd0df83cSstephan  };
829cd0df83cSstephan  wMsg('opfs-async-loaded');
830cd0df83cSstephan}).catch((e)=>error("error initializing OPFS asyncer:",e));
831