Refactoring towards being able to distinguish locking-related errors from non-locking errors in OPFS VFS operations. On a branch because it's not yet clear whether this is a misuse of the SQLITE_IOERR_... codes.

FossilOrigin-Name: 646fe2ce871d583f575f392b486bbe59658e49322dc336d79adf2edb2ee64aec
diff --git a/ext/wasm/api/sqlite3-opfs-async-proxy.js b/ext/wasm/api/sqlite3-opfs-async-proxy.js
index d32390f..8e0d615 100644
--- a/ext/wasm/api/sqlite3-opfs-async-proxy.js
+++ b/ext/wasm/api/sqlite3-opfs-async-proxy.js
@@ -136,6 +136,25 @@
 };
 
 /**
+   An error class specifically for use with getSyncHandle(), the goal
+   of which is to eventually be able to distinguish unambiguously
+   between locking-related failures and other types, noting that we
+   cannot currently do so because createSyncAccessHandle() does not
+   define its exceptions in the required level of detail.
+*/
+class GetSyncHandleError extends Error {
+  constructor(errorObject, ...msg){
+    super();
+    this.error = errorObject;
+    this.message = [
+      ...msg, ': Original exception ['+errorObject.name+']:',
+      errorObject.message
+    ].join(' ');
+    this.name = 'GetSyncHandleError';
+  }
+};
+
+/**
    Returns the sync access handle associated with the given file
    handle object (which must be a valid handle object, as created by
    xOpen()), lazily opening it if needed.
@@ -154,16 +173,17 @@
     let i = 1, ms = msBase;
     for(; true; ms = msBase * ++i){
       try {
-        //if(i<3) toss("Just testing.");
+        //if(1 || i<2) toss("Just testing getSyncHandle() exception handling.");
         //TODO? A config option which tells it to throw here
         //randomly every now and then, for testing purposes.
         fh.syncHandle = await fh.fileHandle.createSyncAccessHandle();
         break;
       }catch(e){
         if(i === maxTries){
-          toss("Error getting sync handle.",maxTries,
-               "attempts failed. ",fh.filenameAbs, ":", e.message);
-          throw e;
+          throw new GetSyncHandleError(
+            e, "Error getting sync handle.",maxTries,
+            "attempts failed.",fh.filenameAbs
+          );
         }
         warn("Error getting sync handle. Waiting",ms,
               "ms and trying again.",fh.filenameAbs,e);
@@ -199,7 +219,7 @@
    Atomics.notify()'s it.
 */
 const storeAndNotify = (opName, value)=>{
-  log(opName+"() => notify(",state.opIds.rc,",",value,")");
+  log(opName+"() => notify( rc =",value,")");
   Atomics.store(state.sabOPView, state.opIds.rc, value);
   Atomics.notify(state.sabOPView, state.opIds.rc);
 };
@@ -371,18 +391,20 @@
   xFileSize: async function(fid/*sqlite3_file pointer*/){
     mTimeStart('xFileSize');
     const fh = __openFiles[fid];
-    let sz;
+    let rc;
     wTimeStart('xFileSize');
     try{
-      sz = await (await getSyncHandle(fh)).getSize();
-      state.s11n.serialize(Number(sz));
-      sz = 0;
+      rc = await (await getSyncHandle(fh)).getSize();
+      state.s11n.serialize(Number(rc));
+      rc = 0;
     }catch(e){
       state.s11n.storeException(2,e);
-      sz = state.sq3Codes.SQLITE_IOERR;
+      rc = (e instanceof GetSyncHandleError)
+        ? state.sq3Codes.SQLITE_IOERR_LOCK
+        : state.sq3Codes.SQLITE_IOERR;
     }
     wTimeEnd();
-    storeAndNotify('xFileSize', sz);
+    storeAndNotify('xFileSize', rc);
     mTimeEnd();
   },
   xLock: async function(fid/*sqlite3_file pointer*/,
@@ -395,7 +417,9 @@
       try { await getSyncHandle(fh) }
       catch(e){
         state.s11n.storeException(1,e);
-        rc = state.sq3Codes.SQLITE_IOERR;
+        rc = (e instanceof GetSyncHandleError)
+          ? state.sq3Codes.SQLITE_IOERR_LOCK
+          : state.sq3Codes.SQLITE_IOERR;
       }
       wTimeEnd();
     }
@@ -465,7 +489,9 @@
       if(undefined===nRead) wTimeEnd();
       error("xRead() failed",e,fh);
       state.s11n.storeException(1,e);
-      rc = state.sq3Codes.SQLITE_IOERR_READ;
+      rc = (e instanceof GetSyncHandleError)
+        ? state.sq3Codes.SQLITE_IOERR_LOCK
+        : state.sq3Codes.SQLITE_IOERR_READ;
     }
     storeAndNotify('xRead',rc);
     mTimeEnd();
@@ -480,6 +506,7 @@
         await fh.syncHandle.flush();
       }catch(e){
         state.s11n.storeException(2,e);
+        rc = state.sq3Codes.SQLITE_IOERR_FSYNC;
       }
       wTimeEnd();
     }
@@ -497,7 +524,9 @@
     }catch(e){
       error("xTruncate():",e,fh);
       state.s11n.storeException(2,e);
-      rc = state.sq3Codes.SQLITE_IOERR_TRUNCATE;
+      rc = (e instanceof GetSyncHandleError)
+        ? state.sq3Codes.SQLITE_IOERR_LOCK
+        : state.sq3Codes.SQLITE_IOERR_TRUNCATE;
     }
     wTimeEnd();
     storeAndNotify('xTruncate',rc);
@@ -514,7 +543,7 @@
       try { await closeSyncHandle(fh) }
       catch(e){
         state.s11n.storeException(1,e);
-        rc = state.sq3Codes.SQLITE_IOERR;
+        rc = state.sq3Codes.SQLITE_IOERR_UNLOCK;
       }
       wTimeEnd();
     }
@@ -536,7 +565,9 @@
     }catch(e){
       error("xWrite():",e,fh);
       state.s11n.storeException(1,e);
-      rc = state.sq3Codes.SQLITE_IOERR_WRITE;
+      rc = (e instanceof GetSyncHandleError)
+        ? state.sq3Codes.SQLITE_IOERR_LOCK
+        : state.sq3Codes.SQLITE_IOERR_WRITE;
     }
     wTimeEnd();
     storeAndNotify('xWrite',rc);
@@ -641,7 +672,7 @@
   state.s11n.storeException = state.asyncS11nExceptions
     ? ((priority,e)=>{
       if(priority<=state.asyncS11nExceptions){
-        state.s11n.serialize(e.message);
+        state.s11n.serialize([e.name,': ',e.message].join(''));
       }
     })
     : ()=>{};