# See the file LICENSE for redistribution information. # # Copyright (c) 1996, 2012 Oracle and/or its affiliates. All rights reserved. # # The procs in this file are used for replication messaging # ONLY when the default mechanism of setting up a queue of # messages in a environment is not possible. This situation # is fairly rare, but it is necessary when a replication # test simultaneously runs different versions of Berkeley DB, # because different versions cannot share an env. # # Note, all procs should be named with the suffix _noenv # so it's explicit that we are using them. # # Close up a replication group - close all message dbs. proc replclose_noenv { queuedir } { global queuedbs machids set dbs [array names queuedbs] foreach tofrom $dbs { set handle $queuedbs($tofrom) error_check_good db_close [$handle close] 0 unset queuedbs($tofrom) } set machids {} } # Create a replication group for testing. proc replsetup_noenv { queuedir } { global queuedbs machids file mkdir $queuedir # If there are any leftover handles, get rid of them. set dbs [array names queuedbs] foreach tofrom $dbs { unset queuedbs($tofrom) } set machids {} } # Send function for replication. proc replsend_noenv { control rec fromid toid flags lsn } { global is_repchild global queuedbs machids global drop drop_msg global perm_sent_list global anywhere global qtestdir testdir if { ![info exists qtestdir] } { set qtestdir $testdir } set queuedir $qtestdir/MSGQUEUEDIR set permflags [lsearch $flags "perm"] if { [llength $perm_sent_list] != 0 && $permflags != -1 } { # puts "replsend_noenv sent perm message, LSN $lsn" lappend perm_sent_list $lsn } # # If we are testing with dropped messages, then we drop every # $drop_msg time. If we do that just return 0 and don't do # anything. However, avoid dropping PAGE_REQ and LOG_REQ, because # currently recovering from those cases can take a while, and some tests # rely on the assumption that a single log_flush from the master clears # up any missing messages. # if { $drop != 0 && !([berkdb msgtype $control] eq "page_req" || [berkdb msgtype $control] eq "log_req")} { incr drop if { $drop == $drop_msg } { set drop 1 return 0 } } # XXX # -1 is DB_BROADCAST_EID if { $toid == -1 } { set machlist $machids } else { set m NULL # If we can send this anywhere, send it to the first id # we find that is neither toid or fromid. If we don't # find any other candidates, this falls back to the # original toid. if { $anywhere != 0 } { set anyflags [lsearch $flags "any"] if { $anyflags != -1 } { foreach m $machids { if { $m == $fromid || $m == $toid } { continue } set machlist [list $m] break } } } # # If we didn't find a different site, fall back # to the toid. # if { $m == "NULL" } { set machlist [list $toid] } } foreach m $machlist { # Do not broadcast to self. if { $m == $fromid } { continue } # Find the handle for the right message file. set pid [pid] set db $queuedbs($m.$fromid.$pid) set stat [catch {$db put -append [list $control $rec $fromid]} ret] } if { $is_repchild } { replready_noenv $fromid from } return 0 } # proc replmsglen_noenv { machid {tf "to"}} { global queuedbs qtestdir testdir if { ![info exists qtestdir] } { set qtestdir $testdir } set queuedir $qtestdir/MSGQUEUEDIR set orig [pwd] cd $queuedir if { $tf == "to" } { set msgdbs [glob -nocomplain ready.$machid.*] } else { set msgdbs [glob -nocomplain ready.*.$machid.*] } cd $orig return [llength $msgdbs] } # Discard all the pending messages for a particular site. proc replclear_noenv { machid {tf "to"}} { global queuedbs qtestdir testdir if { ![info exists qtestdir] } { set qtestdir $testdir } set queuedir $qtestdir/MSGQUEUEDIR set orig [pwd] cd $queuedir if { $tf == "to" } { set msgdbs [glob -nocomplain ready.$machid.*] } else { set msgdbs [glob -nocomplain ready.*.$machid.*] } foreach m $msgdbs { file delete -force $m } cd $orig set dbs [array names queuedbs] foreach tofrom $dbs { # Process only messages _to_ the specified machid. if { [string match $machid.* $tofrom] == 1 } { set db $queuedbs($tofrom) set dbc [$db cursor] for { set dbt [$dbc get -first] } \ { [llength $dbt] > 0 } \ { set dbt [$dbc get -next] } { error_check_good \ replclear($machid)_del [$dbc del] 0 } error_check_good replclear($db)_dbc_close [$dbc close] 0 } } cd $queuedir if { $tf == "to" } { set msgdbs [glob -nocomplain temp.$machid.*] } else { set msgdbs [glob -nocomplain temp.*.$machid.*] } foreach m $msgdbs { # file delete -force $m } cd $orig } # Makes messages available to replprocessqueue by closing and # renaming the message files. We ready the files for one machine # ID at a time -- just those "to" or "from" the machine we want to # process, depending on 'tf'. proc replready_noenv { machid tf } { global queuedbs machids global counter global qtestdir testdir if { ![info exists qtestdir] } { set qtestdir $testdir } set queuedir $qtestdir/MSGQUEUEDIR set pid [pid] # # Close the temporary message files for the specified machine. # Only close it if there are messages available. # set dbs [array names queuedbs] set closed {} foreach tofrom $dbs { set toidx [string first . $tofrom] set toid [string replace $tofrom $toidx end] set fidx [expr $toidx + 1] set fromidx [string first . $tofrom $fidx] # # First chop off the end, then chop off the toid # in the beginning. # set fromid [string replace $tofrom $fromidx end] set fromid [string replace $fromid 0 $toidx] if { ($tf == "to" && $machid == $toid) || \ ($tf == "from" && $machid == $fromid) } { set nkeys [stat_field $queuedbs($tofrom) \ stat "Number of keys"] if { $nkeys != 0 } { lappend closed \ [list $toid $fromid temp.$tofrom] error_check_good temp_close \ [$queuedbs($tofrom) close] 0 } } } # Rename the message files. set cwd [pwd] foreach filename $closed { set toid [lindex $filename 0] set fromid [lindex $filename 1] set fname [lindex $filename 2] set tofrom [string replace $fname 0 4] incr counter($machid) cd $queuedir # puts "$queuedir: Msg ready $fname to ready.$tofrom.$counter($machid)" file rename -force $fname ready.$tofrom.$counter($machid) cd $cwd replsetuptempfile_noenv $toid $fromid $queuedir } } # Add a machine to a replication environment. This checks # that we have not already established that machine id, and # adds the machid to the list of ids. proc repladd_noenv { machid } { global queuedbs machids counter qtestdir testdir if { ![info exists qtestdir] } { set qtestdir $testdir } set queuedir $qtestdir/MSGQUEUEDIR if { [info exists machids] } { if { [lsearch -exact $machids $machid] >= 0 } { error "FAIL: repladd_noenv: machid $machid already exists." } } set counter($machid) 0 lappend machids $machid # Create all the databases that receive messages sent _to_ # the new machid. replcreatetofiles_noenv $machid $queuedir # Create all the databases that receive messages sent _from_ # the new machid. replcreatefromfiles_noenv $machid $queuedir } # Creates all the databases that a machid needs for receiving messages # from other participants in a replication group. Used when first # establishing the temp files, but also used whenever replready_noenv moves # the temp files away, because we'll need new files for any future messages. proc replcreatetofiles_noenv { toid queuedir } { global machids foreach m $machids { # We don't need a file for a machid to send itself messages. if { $m == $toid } { continue } replsetuptempfile_noenv $toid $m $queuedir } } # Creates all the databases that a machid needs for sending messages # to other participants in a replication group. Used when first # establishing the temp files only. Replready moves files based on # recipient, so we recreate files based on the recipient, also. proc replcreatefromfiles_noenv { fromid queuedir } { global machids foreach m $machids { # We don't need a file for a machid to send itself messages. if { $m == $fromid } { continue } replsetuptempfile_noenv $m $fromid $queuedir } } proc replsetuptempfile_noenv { to from queuedir } { global queuedbs set pid [pid] # puts "Open new temp.$to.$from.$pid" set queuedbs($to.$from.$pid) [berkdb_open -create -excl -recno\ -renumber $queuedir/temp.$to.$from.$pid] error_check_good open_queuedbs [is_valid_db $queuedbs($to.$from.$pid)] TRUE } # Process a queue of messages, skipping every "skip_interval" entry. # We traverse the entire queue, but since we skip some messages, we # may end up leaving things in the queue, which should get picked up # on a later run. proc replprocessqueue_noenv { dbenv machid { skip_interval 0 } { hold_electp NONE } \ { dupmasterp NONE } { errp NONE } } { global errorCode global perm_response_list global qtestdir testdir # hold_electp is a call-by-reference variable which lets our caller # know we need to hold an election. if { [string compare $hold_electp NONE] != 0 } { upvar $hold_electp hold_elect } set hold_elect 0 # dupmasterp is a call-by-reference variable which lets our caller # know we have a duplicate master. if { [string compare $dupmasterp NONE] != 0 } { upvar $dupmasterp dupmaster } set dupmaster 0 # errp is a call-by-reference variable which lets our caller # know we have gotten an error (that they expect). if { [string compare $errp NONE] != 0 } { upvar $errp errorp } set errorp 0 set nproced 0 set queuedir $qtestdir/MSGQUEUEDIR # puts "replprocessqueue_noenv: Make ready messages to eid $machid" # Change directories temporarily so we get just the msg file name. set cwd [pwd] cd $queuedir set msgdbs [glob -nocomplain ready.$machid.*] # puts "$queuedir.$machid: My messages: $msgdbs" cd $cwd foreach msgdb $msgdbs { set db [berkdb_open $queuedir/$msgdb] set dbc [$db cursor] error_check_good process_dbc($machid) \ [is_valid_cursor $dbc $db] TRUE for { set dbt [$dbc get -first] } \ { [llength $dbt] != 0 } \ { set dbt [$dbc get -next] } { set data [lindex [lindex $dbt 0] 1] set recno [lindex [lindex $dbt 0] 0] # If skip_interval is nonzero, we want to process # messages out of order. We do this in a simple but # slimy way -- continue walking with the cursor # without processing the message or deleting it from # the queue, but do increment "nproced". The way # this proc is normally used, the precise value of # nproced doesn't matter--we just don't assume the # queues are empty if it's nonzero. Thus, if we # contrive to make sure it's nonzero, we'll always # come back to records we've skipped on a later call # to replprocessqueue. (If there really are no records, # we'll never get here.) # # Skip every skip_interval'th record (and use a # remainder other than zero so that we're guaranteed # to really process at least one record on every call). if { $skip_interval != 0 } { if { $nproced % $skip_interval == 1 } { incr nproced set dbt [$dbc get -next] continue } } # We need to remove the current message from the # queue, because we're about to end the transaction # and someone else processing messages might come in # and reprocess this message which would be bad. # error_check_good queue_remove [$dbc del] 0 # We have to play an ugly cursor game here: we # currently hold a lock on the page of messages, but # rep_process_message might need to lock the page with # a different cursor in order to send a response. So # save the next recno, close the cursor, and then # reopen and reset the cursor. If someone else is # processing this queue, our entry might have gone # away, and we need to be able to handle that. # # error_check_good dbc_process_close [$dbc close] 0 set ret [catch {$dbenv rep_process_message \ [lindex $data 2] [lindex $data 0] \ [lindex $data 1]} res] # Save all ISPERM and NOTPERM responses so we can # compare their LSNs to the LSN in the log. The # variable perm_response_list holds the entire # response so we can extract responses and LSNs as # needed. # if { [llength $perm_response_list] != 0 && \ ([is_substr $res ISPERM] || [is_substr $res NOTPERM]) } { lappend perm_response_list $res } if { $ret != 0 } { if { [string compare $errp NONE] != 0 } { set errorp "$dbenv $machid $res" } else { error "FAIL:[timestamp]\ rep_process_message returned $res" } } incr nproced if { $ret == 0 } { set rettype [lindex $res 0] set retval [lindex $res 1] # # Do nothing for 0 and NEWSITE # if { [is_substr $rettype HOLDELECTION] } { set hold_elect 1 } if { [is_substr $rettype DUPMASTER] } { set dupmaster "1 $dbenv $machid" } if { [is_substr $rettype NOTPERM] || \ [is_substr $rettype ISPERM] } { set lsnfile [lindex $retval 0] set lsnoff [lindex $retval 1] } } if { $errorp != 0 } { # Break on an error, caller wants to handle it. break } if { $hold_elect == 1 } { # Break on a HOLDELECTION, for the same reason. break } if { $dupmaster == 1 } { # Break on a DUPMASTER, for the same reason. break } } error_check_good dbc_close [$dbc close] 0 # # Check the number of keys remaining because we only # want to rename to done, message file that are # fully processed. Some message types might break # out of the loop early and we want to process # the remaining messages the next time through. # set nkeys [stat_field $db stat "Number of keys"] error_check_good db_close [$db close] 0 if { $nkeys == 0 } { set dbname [string replace $msgdb 0 5 done.] # # We have to do a special dance to get rid of the # empty messaging files because of the way Windows # handles open files marked for deletion. # On Windows, a file is marked for deletion but # does not actually get deleted until the last handle # is closed. This causes a problem when a test tries # to create a new file with a previously-used name, # and Windows believes the old file still exists. # Therefore, we rename the files before deleting them, # to guarantee they are out of the way. # file rename -force $queuedir/$msgdb $queuedir/$dbname file delete -force $queuedir/$dbname } } # Return the number of messages processed. return $nproced }