1# See the file LICENSE for redistribution information.
2#
3# Copyright (c) 2000,2008 Oracle.  All rights reserved.
4#
5# $Id: recd012.tcl,v 12.6 2008/01/08 20:58:53 bostic Exp $
6#
7# TEST	recd012
8# TEST	Test of log file ID management. [#2288]
9# TEST	Test recovery handling of file opens and closes.
10proc recd012 { method {start 0} \
11    {niter 49} {noutiter 25} {niniter 100} {ndbs 5} args } {
12	source ./include.tcl
13
14	set tnum "012"
15	set pagesize 512
16
17	if { $is_qnx_test } {
18		set niter 40
19	}
20
21	puts "Recd$tnum $method ($args): Test recovery file management."
22	set pgindex [lsearch -exact $args "-pagesize"]
23	if { $pgindex != -1 } {
24		puts "Recd012: skipping for specific pagesizes"
25		return
26	}
27
28	for { set i $start } { $i <= $niter } { incr i } {
29		env_cleanup $testdir
30
31		# For repeatability, we pass in the iteration number
32		# as a parameter and use that in recd012_body to seed
33		# the random number generator to randomize our operations.
34		# This lets us re-run a potentially failing iteration
35		# without having to start from the beginning and work
36		# our way to it.
37		#
38		# The number of databases ranges from 4 to 8 and is
39		# a function of $niter
40		# set ndbs [expr ($i % 5) + 4]
41
42		recd012_body \
43		    $method $ndbs $i $noutiter $niniter $pagesize $tnum $args
44	}
45}
46
47proc recd012_body { method {ndbs 5} iter noutiter niniter psz tnum {largs ""} } {
48	global alphabet rand_init fixed_len recd012_ofkey recd012_ofckptkey
49	source ./include.tcl
50
51	set largs [convert_args $method $largs]
52	set omethod [convert_method $method]
53
54	puts "\tRecd$tnum $method ($largs): Iteration $iter"
55	puts "\t\tRecd$tnum.a: Create environment and $ndbs databases."
56
57	# We run out of lockers during some of the recovery runs, so
58	# we need to make sure that we specify a DB_CONFIG that will
59	# give us enough lockers.
60	set f [open $testdir/DB_CONFIG w]
61	puts $f "set_lk_max_lockers	5000"
62	close $f
63
64	set flags "-create -txn -home $testdir"
65	set env_cmd "berkdb_env $flags"
66	error_check_good env_remove [berkdb envremove -home $testdir] 0
67	set dbenv [eval $env_cmd]
68	error_check_good dbenv [is_valid_env $dbenv] TRUE
69
70	# Initialize random number generator based on $iter.
71	berkdb srand [expr $iter + $rand_init]
72
73	# Initialize database that keeps track of number of open files (so
74	# we don't run out of descriptors).
75	set ofname of.db
76	set txn [$dbenv txn]
77	error_check_good open_txn_begin [is_valid_txn $txn $dbenv] TRUE
78	set ofdb [berkdb_open -env $dbenv -txn $txn\
79	    -create -dup -mode 0644 -btree -pagesize 512 $ofname]
80	error_check_good of_open [is_valid_db $ofdb] TRUE
81	error_check_good open_txn_commit [$txn commit] 0
82	set oftxn [$dbenv txn]
83	error_check_good of_txn [is_valid_txn $oftxn $dbenv] TRUE
84	error_check_good of_put [$ofdb put -txn $oftxn $recd012_ofkey 1] 0
85	error_check_good of_put2 [$ofdb put -txn $oftxn $recd012_ofckptkey 0] 0
86	error_check_good of_put3 [$ofdb put -txn $oftxn $recd012_ofckptkey 0] 0
87	error_check_good of_txn_commit [$oftxn commit] 0
88	error_check_good of_close [$ofdb close] 0
89
90	# Create ndbs databases to work in, and a file listing db names to
91	# pick from.
92	set f [open $testdir/dblist w]
93
94	set oflags "-auto_commit -env $dbenv \
95	    -create -mode 0644 -pagesize $psz $largs $omethod"
96	for { set i 0 } { $i < $ndbs } { incr i } {
97		# 50-50 chance of being a subdb, unless we're a queue.
98		if { [berkdb random_int 0 1] || [is_queue $method] } {
99			# not a subdb
100			set dbname recd$tnum-$i.db
101		} else {
102			# subdb
103			set dbname "recd$tnum-subdb.db s$i"
104		}
105		puts $f $dbname
106		set db [eval berkdb_open $oflags $dbname]
107		error_check_good db($i) [is_valid_db $db] TRUE
108		error_check_good db($i)_close [$db close] 0
109	}
110	close $f
111	error_check_good env_close [$dbenv close] 0
112
113	# Now we get to the meat of things.  Our goal is to do some number
114	# of opens, closes, updates, and shutdowns (simulated here by a
115	# close of all open handles and a close/reopen of the environment,
116	# with or without an envremove), matching the regular expression
117	#
118	#	((O[OUC]+S)+R+V)
119	#
120	# We'll repeat the inner + a random number up to $niniter times,
121	# and the outer + a random number up to $noutiter times.
122	#
123	# In order to simulate shutdowns, we'll perform the opens, closes,
124	# and updates in a separate process, which we'll exit without closing
125	# all handles properly.  The environment will be left lying around
126	# before we run recovery 50% of the time.
127	set out [berkdb random_int 1 $noutiter]
128	puts \
129    "\t\tRecd$tnum.b: Performing $out recoveries of up to $niniter ops."
130	for { set i 0 } { $i < $out } { incr i } {
131		set child [open "|$tclsh_path" w]
132
133		# For performance, don't source everything,
134		# just what we'll need.
135		puts $child "load $tcllib"
136		puts $child "set fixed_len $fixed_len"
137		puts $child "source $src_root/test/testutils.tcl"
138		puts $child "source $src_root/test/recd$tnum.tcl"
139
140		set rnd [expr $iter * 10000 + $i * 100 + $rand_init]
141
142		# Go.
143		berkdb debug_check
144		puts $child "recd012_dochild {$env_cmd} $rnd $i $niniter\
145		    $ndbs $tnum $method $ofname $largs"
146		close $child
147
148		# Run recovery 0-3 times.
149		set nrecs [berkdb random_int 0 3]
150		for { set j 0 } { $j < $nrecs } { incr j } {
151			berkdb debug_check
152			set ret [catch {exec $util_path/db_recover \
153			    -h $testdir} res]
154			if { $ret != 0 } {
155				puts "FAIL: db_recover returned with nonzero\
156				    exit status, output as follows:"
157				file mkdir /tmp/12out
158				set fd [open /tmp/12out/[pid] w]
159				puts $fd $res
160				close $fd
161			}
162			error_check_good recover($j) $ret 0
163		}
164	}
165
166	# Run recovery one final time;  it doesn't make sense to
167	# check integrity if we do not.
168	set ret [catch {exec $util_path/db_recover -h $testdir} res]
169	if { $ret != 0 } {
170		puts "FAIL: db_recover returned with nonzero\
171		    exit status, output as follows:"
172		puts $res
173	}
174
175	# Make sure each datum is the correct filename.
176	puts "\t\tRecd$tnum.c: Checking data integrity."
177	set dbenv [berkdb_env -create -private -home $testdir]
178	error_check_good env_open_integrity [is_valid_env $dbenv] TRUE
179	set f [open $testdir/dblist r]
180	set i 0
181	while { [gets $f dbinfo] > 0 } {
182		set db [eval berkdb_open -env $dbenv $dbinfo]
183		error_check_good dbopen($dbinfo) [is_valid_db $db] TRUE
184
185		set dbc [$db cursor]
186		error_check_good cursor [is_valid_cursor $dbc $db] TRUE
187
188		for { set dbt [$dbc get -first] } { [llength $dbt] > 0 } \
189		    { set dbt [$dbc get -next] } {
190			error_check_good integrity [lindex [lindex $dbt 0] 1] \
191			    [pad_data $method $dbinfo]
192		}
193		error_check_good dbc_close [$dbc close] 0
194		error_check_good db_close [$db close] 0
195	}
196	close $f
197	error_check_good env_close_integrity [$dbenv close] 0
198
199	# Verify
200	error_check_good verify \
201	    [verify_dir $testdir "\t\tRecd$tnum.d: " 0 0 1] 0
202}
203
204proc recd012_dochild { env_cmd rnd outiter niniter ndbs tnum method\
205    ofname args } {
206	global recd012_ofkey
207	source ./include.tcl
208	if { [is_record_based $method] } {
209		set keybase ""
210	} else {
211		set keybase .[repeat abcdefghijklmnopqrstuvwxyz 4]
212	}
213
214	# Initialize our random number generator, repeatably based on an arg.
215	berkdb srand $rnd
216
217	# Open our env.
218	set dbenv [eval $env_cmd]
219	error_check_good env_open [is_valid_env $dbenv] TRUE
220
221	# Find out how many databases appear to be open in the log--we
222	# don't want recovery to run out of filehandles.
223	set txn [$dbenv txn]
224	error_check_good child_txn_begin [is_valid_txn $txn $dbenv] TRUE
225	set ofdb [berkdb_open -env $dbenv -txn $txn $ofname]
226	error_check_good child_txn_commit [$txn commit] 0
227
228	set oftxn [$dbenv txn]
229	error_check_good of_txn [is_valid_txn $oftxn $dbenv] TRUE
230	set dbt [$ofdb get -txn $oftxn $recd012_ofkey]
231	error_check_good of_get [lindex [lindex $dbt 0] 0] $recd012_ofkey
232	set nopenfiles [lindex [lindex $dbt 0] 1]
233
234	error_check_good of_commit [$oftxn commit] 0
235
236	# Read our dbnames
237	set f [open $testdir/dblist r]
238	set i 0
239	while { [gets $f dbname($i)] > 0 } {
240		incr i
241	}
242	close $f
243
244	# We now have $ndbs extant databases.
245	# Open one of them, just to get us started.
246	set opendbs {}
247	set oflags "-env $dbenv $args"
248
249	# Start a transaction, just to get us started.
250	set curtxn [$dbenv txn]
251	error_check_good txn [is_valid_txn $curtxn $dbenv] TRUE
252
253	# Inner loop.  Do $in iterations of a random open, close, or
254	# update, where $in is between 1 and $niniter.
255	set in [berkdb random_int 1 $niniter]
256	for { set j 0 }	{ $j < $in } { incr j } {
257		set op [berkdb random_int 0 2]
258		switch $op {
259		0 {
260			# Open.
261			recd012_open
262		}
263		1 {
264			# Update.  Put random-number$keybase as key,
265			# filename as data, into random database.
266			set num_open [llength $opendbs]
267			if { $num_open == 0 } {
268				# If none are open, do an open first.
269				recd012_open
270				set num_open [llength $opendbs]
271			}
272			set n [berkdb random_int 0 [expr $num_open - 1]]
273			set pair [lindex $opendbs $n]
274			set udb [lindex $pair 0]
275			set uname [lindex $pair 1]
276
277			set key [berkdb random_int 1000 1999]$keybase
278			set data [chop_data $method $uname]
279			error_check_good put($uname,$udb,$key,$data) \
280			    [$udb put -txn $curtxn $key $data] 0
281
282			# One time in four, commit the transaction.
283			if { [berkdb random_int 0 3] == 0 && 0 } {
284				error_check_good txn_recommit \
285				    [$curtxn commit] 0
286				set curtxn [$dbenv txn]
287				error_check_good txn_reopen \
288				    [is_valid_txn $curtxn $dbenv] TRUE
289			}
290		}
291		2 {
292			# Close.
293			if { [llength $opendbs] == 0 } {
294				# If none are open, open instead of closing.
295				recd012_open
296				continue
297			}
298
299			# Commit curtxn first, lest we self-deadlock.
300			error_check_good txn_recommit [$curtxn commit] 0
301
302			# Do it.
303			set which [berkdb random_int 0 \
304			    [expr [llength $opendbs] - 1]]
305
306			set db [lindex [lindex $opendbs $which] 0]
307			error_check_good db_choice [is_valid_db $db] TRUE
308			global errorCode errorInfo
309
310			error_check_good db_close \
311			    [[lindex [lindex $opendbs $which] 0] close] 0
312
313			set opendbs [lreplace $opendbs $which $which]
314			incr nopenfiles -1
315
316			# Reopen txn.
317			set curtxn [$dbenv txn]
318			error_check_good txn_reopen \
319			    [is_valid_txn $curtxn $dbenv] TRUE
320		}
321		}
322
323		# One time in two hundred, checkpoint.
324		if { [berkdb random_int 0 199] == 0 } {
325			puts "\t\t\tRecd$tnum:\
326			    Random checkpoint after operation $outiter.$j."
327			error_check_good txn_ckpt \
328			    [$dbenv txn_checkpoint] 0
329			set nopenfiles \
330			    [recd012_nopenfiles_ckpt $dbenv $ofdb $nopenfiles]
331		}
332	}
333
334	# We have to commit curtxn.  It'd be kind of nice not to, but
335	# if we start in again without running recovery, we may block
336	# ourselves.
337	error_check_good curtxn_commit [$curtxn commit] 0
338
339	# Put back the new number of open files.
340	set oftxn [$dbenv txn]
341	error_check_good of_txn [is_valid_txn $oftxn $dbenv] TRUE
342	error_check_good of_del [$ofdb del -txn $oftxn $recd012_ofkey] 0
343	error_check_good of_put \
344	    [$ofdb put -txn $oftxn $recd012_ofkey $nopenfiles] 0
345	error_check_good of_commit [$oftxn commit] 0
346	error_check_good ofdb_close [$ofdb close] 0
347}
348
349proc recd012_open { } {
350	# This is basically an inline and has to modify curtxn,
351	# so use upvars.
352	upvar curtxn curtxn
353	upvar ndbs ndbs
354	upvar dbname dbname
355	upvar dbenv dbenv
356	upvar oflags oflags
357	upvar opendbs opendbs
358	upvar nopenfiles nopenfiles
359
360	# Return without an open if we've already opened too many files--
361	# we don't want to make recovery run out of filehandles.
362	if { $nopenfiles > 30 } {
363		#puts "skipping--too many open files"
364		return -code break
365	}
366
367	# Commit curtxn first, lest we self-deadlock.
368	error_check_good txn_recommit \
369	    [$curtxn commit] 0
370
371	# Do it.
372	set which [berkdb random_int 0 [expr $ndbs - 1]]
373
374	set db [eval berkdb_open -auto_commit $oflags $dbname($which)]
375
376	lappend opendbs [list $db $dbname($which)]
377
378	# Reopen txn.
379	set curtxn [$dbenv txn]
380	error_check_good txn_reopen [is_valid_txn $curtxn $dbenv] TRUE
381
382	incr nopenfiles
383}
384
385# Update the database containing the number of files that db_recover has
386# to contend with--we want to avoid letting it run out of file descriptors.
387# We do this by keeping track of the number of unclosed opens since the
388# checkpoint before last.
389# $recd012_ofkey stores this current value;  the two dups available
390# at $recd012_ofckptkey store the number of opens since the last checkpoint
391# previous.
392# Thus, if the current value is 17 when we do a checkpoint, and the
393# stored values are 3 and 8, the new current value (which we return)
394# is 14, and the new stored values are 8 and 6.
395proc recd012_nopenfiles_ckpt { env db nopenfiles } {
396	global recd012_ofckptkey
397	set txn [$env txn]
398	error_check_good nopenfiles_ckpt_txn [is_valid_txn $txn $env] TRUE
399
400	set dbc [$db cursor -txn $txn]
401	error_check_good db_cursor [is_valid_cursor $dbc $db] TRUE
402
403	# Get the first ckpt value and delete it.
404	set dbt [$dbc get -set $recd012_ofckptkey]
405	error_check_good set [llength $dbt] 1
406
407	set discard [lindex [lindex $dbt 0] 1]
408	error_check_good del [$dbc del] 0
409
410	set nopenfiles [expr $nopenfiles - $discard]
411
412	# Get the next ckpt value
413	set dbt [$dbc get -nextdup]
414	error_check_good set2 [llength $dbt] 1
415
416	# Calculate how many opens we've had since this checkpoint before last.
417	set onlast [lindex [lindex $dbt 0] 1]
418	set sincelast [expr $nopenfiles - $onlast]
419
420	# Put this new number at the end of the dup set.
421	error_check_good put [$dbc put -keylast $recd012_ofckptkey $sincelast] 0
422
423	# We should never deadlock since we're the only one in this db.
424	error_check_good dbc_close [$dbc close] 0
425	error_check_good txn_commit [$txn commit] 0
426
427	return $nopenfiles
428}
429
430# globals -- it's not worth passing these around, as they're constants
431set recd012_ofkey OPENFILES
432set recd012_ofckptkey CKPTS
433