/ Check-in [fbbcacb1]
Login

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

Overview
Comment:Add tests for handling errors returned by xShm VFS methods.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: fbbcacb137e8f5246b88ad09331236aaa1900f60
User & Date: dan 2010-05-06 07:43:58
Context
2010-05-06
11:32
Add test cases to test the libraries handling of corrupt wal-index headers. check-in: 9465b267 user: dan tags: trunk
07:43
Add tests for handling errors returned by xShm VFS methods. check-in: fbbcacb1 user: dan tags: trunk
2010-05-05
20:00
Change the behavior of the sqlite3_wal_hook() callback. It should now return SQLITE_OK or an error code and the error code is propagated back up the stack. If a checkpoint is desired, the callback should invoke sqlite3_wal_callback() itself. check-in: 1b14195e user: drh tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to src/test_vfs.c.

337
338
339
340
341
342
343

344
345
346
347
348
349
350
...
463
464
465
466
467
468
469
470
471
472
473
474



475
476
477
478
479
480
481
...
545
546
547
548
549
550
551

552

553
554
555
556
557
558
559
...
755
756
757
758
759
760
761

762
763
764
765
766
767
768
769
770
771
772
773
774
  return PARENTVFS(pVfs)->xCurrentTime(PARENTVFS(pVfs), pTimeOut);
}

static void tvfsGrowBuffer(TestvfsShm *pShm, int reqSize, int *pNewSize){
  TestvfsBuffer *pBuffer = pShm->pBuffer;
  if( reqSize>pBuffer->n ){
    pBuffer->a = (u8 *)ckrealloc((char *)pBuffer->a, reqSize);

    pBuffer->n = reqSize;
  }
  *pNewSize = pBuffer->n;
}

static void tvfsExecTcl(
  Testvfs *p, 
................................................................................
  int reqSize,
  int *pNewSize
){
  int rc = SQLITE_OK;
  Testvfs *p = (Testvfs *)(pVfs->pAppData);
  TestvfsShm *pShm = (TestvfsShm *)pShmHandle;

  tvfsGrowBuffer(pShm, reqSize, pNewSize);
  tvfsExecTcl(p, "xShmSize", 
      Tcl_NewStringObj(pShm->pBuffer->zFile, -1), pShm->id, 0
  );
  tvfsResultCode(p, &rc);



  return rc;
}

static int tvfsShmGet(
  sqlite3_vfs *pVfs,
  sqlite3_shm *pShmHandle, 
  int reqMapSize, 
................................................................................
  int deleteFlag
){
  int rc = SQLITE_OK;
  Testvfs *p = (Testvfs *)(pVfs->pAppData);
  TestvfsShm *pShm = (TestvfsShm *)pShmHandle;
  TestvfsBuffer *pBuffer = pShm->pBuffer;


  assert( (deleteFlag!=0)==(pBuffer->nRef==1) );


  tvfsExecTcl(p, "xShmClose", 
      Tcl_NewStringObj(pShm->pBuffer->zFile, -1), pShm->id, 0
  );
  tvfsResultCode(p, &rc);

  pBuffer->nRef--;
................................................................................

  pVfs = (sqlite3_vfs *)ckalloc(sizeof(sqlite3_vfs));
  memcpy(pVfs, &tvfs_vfs, sizeof(sqlite3_vfs));
  pVfs->pAppData = (void *)p;
  pVfs->zName = p->zName;
  pVfs->mxPathname = p->pParent->mxPathname;
  pVfs->szOsFile += p->pParent->szOsFile;


  Tcl_CreateObjCommand(interp, zVfs, testvfs_obj_cmd, p, testvfs_obj_del);
  sqlite3_vfs_register(pVfs, 0);

  return TCL_OK;
}

int Sqlitetestvfs_Init(Tcl_Interp *interp){
  Tcl_CreateObjCommand(interp, "testvfs", testvfs_cmd, 0, 0);
  return TCL_OK;
}

#endif







>







 







<




>
>
>







 







>

>







 







>













337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
...
464
465
466
467
468
469
470

471
472
473
474
475
476
477
478
479
480
481
482
483
484
...
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
...
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
  return PARENTVFS(pVfs)->xCurrentTime(PARENTVFS(pVfs), pTimeOut);
}

static void tvfsGrowBuffer(TestvfsShm *pShm, int reqSize, int *pNewSize){
  TestvfsBuffer *pBuffer = pShm->pBuffer;
  if( reqSize>pBuffer->n ){
    pBuffer->a = (u8 *)ckrealloc((char *)pBuffer->a, reqSize);
    memset(&pBuffer->a[pBuffer->n], 0x55, reqSize-pBuffer->n);
    pBuffer->n = reqSize;
  }
  *pNewSize = pBuffer->n;
}

static void tvfsExecTcl(
  Testvfs *p, 
................................................................................
  int reqSize,
  int *pNewSize
){
  int rc = SQLITE_OK;
  Testvfs *p = (Testvfs *)(pVfs->pAppData);
  TestvfsShm *pShm = (TestvfsShm *)pShmHandle;


  tvfsExecTcl(p, "xShmSize", 
      Tcl_NewStringObj(pShm->pBuffer->zFile, -1), pShm->id, 0
  );
  tvfsResultCode(p, &rc);
  if( rc==SQLITE_OK ){
    tvfsGrowBuffer(pShm, reqSize, pNewSize);
  }
  return rc;
}

static int tvfsShmGet(
  sqlite3_vfs *pVfs,
  sqlite3_shm *pShmHandle, 
  int reqMapSize, 
................................................................................
  int deleteFlag
){
  int rc = SQLITE_OK;
  Testvfs *p = (Testvfs *)(pVfs->pAppData);
  TestvfsShm *pShm = (TestvfsShm *)pShmHandle;
  TestvfsBuffer *pBuffer = pShm->pBuffer;

#if 0
  assert( (deleteFlag!=0)==(pBuffer->nRef==1) );
#endif

  tvfsExecTcl(p, "xShmClose", 
      Tcl_NewStringObj(pShm->pBuffer->zFile, -1), pShm->id, 0
  );
  tvfsResultCode(p, &rc);

  pBuffer->nRef--;
................................................................................

  pVfs = (sqlite3_vfs *)ckalloc(sizeof(sqlite3_vfs));
  memcpy(pVfs, &tvfs_vfs, sizeof(sqlite3_vfs));
  pVfs->pAppData = (void *)p;
  pVfs->zName = p->zName;
  pVfs->mxPathname = p->pParent->mxPathname;
  pVfs->szOsFile += p->pParent->szOsFile;
  p->pVfs = pVfs;

  Tcl_CreateObjCommand(interp, zVfs, testvfs_obj_cmd, p, testvfs_obj_del);
  sqlite3_vfs_register(pVfs, 0);

  return TCL_OK;
}

int Sqlitetestvfs_Init(Tcl_Interp *interp){
  Tcl_CreateObjCommand(interp, "testvfs", testvfs_cmd, 0, 0);
  return TCL_OK;
}

#endif

Changes to src/wal.c.

666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682

683
684



685
686
687
688
689
690
691



692
693
694
695
696
697
698
...
706
707
708
709
710
711
712

713
714
715
716
717
718
719
720
...
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
    nBlock = 256;
  }

  *piPage = iRet;
  return (iRet==0xFFFFFFFF);
}

static WalIterator *walIteratorInit(Wal *pWal){
  u32 *aData;                     /* Content of the wal-index file */
  WalIterator *p;                 /* Return value */
  int nSegment;                   /* Number of segments to merge */
  u32 iLast;                      /* Last frame in log */
  int nByte;                      /* Number of bytes to allocate */
  int i;                          /* Iterator variable */
  int nFinal;                     /* Number of unindexed entries */
  struct WalSegment *pFinal;      /* Final (unindexed) segment */
  u8 *aTmp;                       /* Temp space used by merge-sort */


  walIndexMap(pWal, -1);



  aData = pWal->pWiData;
  iLast = pWal->hdr.iLastPg;
  nSegment = (iLast >> 8) + 1;
  nFinal = (iLast & 0x000000FF);

  nByte = sizeof(WalIterator) + (nSegment-1)*sizeof(struct WalSegment) + 512;
  p = (WalIterator *)sqlite3_malloc(nByte);




  if( p ){
    memset(p, 0, nByte);
    p->nSegment = nSegment;

    for(i=0; i<nSegment-1; i++){
      p->aSegment[i].aDbPage = &aData[walIndexEntry(i*256+1)];
................................................................................
    for(i=0; i<nFinal; i++){
      pFinal->aIndex[i] = i;
    }
    walMergesort8(pFinal->aDbPage, aTmp, pFinal->aIndex, &nFinal);
    p->nFinal = nFinal;
  }


  return p;
}

/* 
** Free a log iterator allocated by walIteratorInit().
*/
static void walIteratorFree(WalIterator *p){
  sqlite3_free(p);
................................................................................
  int rc;                         /* Return code */
  int pgsz = pWal->hdr.pgsz;      /* Database page-size */
  WalIterator *pIter = 0;         /* Wal iterator context */
  u32 iDbpage = 0;                /* Next database page to write */
  u32 iFrame = 0;                 /* Wal frame containing data for iDbpage */

  /* Allocate the iterator */
  pIter = walIteratorInit(pWal);
  if( !pIter ) return SQLITE_NOMEM;

  if( pWal->hdr.iLastPg==0 ){
    rc = SQLITE_OK;
    goto out;
  }

  if( pWal->hdr.pgsz!=nBuf ){
    rc = SQLITE_CORRUPT_BKPT;
    goto out;
  }







|









>

|
>
>
>







>
>
>







 







>
|







 







|
<
<
|
<







666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
...
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
...
741
742
743
744
745
746
747
748


749

750
751
752
753
754
755
756
    nBlock = 256;
  }

  *piPage = iRet;
  return (iRet==0xFFFFFFFF);
}

static int walIteratorInit(Wal *pWal, WalIterator **pp){
  u32 *aData;                     /* Content of the wal-index file */
  WalIterator *p;                 /* Return value */
  int nSegment;                   /* Number of segments to merge */
  u32 iLast;                      /* Last frame in log */
  int nByte;                      /* Number of bytes to allocate */
  int i;                          /* Iterator variable */
  int nFinal;                     /* Number of unindexed entries */
  struct WalSegment *pFinal;      /* Final (unindexed) segment */
  u8 *aTmp;                       /* Temp space used by merge-sort */
  int rc;                         /* Return code of walIndexMap() */

  rc = walIndexMap(pWal, -1);
  if( rc!=SQLITE_OK ){
    return rc;
  }
  aData = pWal->pWiData;
  iLast = pWal->hdr.iLastPg;
  nSegment = (iLast >> 8) + 1;
  nFinal = (iLast & 0x000000FF);

  nByte = sizeof(WalIterator) + (nSegment-1)*sizeof(struct WalSegment) + 512;
  p = (WalIterator *)sqlite3_malloc(nByte);
  if( !p ){
    return SQLITE_NOMEM;
  }

  if( p ){
    memset(p, 0, nByte);
    p->nSegment = nSegment;

    for(i=0; i<nSegment-1; i++){
      p->aSegment[i].aDbPage = &aData[walIndexEntry(i*256+1)];
................................................................................
    for(i=0; i<nFinal; i++){
      pFinal->aIndex[i] = i;
    }
    walMergesort8(pFinal->aDbPage, aTmp, pFinal->aIndex, &nFinal);
    p->nFinal = nFinal;
  }

  *pp = p;
  return SQLITE_OK;
}

/* 
** Free a log iterator allocated by walIteratorInit().
*/
static void walIteratorFree(WalIterator *p){
  sqlite3_free(p);
................................................................................
  int rc;                         /* Return code */
  int pgsz = pWal->hdr.pgsz;      /* Database page-size */
  WalIterator *pIter = 0;         /* Wal iterator context */
  u32 iDbpage = 0;                /* Next database page to write */
  u32 iFrame = 0;                 /* Wal frame containing data for iDbpage */

  /* Allocate the iterator */
  rc = walIteratorInit(pWal, &pIter);


  if( rc!=SQLITE_OK || pWal->hdr.iLastPg==0 ){

    goto out;
  }

  if( pWal->hdr.pgsz!=nBuf ){
    rc = SQLITE_CORRUPT_BKPT;
    goto out;
  }

Changes to test/walfault.test.

49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87






88





89
90
91
92



93
94
95

96
97
98
99
100
101
102
103

104

105
106

107
108
109
110
111
112
113
114
115
116
117

118
119
120
121
122
123
124
































































125
126
  sqlite3_extended_result_codes db 1
  sqlite3_db_config_lookaside db 0 0 0
} -sqlbody {
  SELECT count(*) FROM x;
}

# A [testvfs] callback for the VFS created by [do_shmfault_test]. This
# callback injects SQLITE_IOERR faults into the following methods:
#
#   xShmOpen
#   xShmSize
#   xShmGet
# 
# Faults are not injected into xShmRelease, xShmClose or xShmLock method 
# calls. The global tcl variables used are:
#
#   $::shmfault_ioerr_countdown
#   $::shmfault_ioerr_persist

#
proc shmfault_vfs_cb {method args} {

  # If ::shmfault_ioerr_countdown is not set, always return SQLITE_OK.
  #
  if {[info exists ::shmfault_ioerr_countdown]==0} { return SQLITE_OK }

  if {$method == "xShmOpen"
   || $method == "xShmSize"
   || $method == "xShmGet"
  } {
    incr ::shmfault_ioerr_countdown -1
    if { ($::shmfault_ioerr_countdown==0)
      || ($::shmfault_ioerr_countdown<=0 && $::shmfault_ioerr_persist)
    } {
      return SQLITE_IOERR
    }
  }
  return SQLITE_OK
}







proc do_shmfault_test {name args} {





  array set A $args

  # Create a VFS to use:
  testvfs shmfault shmfault_vfs_cb



  
  foreach mode {transient persistent} {
    set ::shmfault_ioerr_persist [expr {$mode == "persistent"}]

    for {set nDelay 1} {$nDelay < 10000} {incr nDelay} {
      set ::shmfault_ioerr_countdown $nDelay
  
      file delete -force test.db test.db-wal test.db-journal
      
      set rc [catch {
        sqlite3 db test.db -vfs shmfault
        db eval $A(-sqlbody)

      } msg]

      set hit_error [expr {$::shmfault_ioerr_countdown<=0}]
      unset ::shmfault_ioerr_countdown

      catch { db close }
      
      do_test $name-$mode.$nDelay.1 [list set {} $hit_error] $rc
  
      if {$hit_error==0} break
    }
  }

  shmfault delete
}


do_shmfault_test walfault-shm-1 -sqlbody {
  PRAGMA journal_mode = WAL;
  CREATE TABLE t1(a PRIMARY KEY, b);
  INSERT INTO t1 VALUES('a', 'b');
  PRAGMA wal_checkpoint;
}

































































finish_test








|
|
|
|
|






>







|
<
<
<










>
>
>
>
>
>

>
>
>
>
>




>
>
>



>

<


|
|
<
|
>
|
>


>











>







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


49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109

110
111
112
113

114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
  sqlite3_extended_result_codes db 1
  sqlite3_db_config_lookaside db 0 0 0
} -sqlbody {
  SELECT count(*) FROM x;
}

# A [testvfs] callback for the VFS created by [do_shmfault_test]. This
# callback injects SQLITE_IOERR faults into methods for which an entry
# in array ::shmfault_ioerr_methods is defined. For example, to enable
# errors in xShmOpen:
#
#   set ::shmfault_ioerr_methods(xShmOpen) 1
# 
# Faults are not injected into xShmRelease, xShmClose or xShmLock method 
# calls. The global tcl variables used are:
#
#   $::shmfault_ioerr_countdown
#   $::shmfault_ioerr_persist
#   $::shmfault_ioerr_methods
#
proc shmfault_vfs_cb {method args} {

  # If ::shmfault_ioerr_countdown is not set, always return SQLITE_OK.
  #
  if {[info exists ::shmfault_ioerr_countdown]==0} { return SQLITE_OK }

  if {[info exists ::shmfault_ioerr_methods($method)]} {



    incr ::shmfault_ioerr_countdown -1
    if { ($::shmfault_ioerr_countdown==0)
      || ($::shmfault_ioerr_countdown<=0 && $::shmfault_ioerr_persist)
    } {
      return SQLITE_IOERR
    }
  }
  return SQLITE_OK
}

# Options are:
#
#   -tclprep TCL
#   -sqlprep SQL
#   -sqlbody SQL
#
proc do_shmfault_test {name args} {

  set A(-tclprep) "sqlite3 db test.db -vfs shmfault"
  set A(-sqlprep) ""
  set A(-sqlbody) ""
  set A(-methods) [list xShmGet xShmOpen xShmSize]
  array set A $args

  # Create a VFS to use:
  testvfs shmfault shmfault_vfs_cb

  unset -nocomplain ::shmfault_ioerr_methods
  foreach m $A(-methods) { set ::shmfault_ioerr_methods($m) 1 }
  
  foreach mode {transient persistent} {
    set ::shmfault_ioerr_persist [expr {$mode == "persistent"}]

    for {set nDelay 1} {$nDelay < 10000} {incr nDelay} {

  
      file delete -force test.db test.db-wal test.db-journal

      eval $A(-tclprep)

      db eval $A(-sqlprep)

      set ::shmfault_ioerr_countdown $nDelay
      set rc [catch { db eval $A(-sqlbody) } msg]
      set hit_error [expr {$::shmfault_ioerr_countdown<=0}]
      unset ::shmfault_ioerr_countdown

      catch { db close }
      
      do_test $name-$mode.$nDelay.1 [list set {} $hit_error] $rc
  
      if {$hit_error==0} break
    }
  }

  shmfault delete
}


do_shmfault_test walfault-shm-1 -sqlbody {
  PRAGMA journal_mode = WAL;
  CREATE TABLE t1(a PRIMARY KEY, b);
  INSERT INTO t1 VALUES('a', 'b');
  PRAGMA wal_checkpoint;
}

do_shmfault_test walfault-shm-2 -methods xShmSize -sqlprep {
  PRAGMA page_size = 512;
  PRAGMA journal_mode = WAL;
  PRAGMA wal_autocheckpoint = 0;
} -sqlbody {
  CREATE TABLE t1(x);
  BEGIN;
    INSERT INTO t1 VALUES(randomblob(400));           /* 1 */
    INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 2 */
    INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 4 */
    INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 8 */
    INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 16 */
    INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 32 */
    INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 64 */
    INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 128 */
    INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 256 */
    INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 512 */
    INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 1024 */
    INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 2048 */
    INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 4096 */
    INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 8192 */
    INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 16384 */
  COMMIT;
}

do_shmfault_test walfault-shm-3 -methods xShmSize -tclprep {
  sqlite3 db test.db -vfs shmfault
  unset -nocomplain ::shmfault_ioerr_countdown
  db eval {
    PRAGMA page_size = 512;
    PRAGMA journal_mode = WAL;
    PRAGMA wal_autocheckpoint = 0;
    CREATE TABLE t1(x);
    BEGIN;
      INSERT INTO t1 VALUES(randomblob(400));           /* 1 */
      INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 2 */
      INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 4 */
      INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 8 */
      INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 16 */
      INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 32 */
      INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 64 */
      INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 128 */
      INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 256 */
      INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 512 */
      INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 1024 */
      INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 2048 */
      INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 4096 */
      INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 8192 */
      INSERT INTO t1 SELECT randomblob(400) FROM t1;    /* 16384 */
    COMMIT;
  }

  set ::shmfault_ioerr_countdown 1
  set ::shmfault_ioerr_methods(xShmGet) 1
  db close
  unset ::shmfault_ioerr_methods(xShmGet)
  if {[file exists test.db-wal]==0} {error "Failed to create WAL file!"}

  sqlite3 db test.db -vfs shmfault
breakpoint
} -sqlbody {
  SELECT count(*) FROM t1;
}

finish_test