stephan | 132a87b | 2022-09-17 15:08:22 +0000 | [diff] [blame] | 1 | /* |
| 2 | 2022-09-16 |
| 3 | |
| 4 | The author disclaims copyright to this source code. In place of a |
| 5 | legal notice, here is a blessing: |
| 6 | |
| 7 | * May you do good and not evil. |
| 8 | * May you find forgiveness for yourself and forgive others. |
| 9 | * May you share freely, never taking more than you give. |
| 10 | |
| 11 | *********************************************************************** |
| 12 | |
stephan | e6f8a09 | 2022-09-17 21:13:26 +0000 | [diff] [blame] | 13 | An INCOMPLETE and UNDER CONSTRUCTION experiment for OPFS: a Worker |
| 14 | which manages asynchronous OPFS handles on behalf of a synchronous |
| 15 | API which controls it via a combination of Worker messages, |
| 16 | SharedArrayBuffer, and Atomics. |
stephan | 132a87b | 2022-09-17 15:08:22 +0000 | [diff] [blame] | 17 | |
| 18 | Highly indebted to: |
| 19 | |
| 20 | https://github.com/rhashimoto/wa-sqlite/blob/master/src/examples/OriginPrivateFileSystemVFS.js |
| 21 | |
| 22 | for demonstrating how to use the OPFS APIs. |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 23 | |
| 24 | This file is to be loaded as a Worker. It does not have any direct |
| 25 | access to the sqlite3 JS/WASM bits, so any bits which it needs (most |
| 26 | notably SQLITE_xxx integer codes) have to be imported into it via an |
| 27 | initialization process. |
stephan | 7ff8da8 | 2022-10-03 09:21:37 +0000 | [diff] [blame] | 28 | |
| 29 | Potential TODOs: |
| 30 | |
| 31 | - When idle for "a long time", close the sync access handle in order |
| 32 | to release the lock, then re-open it on demand. Similarly, delay |
| 33 | fetching of the sync access handle until we need it. The intent |
| 34 | would be to help multi-tab access to a db avoid locking issues. |
stephan | 132a87b | 2022-09-17 15:08:22 +0000 | [diff] [blame] | 35 | */ |
| 36 | 'use strict'; |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 37 | const toss = function(...args){throw new Error(args.join(' '))}; |
| 38 | if(self.window === self){ |
| 39 | toss("This code cannot run from the main thread.", |
| 40 | "Load it as a Worker from a separate Worker."); |
| 41 | }else if(!navigator.storage.getDirectory){ |
| 42 | toss("This API requires navigator.storage.getDirectory."); |
| 43 | } |
| 44 | /** |
| 45 | Will hold state copied to this object from the syncronous side of |
| 46 | this API. |
| 47 | */ |
| 48 | const state = Object.create(null); |
| 49 | /** |
| 50 | verbose: |
| 51 | |
| 52 | 0 = no logging output |
| 53 | 1 = only errors |
| 54 | 2 = warnings and errors |
| 55 | 3 = debug, warnings, and errors |
| 56 | */ |
| 57 | state.verbose = 2; |
| 58 | |
stephan | 509f405 | 2022-09-19 09:58:01 +0000 | [diff] [blame] | 59 | const loggers = { |
| 60 | 0:console.error.bind(console), |
| 61 | 1:console.warn.bind(console), |
| 62 | 2:console.log.bind(console) |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 63 | }; |
stephan | 509f405 | 2022-09-19 09:58:01 +0000 | [diff] [blame] | 64 | const logImpl = (level,...args)=>{ |
| 65 | if(state.verbose>level) loggers[level]("OPFS asyncer:",...args); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 66 | }; |
stephan | 509f405 | 2022-09-19 09:58:01 +0000 | [diff] [blame] | 67 | const log = (...args)=>logImpl(2, ...args); |
| 68 | const warn = (...args)=>logImpl(1, ...args); |
| 69 | const error = (...args)=>logImpl(0, ...args); |
stephan | f815011 | 2022-09-19 17:09:09 +0000 | [diff] [blame] | 70 | const metrics = Object.create(null); |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 71 | metrics.reset = ()=>{ |
| 72 | let k; |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 73 | const r = (m)=>(m.count = m.time = m.wait = 0); |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 74 | for(k in state.opIds){ |
| 75 | r(metrics[k] = Object.create(null)); |
| 76 | } |
stephan | b8c8d4e | 2022-09-20 13:25:39 +0000 | [diff] [blame] | 77 | let s = metrics.s11n = Object.create(null); |
| 78 | s = s.serialize = Object.create(null); |
| 79 | s.count = s.time = 0; |
| 80 | s = metrics.s11n.deserialize = Object.create(null); |
| 81 | s.count = s.time = 0; |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 82 | }; |
| 83 | metrics.dump = ()=>{ |
| 84 | let k, n = 0, t = 0, w = 0; |
| 85 | for(k in state.opIds){ |
| 86 | const m = metrics[k]; |
| 87 | n += m.count; |
| 88 | t += m.time; |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 89 | w += m.wait; |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 90 | m.avgTime = (m.count && m.time) ? (m.time / m.count) : 0; |
| 91 | } |
| 92 | console.log(self.location.href, |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 93 | "metrics for",self.location.href,":\n", |
stephan | 56fae74 | 2022-09-24 10:12:19 +0000 | [diff] [blame] | 94 | metrics, |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 95 | "\nTotal of",n,"op(s) for",t,"ms", |
| 96 | "approx",w,"ms spent waiting on OPFS APIs."); |
stephan | 56fae74 | 2022-09-24 10:12:19 +0000 | [diff] [blame] | 97 | console.log("Serialization metrics:",metrics.s11n); |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 98 | }; |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 99 | |
stephan | f6c686c | 2022-09-30 11:01:44 +0000 | [diff] [blame] | 100 | //warn("This file is very much experimental and under construction.",self.location.pathname); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 101 | |
| 102 | /** |
| 103 | Map of sqlite3_file pointers (integers) to metadata related to a |
| 104 | given OPFS file handles. The pointers are, in this side of the |
| 105 | interface, opaque file handle IDs provided by the synchronous |
| 106 | part of this constellation. Each value is an object with a structure |
| 107 | demonstrated in the xOpen() impl. |
| 108 | */ |
| 109 | const __openFiles = Object.create(null); |
| 110 | |
| 111 | /** |
stephan | 8200a6d | 2022-09-17 23:29:27 +0000 | [diff] [blame] | 112 | Expects an OPFS file path. It gets resolved, such that ".." |
| 113 | components are properly expanded, and returned. If the 2nd |
| 114 | are is true, it's returned as an array of path elements, |
| 115 | else it's returned as an absolute path string. |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 116 | */ |
stephan | 8200a6d | 2022-09-17 23:29:27 +0000 | [diff] [blame] | 117 | const getResolvedPath = function(filename,splitIt){ |
| 118 | const p = new URL( |
| 119 | filename, 'file://irrelevant' |
| 120 | ).pathname; |
| 121 | return splitIt ? p.split('/').filter((v)=>!!v) : p; |
stephan | 509f405 | 2022-09-19 09:58:01 +0000 | [diff] [blame] | 122 | }; |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 123 | |
| 124 | /** |
| 125 | Takes the absolute path to a filesystem element. Returns an array |
| 126 | of [handleOfContainingDir, filename]. If the 2nd argument is |
| 127 | truthy then each directory element leading to the file is created |
| 128 | along the way. Throws if any creation or resolution fails. |
| 129 | */ |
stephan | 56fae74 | 2022-09-24 10:12:19 +0000 | [diff] [blame] | 130 | const getDirForFilename = async function f(absFilename, createDirs = false){ |
stephan | 8200a6d | 2022-09-17 23:29:27 +0000 | [diff] [blame] | 131 | const path = getResolvedPath(absFilename, true); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 132 | const filename = path.pop(); |
stephan | 8200a6d | 2022-09-17 23:29:27 +0000 | [diff] [blame] | 133 | let dh = state.rootDir; |
| 134 | for(const dirName of path){ |
| 135 | if(dirName){ |
| 136 | dh = await dh.getDirectoryHandle(dirName, {create: !!createDirs}); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 137 | } |
stephan | 132a87b | 2022-09-17 15:08:22 +0000 | [diff] [blame] | 138 | } |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 139 | return [dh, filename]; |
| 140 | }; |
stephan | 132a87b | 2022-09-17 15:08:22 +0000 | [diff] [blame] | 141 | |
stephan | 7ff8da8 | 2022-10-03 09:21:37 +0000 | [diff] [blame] | 142 | /** |
| 143 | Returns the sync access handle associated with the given file |
| 144 | handle object (which must be a valid handle object), lazily opening |
stephan | 5f0b67c | 2022-10-03 11:33:35 +0000 | [diff] [blame] | 145 | it if needed. Timestamps the handle for use in relinquishing it |
| 146 | during idle time. |
stephan | 7ff8da8 | 2022-10-03 09:21:37 +0000 | [diff] [blame] | 147 | */ |
stephan | 5f0b67c | 2022-10-03 11:33:35 +0000 | [diff] [blame] | 148 | const getSyncHandle = async (fh)=>{ |
| 149 | if(!fh.syncHandle){ |
| 150 | //const t = performance.now(); |
| 151 | //warn("Creating sync handle for",fh.filenameAbs); |
| 152 | fh.syncHandle = await fh.fileHandle.createSyncAccessHandle(); |
| 153 | //warn("Got sync handle for",fh.filenameAbs,'in',performance.now() - t,'ms'); |
| 154 | } |
| 155 | fh.syncHandleTime = performance.now(); |
| 156 | return fh.syncHandle; |
| 157 | }; |
| 158 | |
| 159 | const closeSyncHandle = async (fh)=>{ |
| 160 | if(fh.syncHandle){ |
| 161 | //warn("Closing sync handle for",fh.filenameAbs); |
| 162 | const h = fh.syncHandle; |
| 163 | delete fh.syncHandle; |
| 164 | return h.close(); |
| 165 | } |
| 166 | }; |
stephan | 132a87b | 2022-09-17 15:08:22 +0000 | [diff] [blame] | 167 | |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 168 | /** |
stephan | 72ab400 | 2022-09-21 12:27:35 +0000 | [diff] [blame] | 169 | Stores the given value at state.sabOPView[state.opIds.rc] and then |
| 170 | Atomics.notify()'s it. |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 171 | */ |
| 172 | const storeAndNotify = (opName, value)=>{ |
stephan | c9e2602 | 2022-09-20 10:11:52 +0000 | [diff] [blame] | 173 | log(opName+"() => notify(",state.opIds.rc,",",value,")"); |
| 174 | Atomics.store(state.sabOPView, state.opIds.rc, value); |
| 175 | Atomics.notify(state.sabOPView, state.opIds.rc); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 176 | }; |
stephan | 132a87b | 2022-09-17 15:08:22 +0000 | [diff] [blame] | 177 | |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 178 | /** |
| 179 | Throws if fh is a file-holding object which is flagged as read-only. |
| 180 | */ |
| 181 | const affirmNotRO = function(opName,fh){ |
| 182 | if(fh.readOnly) toss(opName+"(): File is read-only: "+fh.filenameAbs); |
| 183 | }; |
| 184 | |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 185 | |
| 186 | const opTimer = Object.create(null); |
| 187 | opTimer.op = undefined; |
| 188 | opTimer.start = undefined; |
| 189 | const mTimeStart = (op)=>{ |
| 190 | opTimer.start = performance.now(); |
| 191 | opTimer.op = op; |
| 192 | //metrics[op] || toss("Maintenance required: missing metrics for",op); |
| 193 | ++metrics[op].count; |
| 194 | }; |
| 195 | const mTimeEnd = ()=>( |
| 196 | metrics[opTimer.op].time += performance.now() - opTimer.start |
| 197 | ); |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 198 | const waitTimer = Object.create(null); |
| 199 | waitTimer.op = undefined; |
| 200 | waitTimer.start = undefined; |
| 201 | const wTimeStart = (op)=>{ |
| 202 | waitTimer.start = performance.now(); |
| 203 | waitTimer.op = op; |
| 204 | //metrics[op] || toss("Maintenance required: missing metrics for",op); |
| 205 | }; |
| 206 | const wTimeEnd = ()=>( |
| 207 | metrics[waitTimer.op].wait += performance.now() - waitTimer.start |
| 208 | ); |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 209 | |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 210 | /** |
stephan | 3c272ba | 2022-10-04 00:54:00 +0000 | [diff] [blame^] | 211 | Set to true by the 'opfs-async-shutdown' command to quite the wait loop. |
| 212 | This is only intended for debugging purposes: we cannot inspect this |
| 213 | file's state while the tight waitLoop() is running. |
| 214 | */ |
| 215 | let flagAsyncShutdown = false; |
| 216 | |
| 217 | /** |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 218 | Asynchronous wrappers for sqlite3_vfs and sqlite3_io_methods |
| 219 | methods. Maintenance reminder: members are in alphabetical order |
| 220 | to simplify finding them. |
| 221 | */ |
| 222 | const vfsAsyncImpls = { |
stephan | 3c272ba | 2022-10-04 00:54:00 +0000 | [diff] [blame^] | 223 | 'opfs-async-metrics': async ()=>{ |
| 224 | mTimeStart('opfs-async-metrics'); |
stephan | 56fae74 | 2022-09-24 10:12:19 +0000 | [diff] [blame] | 225 | metrics.dump(); |
stephan | 3c272ba | 2022-10-04 00:54:00 +0000 | [diff] [blame^] | 226 | storeAndNotify('opfs-async-metrics', 0); |
stephan | 56fae74 | 2022-09-24 10:12:19 +0000 | [diff] [blame] | 227 | mTimeEnd(); |
| 228 | }, |
stephan | 3c272ba | 2022-10-04 00:54:00 +0000 | [diff] [blame^] | 229 | 'opfs-async-shutdown': async ()=>{ |
| 230 | flagAsyncShutdown = true; |
| 231 | storeAndNotify('opfs-async-shutdown', 0); |
| 232 | }, |
stephan | 56fae74 | 2022-09-24 10:12:19 +0000 | [diff] [blame] | 233 | mkdir: async (dirname)=>{ |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 234 | mTimeStart('mkdir'); |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 235 | let rc = 0; |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 236 | wTimeStart('mkdir'); |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 237 | try { |
stephan | 56fae74 | 2022-09-24 10:12:19 +0000 | [diff] [blame] | 238 | await getDirForFilename(dirname+"/filepart", true); |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 239 | }catch(e){ |
stephan | 56fae74 | 2022-09-24 10:12:19 +0000 | [diff] [blame] | 240 | state.s11n.storeException(2,e); |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 241 | rc = state.sq3Codes.SQLITE_IOERR; |
stephan | 72ab400 | 2022-09-21 12:27:35 +0000 | [diff] [blame] | 242 | }finally{ |
| 243 | wTimeEnd(); |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 244 | } |
| 245 | storeAndNotify('mkdir', rc); |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 246 | mTimeEnd(); |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 247 | }, |
stephan | 56fae74 | 2022-09-24 10:12:19 +0000 | [diff] [blame] | 248 | xAccess: async (filename)=>{ |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 249 | mTimeStart('xAccess'); |
stephan | 8200a6d | 2022-09-17 23:29:27 +0000 | [diff] [blame] | 250 | /* OPFS cannot support the full range of xAccess() queries sqlite3 |
| 251 | calls for. We can essentially just tell if the file is |
| 252 | accessible, but if it is it's automatically writable (unless |
| 253 | it's locked, which we cannot(?) know without trying to open |
| 254 | it). OPFS does not have the notion of read-only. |
| 255 | |
| 256 | The return semantics of this function differ from sqlite3's |
| 257 | xAccess semantics because we are limited in what we can |
| 258 | communicate back to our synchronous communication partner: 0 = |
| 259 | accessible, non-0 means not accessible. |
| 260 | */ |
| 261 | let rc = 0; |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 262 | wTimeStart('xAccess'); |
stephan | 8200a6d | 2022-09-17 23:29:27 +0000 | [diff] [blame] | 263 | try{ |
stephan | 56fae74 | 2022-09-24 10:12:19 +0000 | [diff] [blame] | 264 | const [dh, fn] = await getDirForFilename(filename); |
stephan | 8200a6d | 2022-09-17 23:29:27 +0000 | [diff] [blame] | 265 | await dh.getFileHandle(fn); |
| 266 | }catch(e){ |
stephan | 56fae74 | 2022-09-24 10:12:19 +0000 | [diff] [blame] | 267 | state.s11n.storeException(2,e); |
stephan | 8200a6d | 2022-09-17 23:29:27 +0000 | [diff] [blame] | 268 | rc = state.sq3Codes.SQLITE_IOERR; |
stephan | 72ab400 | 2022-09-21 12:27:35 +0000 | [diff] [blame] | 269 | }finally{ |
| 270 | wTimeEnd(); |
stephan | 8200a6d | 2022-09-17 23:29:27 +0000 | [diff] [blame] | 271 | } |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 272 | storeAndNotify('xAccess', rc); |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 273 | mTimeEnd(); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 274 | }, |
| 275 | xClose: async function(fid){ |
| 276 | const opName = 'xClose'; |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 277 | mTimeStart(opName); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 278 | const fh = __openFiles[fid]; |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 279 | let rc = 0; |
| 280 | wTimeStart('xClose'); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 281 | if(fh){ |
| 282 | delete __openFiles[fid]; |
stephan | 5f0b67c | 2022-10-03 11:33:35 +0000 | [diff] [blame] | 283 | await closeSyncHandle(fh); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 284 | if(fh.deleteOnClose){ |
| 285 | try{ await fh.dirHandle.removeEntry(fh.filenamePart) } |
| 286 | catch(e){ warn("Ignoring dirHandle.removeEntry() failure of",fh,e) } |
stephan | 132a87b | 2022-09-17 15:08:22 +0000 | [diff] [blame] | 287 | } |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 288 | }else{ |
stephan | 72ab400 | 2022-09-21 12:27:35 +0000 | [diff] [blame] | 289 | state.s11n.serialize(); |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 290 | rc = state.sq3Codes.SQLITE_NOTFOUND; |
stephan | 132a87b | 2022-09-17 15:08:22 +0000 | [diff] [blame] | 291 | } |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 292 | wTimeEnd(); |
| 293 | storeAndNotify(opName, rc); |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 294 | mTimeEnd(); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 295 | }, |
stephan | c4b87be | 2022-09-20 01:28:47 +0000 | [diff] [blame] | 296 | xDelete: async function(...args){ |
| 297 | mTimeStart('xDelete'); |
| 298 | const rc = await vfsAsyncImpls.xDeleteNoWait(...args); |
| 299 | storeAndNotify('xDelete', rc); |
| 300 | mTimeEnd(); |
| 301 | }, |
stephan | 138647a | 2022-09-20 03:31:02 +0000 | [diff] [blame] | 302 | xDeleteNoWait: async function(filename, syncDir = 0, recursive = false){ |
stephan | 8200a6d | 2022-09-17 23:29:27 +0000 | [diff] [blame] | 303 | /* The syncDir flag is, for purposes of the VFS API's semantics, |
| 304 | ignored here. However, if it has the value 0x1234 then: after |
| 305 | deleting the given file, recursively try to delete any empty |
| 306 | directories left behind in its wake (ignoring any errors and |
| 307 | stopping at the first failure). |
| 308 | |
| 309 | That said: we don't know for sure that removeEntry() fails if |
| 310 | the dir is not empty because the API is not documented. It has, |
| 311 | however, a "recursive" flag which defaults to false, so |
| 312 | presumably it will fail if the dir is not empty and that flag |
| 313 | is false. |
| 314 | */ |
stephan | f386012 | 2022-09-18 17:32:35 +0000 | [diff] [blame] | 315 | let rc = 0; |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 316 | wTimeStart('xDelete'); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 317 | try { |
stephan | 8200a6d | 2022-09-17 23:29:27 +0000 | [diff] [blame] | 318 | while(filename){ |
stephan | 56fae74 | 2022-09-24 10:12:19 +0000 | [diff] [blame] | 319 | const [hDir, filenamePart] = await getDirForFilename(filename, false); |
stephan | 8200a6d | 2022-09-17 23:29:27 +0000 | [diff] [blame] | 320 | if(!filenamePart) break; |
stephan | f386012 | 2022-09-18 17:32:35 +0000 | [diff] [blame] | 321 | await hDir.removeEntry(filenamePart, {recursive}); |
stephan | 8200a6d | 2022-09-17 23:29:27 +0000 | [diff] [blame] | 322 | if(0x1234 !== syncDir) break; |
| 323 | filename = getResolvedPath(filename, true); |
| 324 | filename.pop(); |
| 325 | filename = filename.join('/'); |
| 326 | } |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 327 | }catch(e){ |
stephan | 56fae74 | 2022-09-24 10:12:19 +0000 | [diff] [blame] | 328 | state.s11n.storeException(2,e); |
stephan | f386012 | 2022-09-18 17:32:35 +0000 | [diff] [blame] | 329 | rc = state.sq3Codes.SQLITE_IOERR_DELETE; |
stephan | 132a87b | 2022-09-17 15:08:22 +0000 | [diff] [blame] | 330 | } |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 331 | wTimeEnd(); |
stephan | f386012 | 2022-09-18 17:32:35 +0000 | [diff] [blame] | 332 | return rc; |
| 333 | }, |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 334 | xFileSize: async function(fid){ |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 335 | mTimeStart('xFileSize'); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 336 | const fh = __openFiles[fid]; |
| 337 | let sz; |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 338 | wTimeStart('xFileSize'); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 339 | try{ |
stephan | 7ff8da8 | 2022-10-03 09:21:37 +0000 | [diff] [blame] | 340 | sz = await (await getSyncHandle(fh)).getSize(); |
stephan | 138647a | 2022-09-20 03:31:02 +0000 | [diff] [blame] | 341 | state.s11n.serialize(Number(sz)); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 342 | sz = 0; |
| 343 | }catch(e){ |
stephan | 56fae74 | 2022-09-24 10:12:19 +0000 | [diff] [blame] | 344 | state.s11n.storeException(2,e); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 345 | sz = state.sq3Codes.SQLITE_IOERR; |
stephan | 132a87b | 2022-09-17 15:08:22 +0000 | [diff] [blame] | 346 | } |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 347 | wTimeEnd(); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 348 | storeAndNotify('xFileSize', sz); |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 349 | mTimeEnd(); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 350 | }, |
stephan | 138647a | 2022-09-20 03:31:02 +0000 | [diff] [blame] | 351 | xOpen: async function(fid/*sqlite3_file pointer*/, filename, flags){ |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 352 | const opName = 'xOpen'; |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 353 | mTimeStart(opName); |
stephan | c4b87be | 2022-09-20 01:28:47 +0000 | [diff] [blame] | 354 | const deleteOnClose = (state.sq3Codes.SQLITE_OPEN_DELETEONCLOSE & flags); |
| 355 | const create = (state.sq3Codes.SQLITE_OPEN_CREATE & flags); |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 356 | wTimeStart('xOpen'); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 357 | try{ |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 358 | let hDir, filenamePart; |
| 359 | try { |
stephan | 56fae74 | 2022-09-24 10:12:19 +0000 | [diff] [blame] | 360 | [hDir, filenamePart] = await getDirForFilename(filename, !!create); |
stephan | 132a87b | 2022-09-17 15:08:22 +0000 | [diff] [blame] | 361 | }catch(e){ |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 362 | storeAndNotify(opName, state.sql3Codes.SQLITE_NOTFOUND); |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 363 | mTimeEnd(); |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 364 | wTimeEnd(); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 365 | return; |
stephan | 132a87b | 2022-09-17 15:08:22 +0000 | [diff] [blame] | 366 | } |
stephan | c4b87be | 2022-09-20 01:28:47 +0000 | [diff] [blame] | 367 | const hFile = await hDir.getFileHandle(filenamePart, {create}); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 368 | /** |
| 369 | wa-sqlite, at this point, grabs a SyncAccessHandle and |
stephan | 5f0b67c | 2022-10-03 11:33:35 +0000 | [diff] [blame] | 370 | assigns it to the syncHandle prop of the file state |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 371 | object, but only for certain cases and it's unclear why it |
| 372 | places that limitation on it. |
| 373 | */ |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 374 | wTimeEnd(); |
stephan | 5f0b67c | 2022-10-03 11:33:35 +0000 | [diff] [blame] | 375 | __openFiles[fid] = Object.assign(Object.create(null),{ |
stephan | 7ff8da8 | 2022-10-03 09:21:37 +0000 | [diff] [blame] | 376 | filenameAbs: filename, |
| 377 | filenamePart: filenamePart, |
| 378 | dirHandle: hDir, |
| 379 | fileHandle: hFile, |
| 380 | sabView: state.sabFileBufView, |
| 381 | readOnly: create |
| 382 | ? false : (state.sq3Codes.SQLITE_OPEN_READONLY & flags), |
| 383 | deleteOnClose: deleteOnClose |
| 384 | }); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 385 | storeAndNotify(opName, 0); |
| 386 | }catch(e){ |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 387 | wTimeEnd(); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 388 | error(opName,e); |
stephan | 56fae74 | 2022-09-24 10:12:19 +0000 | [diff] [blame] | 389 | state.s11n.storeException(1,e); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 390 | storeAndNotify(opName, state.sq3Codes.SQLITE_IOERR); |
| 391 | } |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 392 | mTimeEnd(); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 393 | }, |
stephan | 138647a | 2022-09-20 03:31:02 +0000 | [diff] [blame] | 394 | xRead: async function(fid,n,offset){ |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 395 | mTimeStart('xRead'); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 396 | let rc = 0; |
stephan | 5f0b67c | 2022-10-03 11:33:35 +0000 | [diff] [blame] | 397 | const fh = __openFiles[fid]; |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 398 | try{ |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 399 | wTimeStart('xRead'); |
stephan | 7ff8da8 | 2022-10-03 09:21:37 +0000 | [diff] [blame] | 400 | const nRead = (await getSyncHandle(fh)).read( |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 401 | fh.sabView.subarray(0, n), |
| 402 | {at: Number(offset)} |
| 403 | ); |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 404 | wTimeEnd(); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 405 | if(nRead < n){/* Zero-fill remaining bytes */ |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 406 | fh.sabView.fill(0, nRead, n); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 407 | rc = state.sq3Codes.SQLITE_IOERR_SHORT_READ; |
| 408 | } |
| 409 | }catch(e){ |
| 410 | error("xRead() failed",e,fh); |
stephan | 56fae74 | 2022-09-24 10:12:19 +0000 | [diff] [blame] | 411 | state.s11n.storeException(1,e); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 412 | rc = state.sq3Codes.SQLITE_IOERR_READ; |
| 413 | } |
| 414 | storeAndNotify('xRead',rc); |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 415 | mTimeEnd(); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 416 | }, |
stephan | 138647a | 2022-09-20 03:31:02 +0000 | [diff] [blame] | 417 | xSync: async function(fid,flags/*ignored*/){ |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 418 | mTimeStart('xSync'); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 419 | const fh = __openFiles[fid]; |
stephan | 72ab400 | 2022-09-21 12:27:35 +0000 | [diff] [blame] | 420 | let rc = 0; |
stephan | 5f0b67c | 2022-10-03 11:33:35 +0000 | [diff] [blame] | 421 | if(!fh.readOnly && fh.syncHandle){ |
stephan | 72ab400 | 2022-09-21 12:27:35 +0000 | [diff] [blame] | 422 | try { |
| 423 | wTimeStart('xSync'); |
stephan | 5f0b67c | 2022-10-03 11:33:35 +0000 | [diff] [blame] | 424 | await fh.syncHandle.flush(); |
stephan | 72ab400 | 2022-09-21 12:27:35 +0000 | [diff] [blame] | 425 | }catch(e){ |
stephan | 56fae74 | 2022-09-24 10:12:19 +0000 | [diff] [blame] | 426 | state.s11n.storeException(2,e); |
stephan | 72ab400 | 2022-09-21 12:27:35 +0000 | [diff] [blame] | 427 | }finally{ |
| 428 | wTimeEnd(); |
| 429 | } |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 430 | } |
stephan | 72ab400 | 2022-09-21 12:27:35 +0000 | [diff] [blame] | 431 | storeAndNotify('xSync',rc); |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 432 | mTimeEnd(); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 433 | }, |
stephan | 138647a | 2022-09-20 03:31:02 +0000 | [diff] [blame] | 434 | xTruncate: async function(fid,size){ |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 435 | mTimeStart('xTruncate'); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 436 | let rc = 0; |
| 437 | const fh = __openFiles[fid]; |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 438 | wTimeStart('xTruncate'); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 439 | try{ |
| 440 | affirmNotRO('xTruncate', fh); |
stephan | 7ff8da8 | 2022-10-03 09:21:37 +0000 | [diff] [blame] | 441 | await (await getSyncHandle(fh)).truncate(size); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 442 | }catch(e){ |
| 443 | error("xTruncate():",e,fh); |
stephan | 56fae74 | 2022-09-24 10:12:19 +0000 | [diff] [blame] | 444 | state.s11n.storeException(2,e); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 445 | rc = state.sq3Codes.SQLITE_IOERR_TRUNCATE; |
| 446 | } |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 447 | wTimeEnd(); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 448 | storeAndNotify('xTruncate',rc); |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 449 | mTimeEnd(); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 450 | }, |
stephan | 138647a | 2022-09-20 03:31:02 +0000 | [diff] [blame] | 451 | xWrite: async function(fid,n,offset){ |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 452 | mTimeStart('xWrite'); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 453 | let rc; |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 454 | wTimeStart('xWrite'); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 455 | try{ |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 456 | const fh = __openFiles[fid]; |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 457 | affirmNotRO('xWrite', fh); |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 458 | rc = ( |
stephan | 7ff8da8 | 2022-10-03 09:21:37 +0000 | [diff] [blame] | 459 | n === (await getSyncHandle(fh)) |
| 460 | .write(fh.sabView.subarray(0, n), |
| 461 | {at: Number(offset)}) |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 462 | ) ? 0 : state.sq3Codes.SQLITE_IOERR_WRITE; |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 463 | }catch(e){ |
| 464 | error("xWrite():",e,fh); |
stephan | 56fae74 | 2022-09-24 10:12:19 +0000 | [diff] [blame] | 465 | state.s11n.storeException(1,e); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 466 | rc = state.sq3Codes.SQLITE_IOERR_WRITE; |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 467 | }finally{ |
| 468 | wTimeEnd(); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 469 | } |
| 470 | storeAndNotify('xWrite',rc); |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 471 | mTimeEnd(); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 472 | } |
| 473 | }; |
| 474 | |
stephan | 138647a | 2022-09-20 03:31:02 +0000 | [diff] [blame] | 475 | const initS11n = ()=>{ |
stephan | b8c8d4e | 2022-09-20 13:25:39 +0000 | [diff] [blame] | 476 | /** |
stephan | 72ab400 | 2022-09-21 12:27:35 +0000 | [diff] [blame] | 477 | ACHTUNG: this code is 100% duplicated in the other half of this |
| 478 | proxy! The documentation is maintained in the "synchronous half". |
stephan | b8c8d4e | 2022-09-20 13:25:39 +0000 | [diff] [blame] | 479 | */ |
stephan | 138647a | 2022-09-20 03:31:02 +0000 | [diff] [blame] | 480 | if(state.s11n) return state.s11n; |
stephan | b8c8d4e | 2022-09-20 13:25:39 +0000 | [diff] [blame] | 481 | const textDecoder = new TextDecoder(), |
| 482 | textEncoder = new TextEncoder('utf-8'), |
| 483 | viewU8 = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize), |
| 484 | viewDV = new DataView(state.sabIO, state.sabS11nOffset, state.sabS11nSize); |
stephan | 138647a | 2022-09-20 03:31:02 +0000 | [diff] [blame] | 485 | state.s11n = Object.create(null); |
stephan | b8c8d4e | 2022-09-20 13:25:39 +0000 | [diff] [blame] | 486 | const TypeIds = Object.create(null); |
| 487 | TypeIds.number = { id: 1, size: 8, getter: 'getFloat64', setter: 'setFloat64' }; |
| 488 | TypeIds.bigint = { id: 2, size: 8, getter: 'getBigInt64', setter: 'setBigInt64' }; |
| 489 | TypeIds.boolean = { id: 3, size: 4, getter: 'getInt32', setter: 'setInt32' }; |
| 490 | TypeIds.string = { id: 4 }; |
stephan | 72ab400 | 2022-09-21 12:27:35 +0000 | [diff] [blame] | 491 | const getTypeId = (v)=>( |
| 492 | TypeIds[typeof v] |
| 493 | || toss("Maintenance required: this value type cannot be serialized.",v) |
| 494 | ); |
stephan | b8c8d4e | 2022-09-20 13:25:39 +0000 | [diff] [blame] | 495 | const getTypeIdById = (tid)=>{ |
| 496 | switch(tid){ |
stephan | 72ab400 | 2022-09-21 12:27:35 +0000 | [diff] [blame] | 497 | case TypeIds.number.id: return TypeIds.number; |
| 498 | case TypeIds.bigint.id: return TypeIds.bigint; |
| 499 | case TypeIds.boolean.id: return TypeIds.boolean; |
| 500 | case TypeIds.string.id: return TypeIds.string; |
| 501 | default: toss("Invalid type ID:",tid); |
stephan | b8c8d4e | 2022-09-20 13:25:39 +0000 | [diff] [blame] | 502 | } |
| 503 | }; |
stephan | 138647a | 2022-09-20 03:31:02 +0000 | [diff] [blame] | 504 | state.s11n.deserialize = function(){ |
stephan | b8c8d4e | 2022-09-20 13:25:39 +0000 | [diff] [blame] | 505 | ++metrics.s11n.deserialize.count; |
| 506 | const t = performance.now(); |
stephan | b8c8d4e | 2022-09-20 13:25:39 +0000 | [diff] [blame] | 507 | const argc = viewU8[0]; |
stephan | 72ab400 | 2022-09-21 12:27:35 +0000 | [diff] [blame] | 508 | const rc = argc ? [] : null; |
stephan | b8c8d4e | 2022-09-20 13:25:39 +0000 | [diff] [blame] | 509 | if(argc){ |
stephan | 72ab400 | 2022-09-21 12:27:35 +0000 | [diff] [blame] | 510 | const typeIds = []; |
| 511 | let offset = 1, i, n, v; |
stephan | b8c8d4e | 2022-09-20 13:25:39 +0000 | [diff] [blame] | 512 | for(i = 0; i < argc; ++i, ++offset){ |
| 513 | typeIds.push(getTypeIdById(viewU8[offset])); |
| 514 | } |
| 515 | for(i = 0; i < argc; ++i){ |
| 516 | const t = typeIds[i]; |
| 517 | if(t.getter){ |
| 518 | v = viewDV[t.getter](offset, state.littleEndian); |
| 519 | offset += t.size; |
stephan | 72ab400 | 2022-09-21 12:27:35 +0000 | [diff] [blame] | 520 | }else{/*String*/ |
stephan | b8c8d4e | 2022-09-20 13:25:39 +0000 | [diff] [blame] | 521 | n = viewDV.getInt32(offset, state.littleEndian); |
| 522 | offset += 4; |
| 523 | v = textDecoder.decode(viewU8.slice(offset, offset+n)); |
| 524 | offset += n; |
| 525 | } |
| 526 | rc.push(v); |
| 527 | } |
| 528 | } |
| 529 | //log("deserialize:",argc, rc); |
| 530 | metrics.s11n.deserialize.time += performance.now() - t; |
| 531 | return rc; |
| 532 | }; |
stephan | 138647a | 2022-09-20 03:31:02 +0000 | [diff] [blame] | 533 | state.s11n.serialize = function(...args){ |
stephan | b8c8d4e | 2022-09-20 13:25:39 +0000 | [diff] [blame] | 534 | const t = performance.now(); |
stephan | 72ab400 | 2022-09-21 12:27:35 +0000 | [diff] [blame] | 535 | ++metrics.s11n.serialize.count; |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 536 | if(args.length){ |
stephan | b8c8d4e | 2022-09-20 13:25:39 +0000 | [diff] [blame] | 537 | //log("serialize():",args); |
stephan | 72ab400 | 2022-09-21 12:27:35 +0000 | [diff] [blame] | 538 | const typeIds = []; |
| 539 | let i = 0, offset = 1; |
| 540 | viewU8[0] = args.length & 0xff /* header = # of args */; |
stephan | b8c8d4e | 2022-09-20 13:25:39 +0000 | [diff] [blame] | 541 | for(; i < args.length; ++i, ++offset){ |
stephan | 72ab400 | 2022-09-21 12:27:35 +0000 | [diff] [blame] | 542 | /* Write the TypeIds.id value into the next args.length |
| 543 | bytes. */ |
stephan | b8c8d4e | 2022-09-20 13:25:39 +0000 | [diff] [blame] | 544 | typeIds.push(getTypeId(args[i])); |
| 545 | viewU8[offset] = typeIds[i].id; |
| 546 | } |
| 547 | for(i = 0; i < args.length; ++i) { |
stephan | 72ab400 | 2022-09-21 12:27:35 +0000 | [diff] [blame] | 548 | /* Deserialize the following bytes based on their |
| 549 | corresponding TypeIds.id from the header. */ |
stephan | b8c8d4e | 2022-09-20 13:25:39 +0000 | [diff] [blame] | 550 | const t = typeIds[i]; |
| 551 | if(t.setter){ |
| 552 | viewDV[t.setter](offset, args[i], state.littleEndian); |
| 553 | offset += t.size; |
stephan | 72ab400 | 2022-09-21 12:27:35 +0000 | [diff] [blame] | 554 | }else{/*String*/ |
stephan | b8c8d4e | 2022-09-20 13:25:39 +0000 | [diff] [blame] | 555 | const s = textEncoder.encode(args[i]); |
| 556 | viewDV.setInt32(offset, s.byteLength, state.littleEndian); |
| 557 | offset += 4; |
| 558 | viewU8.set(s, offset); |
| 559 | offset += s.byteLength; |
| 560 | } |
| 561 | } |
| 562 | //log("serialize() result:",viewU8.slice(0,offset)); |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 563 | }else{ |
stephan | b8c8d4e | 2022-09-20 13:25:39 +0000 | [diff] [blame] | 564 | viewU8[0] = 0; |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 565 | } |
stephan | b8c8d4e | 2022-09-20 13:25:39 +0000 | [diff] [blame] | 566 | metrics.s11n.serialize.time += performance.now() - t; |
stephan | 138647a | 2022-09-20 03:31:02 +0000 | [diff] [blame] | 567 | }; |
stephan | e8afca3 | 2022-09-21 14:02:47 +0000 | [diff] [blame] | 568 | |
| 569 | state.s11n.storeException = state.asyncS11nExceptions |
stephan | 56fae74 | 2022-09-24 10:12:19 +0000 | [diff] [blame] | 570 | ? ((priority,e)=>{ |
| 571 | if(priority<=state.asyncS11nExceptions){ |
| 572 | state.s11n.serialize(e.message); |
| 573 | } |
| 574 | }) |
stephan | e8afca3 | 2022-09-21 14:02:47 +0000 | [diff] [blame] | 575 | : ()=>{}; |
| 576 | |
stephan | 138647a | 2022-09-20 03:31:02 +0000 | [diff] [blame] | 577 | return state.s11n; |
stephan | b8c8d4e | 2022-09-20 13:25:39 +0000 | [diff] [blame] | 578 | }/*initS11n()*/; |
stephan | 138647a | 2022-09-20 03:31:02 +0000 | [diff] [blame] | 579 | |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 580 | const waitLoop = async function f(){ |
stephan | 138647a | 2022-09-20 03:31:02 +0000 | [diff] [blame] | 581 | const opHandlers = Object.create(null); |
stephan | c9e2602 | 2022-09-20 10:11:52 +0000 | [diff] [blame] | 582 | for(let k of Object.keys(state.opIds)){ |
| 583 | const vi = vfsAsyncImpls[k]; |
| 584 | if(!vi) continue; |
stephan | 138647a | 2022-09-20 03:31:02 +0000 | [diff] [blame] | 585 | const o = Object.create(null); |
| 586 | opHandlers[state.opIds[k]] = o; |
| 587 | o.key = k; |
stephan | 56fae74 | 2022-09-24 10:12:19 +0000 | [diff] [blame] | 588 | o.f = vi || toss("No vfsAsyncImpls[",k,"]"); |
stephan | 138647a | 2022-09-20 03:31:02 +0000 | [diff] [blame] | 589 | } |
stephan | 5f0b67c | 2022-10-03 11:33:35 +0000 | [diff] [blame] | 590 | /** |
| 591 | waitTime is how long (ms) to wait for each Atomics.wait(). |
| 592 | We need to wait up periodically to give the thread a chance |
| 593 | to do other things. |
| 594 | */ |
| 595 | const waitTime = 250; |
| 596 | /** |
| 597 | relinquishTime defines the_approximate_ number of ms |
| 598 | after which a db sync access handle will be relinquished |
| 599 | so that we do not hold a persistent lock on it. When the following loop |
| 600 | times out while waiting, every (approximate) increment of this |
| 601 | value it will relinquish any db handles which have been idle for |
| 602 | at least this much time. |
| 603 | |
| 604 | Reaquisition of a sync handle seems to take an average of |
| 605 | 0.6-0.9ms on this dev machine but takes anywhere from 1-3ms |
| 606 | every once in a while (maybe 1 time in 5 or 10). |
| 607 | */ |
| 608 | const relinquishTime = 1000; |
| 609 | let lastOpTime = performance.now(); |
| 610 | let now; |
stephan | 3c272ba | 2022-10-04 00:54:00 +0000 | [diff] [blame^] | 611 | while(!flagAsyncShutdown){ |
stephan | 138647a | 2022-09-20 03:31:02 +0000 | [diff] [blame] | 612 | try { |
stephan | 5f0b67c | 2022-10-03 11:33:35 +0000 | [diff] [blame] | 613 | if('timed-out'===Atomics.wait( |
| 614 | state.sabOPView, state.opIds.whichOp, 0, waitTime |
| 615 | )){ |
| 616 | if(relinquishTime && |
| 617 | (lastOpTime + relinquishTime <= (now = performance.now()))){ |
| 618 | for(const fh of Object.values(__openFiles)){ |
| 619 | if(fh.syncHandle && ( |
| 620 | now - relinquishTime >= fh.syncHandleTime |
| 621 | )){ |
| 622 | //warn("Relinquishing for timeout:",fh.filenameAbs); |
stephan | 4f5bbed | 2022-10-03 13:03:41 +0000 | [diff] [blame] | 623 | await closeSyncHandle(fh) |
| 624 | /* Testing shows that we have to wait on this async |
| 625 | op to finish, else we might try to re-open it |
| 626 | before the close has run. The FS layer does not |
| 627 | retain the order those operations, apparently. */; |
stephan | 5f0b67c | 2022-10-03 11:33:35 +0000 | [diff] [blame] | 628 | } |
| 629 | } |
| 630 | } |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 631 | continue; |
| 632 | } |
stephan | 5f0b67c | 2022-10-03 11:33:35 +0000 | [diff] [blame] | 633 | lastOpTime = performance.now(); |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 634 | const opId = Atomics.load(state.sabOPView, state.opIds.whichOp); |
| 635 | Atomics.store(state.sabOPView, state.opIds.whichOp, 0); |
stephan | 138647a | 2022-09-20 03:31:02 +0000 | [diff] [blame] | 636 | const hnd = opHandlers[opId] ?? toss("No waitLoop handler for whichOp #",opId); |
stephan | 56fae74 | 2022-09-24 10:12:19 +0000 | [diff] [blame] | 637 | const args = state.s11n.deserialize() || []; |
stephan | 5f0b67c | 2022-10-03 11:33:35 +0000 | [diff] [blame] | 638 | state.s11n.serialize(/* clear s11n to keep the caller from |
| 639 | confusing this with an exception string |
| 640 | written by the upcoming operation */); |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 641 | //warn("waitLoop() whichOp =",opId, hnd, args); |
| 642 | if(hnd.f) await hnd.f(...args); |
| 643 | else error("Missing callback for opId",opId); |
stephan | 138647a | 2022-09-20 03:31:02 +0000 | [diff] [blame] | 644 | }catch(e){ |
stephan | 5f0b67c | 2022-10-03 11:33:35 +0000 | [diff] [blame] | 645 | error('in waitLoop():',e); |
stephan | 138647a | 2022-09-20 03:31:02 +0000 | [diff] [blame] | 646 | } |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 647 | }; |
stephan | 138647a | 2022-09-20 03:31:02 +0000 | [diff] [blame] | 648 | }; |
| 649 | |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 650 | navigator.storage.getDirectory().then(function(d){ |
| 651 | const wMsg = (type)=>postMessage({type}); |
| 652 | state.rootDir = d; |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 653 | self.onmessage = function({data}){ |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 654 | switch(data.type){ |
stephan | 138647a | 2022-09-20 03:31:02 +0000 | [diff] [blame] | 655 | case 'opfs-async-init':{ |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 656 | /* Receive shared state from synchronous partner */ |
stephan | 138647a | 2022-09-20 03:31:02 +0000 | [diff] [blame] | 657 | const opt = data.args; |
| 658 | state.littleEndian = opt.littleEndian; |
stephan | e8afca3 | 2022-09-21 14:02:47 +0000 | [diff] [blame] | 659 | state.asyncS11nExceptions = opt.asyncS11nExceptions; |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 660 | state.verbose = opt.verbose ?? 2; |
| 661 | state.fileBufferSize = opt.fileBufferSize; |
stephan | 138647a | 2022-09-20 03:31:02 +0000 | [diff] [blame] | 662 | state.sabS11nOffset = opt.sabS11nOffset; |
| 663 | state.sabS11nSize = opt.sabS11nSize; |
stephan | c4b87be | 2022-09-20 01:28:47 +0000 | [diff] [blame] | 664 | state.sabOP = opt.sabOP; |
| 665 | state.sabOPView = new Int32Array(state.sabOP); |
| 666 | state.sabIO = opt.sabIO; |
| 667 | state.sabFileBufView = new Uint8Array(state.sabIO, 0, state.fileBufferSize); |
stephan | 138647a | 2022-09-20 03:31:02 +0000 | [diff] [blame] | 668 | state.sabS11nView = new Uint8Array(state.sabIO, state.sabS11nOffset, state.sabS11nSize); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 669 | state.opIds = opt.opIds; |
| 670 | state.sq3Codes = opt.sq3Codes; |
| 671 | Object.keys(vfsAsyncImpls).forEach((k)=>{ |
| 672 | if(!Number.isFinite(state.opIds[k])){ |
| 673 | toss("Maintenance required: missing state.opIds[",k,"]"); |
| 674 | } |
| 675 | }); |
stephan | 138647a | 2022-09-20 03:31:02 +0000 | [diff] [blame] | 676 | initS11n(); |
stephan | aec046a | 2022-09-19 18:22:29 +0000 | [diff] [blame] | 677 | metrics.reset(); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 678 | log("init state",state); |
stephan | 138647a | 2022-09-20 03:31:02 +0000 | [diff] [blame] | 679 | wMsg('opfs-async-inited'); |
stephan | 5e8bb0a | 2022-09-20 08:27:57 +0000 | [diff] [blame] | 680 | waitLoop(); |
stephan | 0731554 | 2022-09-17 20:50:12 +0000 | [diff] [blame] | 681 | break; |
| 682 | } |
stephan | 3c272ba | 2022-10-04 00:54:00 +0000 | [diff] [blame^] | 683 | case 'opfs-async-restart': |
| 684 | if(flagAsyncShutdown){ |
| 685 | warn("Restarting after opfs-async-shutdown. Might or might not work."); |
| 686 | flagAsyncShutdown = false; |
| 687 | waitLoop(); |
| 688 | } |
| 689 | break; |
| 690 | case 'opfs-async-metrics': |
| 691 | metrics.dump(); |
| 692 | break; |
stephan | 132a87b | 2022-09-17 15:08:22 +0000 | [diff] [blame] | 693 | } |
| 694 | }; |
stephan | 138647a | 2022-09-20 03:31:02 +0000 | [diff] [blame] | 695 | wMsg('opfs-async-loaded'); |
stephan | 8200a6d | 2022-09-17 23:29:27 +0000 | [diff] [blame] | 696 | }).catch((e)=>error(e)); |