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