SQLite

Check-in [587ed3a5]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Doc updates in JS code. No functional changes.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 587ed3a5d283898ad0e67ccee86a0a4ccc432fa292c0a3534e9e6ec70a7b7780
User & Date: stephan 2024-06-12 12:17:03
Context
2024-06-12
12:36
Fix a potential db corruption case triggered by the OPFS VFS's xCheckReservedLock() implementation, as discussed in forum thread a2f573b00cda1372. (check-in: c298b8ba user: stephan tags: trunk)
12:17
Doc updates in JS code. No functional changes. (check-in: 587ed3a5 user: stephan tags: trunk)
11:39
Slight API doc tweak for xCheckReservedLock(), based on forum feedback. No code changes. (check-in: 2af7a96f user: stephan tags: trunk)
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to ext/wasm/api/sqlite3-opfs-async-proxy.js.

169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
      }
    }
    return [dh, filename];
  };

  /**
     If the given file-holding object has a sync handle attached to it,
     that handle is remove and asynchronously closed. Though it may
     sound sensible to continue work as soon as the close() returns
     (noting that it's asynchronous), doing so can cause operations
     performed soon afterwards, e.g. a call to getSyncHandle() to fail
     because they may happen out of order from the close(). OPFS does
     not guaranty that the actual order of operations is retained in
     such cases. i.e.  always "await" on the result of this function.
  */
  const closeSyncHandle = async (fh)=>{
    if(fh.syncHandle){
      log("Closing sync handle for",fh.filenameAbs);







|


|







169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
      }
    }
    return [dh, filename];
  };

  /**
     If the given file-holding object has a sync handle attached to it,
     that handle is removed and asynchronously closed. Though it may
     sound sensible to continue work as soon as the close() returns
     (noting that it's asynchronous), doing so can cause operations
     performed soon afterwards, e.g. a call to getSyncHandle(), to fail
     because they may happen out of order from the close(). OPFS does
     not guaranty that the actual order of operations is retained in
     such cases. i.e.  always "await" on the result of this function.
  */
  const closeSyncHandle = async (fh)=>{
    if(fh.syncHandle){
      log("Closing sync handle for",fh.filenameAbs);
289
290
291
292
293
294
295














296
297
298
299
300
301
302

     In order to help alleviate cross-tab contention for a dabase, if
     an exception is thrown while acquiring the handle, this routine
     will wait briefly and try again, up to some fixed number of
     times. If acquisition still fails at that point it will give up
     and propagate the exception. Client-level code will see that as
     an I/O error.














  */
  const getSyncHandle = async (fh,opName)=>{
    if(!fh.syncHandle){
      const t = performance.now();
      log("Acquiring sync handle for",fh.filenameAbs);
      const maxTries = 6,
            msBase = state.asyncIdleWaitTime * 2;







>
>
>
>
>
>
>
>
>
>
>
>
>
>







289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316

     In order to help alleviate cross-tab contention for a dabase, if
     an exception is thrown while acquiring the handle, this routine
     will wait briefly and try again, up to some fixed number of
     times. If acquisition still fails at that point it will give up
     and propagate the exception. Client-level code will see that as
     an I/O error.

     2024-06-12: there is a rare race condition here which has been
     reported a single time:

     https://sqlite.org/forum/forumpost/9ee7f5340802d600

     What appears to be happening is that file we're waiting for a
     lock on is deleted while we wait. What currently happens here is
     that a locking exception is thrown but the exception type is
     NotFoundError. In such cases, we very probably should attempt to
     re-open/re-create the file an obtain the lock on it (noting that
     there's another race condition there). That's easy to say but
     creating a viable test for that condition has proven challenging
     so far.
  */
  const getSyncHandle = async (fh,opName)=>{
    if(!fh.syncHandle){
      const t = performance.now();
      log("Acquiring sync handle for",fh.filenameAbs);
      const maxTries = 6,
            msBase = state.asyncIdleWaitTime * 2;
670
671
672
673
674
675
676

677
678


679
680
681
682
683
684
685
      mTimeEnd();
    },
    xUnlock: async function(fid/*sqlite3_file pointer*/,
                            lockType/*SQLITE_LOCK_...*/){
      mTimeStart('xUnlock');
      let rc = 0;
      const fh = __openFiles[fid];

      if( state.sq3Codes.SQLITE_LOCK_NONE===lockType
          && fh.syncHandle ){


        wTimeStart('xUnlock');
        try { await closeSyncHandle(fh) }
        catch(e){
          state.s11n.storeException(1,e);
          rc = state.sq3Codes.SQLITE_IOERR_UNLOCK;
        }
        wTimeEnd();







>
|
<
>
>







684
685
686
687
688
689
690
691
692

693
694
695
696
697
698
699
700
701
      mTimeEnd();
    },
    xUnlock: async function(fid/*sqlite3_file pointer*/,
                            lockType/*SQLITE_LOCK_...*/){
      mTimeStart('xUnlock');
      let rc = 0;
      const fh = __openFiles[fid];
      if( fh.syncHandle
          && state.sq3Codes.SQLITE_LOCK_NONE===lockType

          /* Note that we do not differentiate between lock types in
             this VFS. We're either locked or unlocked. */ ){
        wTimeStart('xUnlock');
        try { await closeSyncHandle(fh) }
        catch(e){
          state.s11n.storeException(1,e);
          rc = state.sq3Codes.SQLITE_IOERR_UNLOCK;
        }
        wTimeEnd();

Changes to ext/wasm/common/whwasmutil.js.

152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
     memory and returning its pointer. In Emscripten this is
     conventionally made available via `Module['_malloc']`. This API
     requires that the alloc routine throw on allocation error, as
     opposed to returning null or 0.

   - 'dealloc()` must behave like C's `free()`, accepting either a
     pointer returned from its allocation counterpart or the values
     null/0 (for which it must be a no-op). allocating N bytes of
     memory and returning its pointer. In Emscripten this is
     conventionally made available via `Module['_free']`.

   APIs which require allocation routines are explicitly documented as
   such and/or have "alloc" in their names.

   This code is developed and maintained in conjunction with the
   Jaccwabyt project:







|
<







152
153
154
155
156
157
158
159

160
161
162
163
164
165
166
     memory and returning its pointer. In Emscripten this is
     conventionally made available via `Module['_malloc']`. This API
     requires that the alloc routine throw on allocation error, as
     opposed to returning null or 0.

   - 'dealloc()` must behave like C's `free()`, accepting either a
     pointer returned from its allocation counterpart or the values
     null/0 (for which it must be a no-op). In Emscripten this is

     conventionally made available via `Module['_free']`.

   APIs which require allocation routines are explicitly documented as
   such and/or have "alloc" in their names.

   This code is developed and maintained in conjunction with the
   Jaccwabyt project:
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745

746
747
748
749
750
751
752
753
754
755
      }
      if(list) list.push(rc);
    }while(list && arguments[0].length);
    return list || rc;
  };

  /**
     The counterpart of peek(), this sets a numeric value at
     the given WASM heap address, using the type to define how many
     bytes are written. Throws if given an invalid type. See
     peek() for details about the type argument. If the 3rd
     argument ends with `*` then it is treated as a pointer type and
     this function behaves as if the 3rd argument were `i32`.

     If the first argument is an array, it is treated like a list
     of pointers and the given value is written to each one.

     Returns `this`. (Prior to 2022-12-09 it returns this function.)

     ACHTUNG: calling this often, e.g. in a loop, can have a noticably

     painful impact on performance. Rather than doing so, use
     heapForSize() to fetch the heap object and assign directly to it
     or use the heap's set() method.
  */
  target.poke = function(ptr, value, type='i8'){
    if (type.endsWith('*')) type = ptrIR;
    const c = (cache.memory && cache.heapSize === cache.memory.buffer.byteLength)
          ? cache : heapWrappers();
    for(const p of (Array.isArray(ptr) ? ptr : [ptr])){
      switch (type) {







|
|
|
|
|
|




|

|
>
|
|
|







725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
      }
      if(list) list.push(rc);
    }while(list && arguments[0].length);
    return list || rc;
  };

  /**
     The counterpart of peek(), this sets a numeric value at the given
     WASM heap address, using the 3rd argument to define how many
     bytes are written. Throws if given an invalid type. See peek()
     for details about the `type` argument. If the 3rd argument ends
     with `*` then it is treated as a pointer type and this function
     behaves as if the 3rd argument were `i32`.

     If the first argument is an array, it is treated like a list
     of pointers and the given value is written to each one.

     Returns `this`. (Prior to 2022-12-09 it returned this function.)

     ACHTUNG: calling this often, e.g. in a loop to populate a large
     chunk of memory, can have a noticably painful impact on
     performance. Rather than doing so, use heapForSize() to fetch the
     heap object and assign directly to it or use the heap's set()
     method.
  */
  target.poke = function(ptr, value, type='i8'){
    if (type.endsWith('*')) type = ptrIR;
    const c = (cache.memory && cache.heapSize === cache.memory.buffer.byteLength)
          ? cache : heapWrappers();
    for(const p of (Array.isArray(ptr) ? ptr : [ptr])){
      switch (type) {