|
1 # 2008 May 12 |
|
2 # |
|
3 # The author disclaims copyright to this source code. In place of |
|
4 # a legal notice, here is a blessing: |
|
5 # |
|
6 # May you do good and not evil. |
|
7 # May you find forgiveness for yourself and forgive others. |
|
8 # May you share freely, never taking more than you give. |
|
9 # |
|
10 #*********************************************************************** |
|
11 # |
|
12 # This file tests that if sqlite3_release_memory() is called to reclaim |
|
13 # memory from a pager that is in the error-state, SQLite does not |
|
14 # incorrectly write dirty pages out to the database (not safe to do |
|
15 # once the pager is in error state). |
|
16 # |
|
17 # $Id: ioerr5.test,v 1.5 2008/08/28 18:35:34 danielk1977 Exp $ |
|
18 |
|
19 set testdir [file dirname $argv0] |
|
20 source $testdir/tester.tcl |
|
21 |
|
22 ifcapable !memorymanage||!shared_cache { |
|
23 finish_test |
|
24 return |
|
25 } |
|
26 |
|
27 db close |
|
28 |
|
29 set ::enable_shared_cache [sqlite3_enable_shared_cache 1] |
|
30 set ::soft_limit [sqlite3_soft_heap_limit 1048576] |
|
31 |
|
32 # This procedure prepares, steps and finalizes an SQL statement via the |
|
33 # UTF-16 APIs. The text representation of an SQLite error code is returned |
|
34 # ("SQLITE_OK", "SQLITE_IOERR" etc.). The actual results returned by the |
|
35 # SQL statement, if it is a SELECT, are not available. |
|
36 # |
|
37 # This can be useful for testing because it forces SQLite to make an extra |
|
38 # call to sqlite3_malloc() when translating from the supplied UTF-16 to |
|
39 # the UTF-8 encoding used internally. |
|
40 # |
|
41 proc dosql16 {zSql {db db}} { |
|
42 set sql [encoding convertto unicode $zSql] |
|
43 append sql "\00\00" |
|
44 set stmt [sqlite3_prepare16 $db $sql -1 {}] |
|
45 sqlite3_step $stmt |
|
46 set rc [sqlite3_finalize $stmt] |
|
47 } |
|
48 |
|
49 proc compilesql16 {zSql {db db}} { |
|
50 set sql [encoding convertto unicode $zSql] |
|
51 append sql "\00\00" |
|
52 set stmt [sqlite3_prepare16 $db $sql -1 {}] |
|
53 set rc [sqlite3_finalize $stmt] |
|
54 } |
|
55 |
|
56 # Open two database connections (handle db and db2) to database "test.db". |
|
57 # |
|
58 proc opendatabases {} { |
|
59 catch {db close} |
|
60 catch {db2 close} |
|
61 sqlite3 db test.db |
|
62 sqlite3 db2 test.db |
|
63 db2 cache size 0 |
|
64 db cache size 0 |
|
65 execsql { |
|
66 pragma page_size=512; |
|
67 pragma auto_vacuum=2; |
|
68 pragma cache_size=16; |
|
69 } |
|
70 } |
|
71 |
|
72 # Open two database connections and create a single table in the db. |
|
73 # |
|
74 do_test ioerr5-1.0 { |
|
75 opendatabases |
|
76 execsql { CREATE TABLE A(Id INTEGER, Name TEXT) } |
|
77 } {} |
|
78 |
|
79 foreach locking_mode {normal exclusive} { |
|
80 set nPage 2 |
|
81 for {set iFail 1} {$iFail<200} {incr iFail} { |
|
82 sqlite3_soft_heap_limit 1048576 |
|
83 opendatabases |
|
84 execsql { pragma locking_mode=exclusive } |
|
85 set nRow [db one {SELECT count(*) FROM a}] |
|
86 |
|
87 # Dirty (at least) one of the pages in the cache. |
|
88 do_test ioerr5-1.$locking_mode-$iFail.1 { |
|
89 execsql { |
|
90 BEGIN EXCLUSIVE; |
|
91 INSERT INTO a VALUES(1, 'ABCDEFGHIJKLMNOP'); |
|
92 } |
|
93 } {} |
|
94 |
|
95 # Now try to commit the transaction. Cause an IO error to occur |
|
96 # within this operation, which moves the pager into the error state. |
|
97 # |
|
98 set ::sqlite_io_error_persist 1 |
|
99 set ::sqlite_io_error_pending $iFail |
|
100 do_test ioerr5-1.$locking_mode-$iFail.2 { |
|
101 set rc [catchsql {COMMIT}] |
|
102 list |
|
103 } {} |
|
104 set ::sqlite_io_error_hit 0 |
|
105 set ::sqlite_io_error_persist 0 |
|
106 set ::sqlite_io_error_pending 0 |
|
107 |
|
108 # Read the contents of the database file into a Tcl variable. |
|
109 # |
|
110 set fd [open test.db] |
|
111 fconfigure $fd -translation binary -encoding binary |
|
112 set zDatabase [read $fd] |
|
113 close $fd |
|
114 |
|
115 # Set a very low soft-limit and then try to compile an SQL statement |
|
116 # from UTF-16 text. To do this, SQLite will need to reclaim memory |
|
117 # from the pager that is in error state. Including that associated |
|
118 # with the dirty page. |
|
119 # |
|
120 do_test ioerr5-1.$locking_mode-$iFail.3 { |
|
121 set bt [btree_from_db db] |
|
122 sqlite3_soft_heap_limit 1024 |
|
123 compilesql16 "SELECT 10" |
|
124 array set stats [btree_pager_stats $bt] |
|
125 |
|
126 # If the pager made it all the way to PAGER_SYNCED state, then |
|
127 # both in-memory pages are clean. Following the calls to |
|
128 # release_memory() that were made as part of the [compilesql16] |
|
129 # above, there will be zero pages left in the cache. |
|
130 # |
|
131 # If the pager did not make it as far as PAGER_SYNCED, the two |
|
132 # in memory pages are still dirty. So there will be 2 pages left |
|
133 # in the cache following the release_memory() calls. |
|
134 # |
|
135 if {$stats(state)==5} { |
|
136 set nPage 0 |
|
137 } |
|
138 expr {$stats(page)==$nPage} |
|
139 } {1} |
|
140 |
|
141 # Ensure that nothing was written to the database while reclaiming |
|
142 # memory from the pager in error state. |
|
143 # |
|
144 do_test ioerr5-1.$locking_mode-$iFail.4 { |
|
145 set fd [open test.db] |
|
146 fconfigure $fd -translation binary -encoding binary |
|
147 set zDatabase2 [read $fd] |
|
148 close $fd |
|
149 expr {$zDatabase eq $zDatabase2} |
|
150 } {1} |
|
151 |
|
152 if {$rc eq [list 0 {}]} { |
|
153 do_test ioerr5.1-$locking_mode-$iFail.3 { |
|
154 execsql { SELECT count(*) FROM a } |
|
155 } [expr $nRow+1] |
|
156 break |
|
157 } |
|
158 } |
|
159 } |
|
160 |
|
161 # Make sure this test script doesn't leave any files open. |
|
162 # |
|
163 do_test ioerr5-1.X { |
|
164 catch { db close } |
|
165 catch { db2 close } |
|
166 set sqlite_open_file_count |
|
167 } 0 |
|
168 |
|
169 do_test ioerr5-2.0 { |
|
170 sqlite3 db test.db |
|
171 execsql { CREATE INDEX i1 ON a(id, name); } |
|
172 } {} |
|
173 |
|
174 foreach locking_mode {exclusive normal} { |
|
175 for {set iFail 1} {$iFail<200} {incr iFail} { |
|
176 sqlite3_soft_heap_limit 1048576 |
|
177 opendatabases |
|
178 execsql { pragma locking_mode=exclusive } |
|
179 set nRow [db one {SELECT count(*) FROM a}] |
|
180 |
|
181 do_test ioerr5-2.$locking_mode-$iFail.1 { |
|
182 execsql { |
|
183 BEGIN EXCLUSIVE; |
|
184 INSERT INTO a VALUES(1, 'ABCDEFGHIJKLMNOP'); |
|
185 } |
|
186 } {} |
|
187 |
|
188 set ::sqlite_io_error_persist 1 |
|
189 set ::sqlite_io_error_pending $iFail |
|
190 |
|
191 sqlite3_release_memory 10000 |
|
192 |
|
193 set error_hit $::sqlite_io_error_hit |
|
194 set ::sqlite_io_error_hit 0 |
|
195 set ::sqlite_io_error_persist 0 |
|
196 set ::sqlite_io_error_pending 0 |
|
197 if {$error_hit} { |
|
198 do_test ioerr5-2.$locking_mode-$iFail.3a { |
|
199 catchsql COMMIT |
|
200 } {1 {disk I/O error}} |
|
201 } else { |
|
202 do_test ioerr5-2.$locking_mode-$iFail.3b { |
|
203 execsql COMMIT |
|
204 } {} |
|
205 break |
|
206 } |
|
207 } |
|
208 } |
|
209 |
|
210 # Make sure this test script doesn't leave any files open. |
|
211 # |
|
212 do_test ioerr5-2.X { |
|
213 catch { db close } |
|
214 catch { db2 close } |
|
215 set sqlite_open_file_count |
|
216 } 0 |
|
217 |
|
218 sqlite3_enable_shared_cache $::enable_shared_cache |
|
219 sqlite3_soft_heap_limit $::soft_limit |
|
220 |
|
221 finish_test |