#!/usr/bonsaitools/bin/perl -w # -*- Mode: perl; indent-tabs-mode: nil -*- # # The contents of this file are subject to the Mozilla Public # License Version 1.1 (the "License"); you may not use this file # except in compliance with the License. You may obtain a copy of # the License at http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or # implied. See the License for the specific language governing # rights and limitations under the License. # # The Original Code is the Bugzilla Bug Tracking System. # # The Initial Developer of the Original Code is Netscape Communications # Corporation. Portions created by Netscape are # Copyright (C) 1998 Netscape Communications Corporation. All # Rights Reserved. # # Contributor(s): Terry Weissman # David Gardiner use diagnostics; use strict; require "globals.pl"; require "defparams.pl"; # Shut up misguided -w warnings about "used only once". "use vars" just # doesn't work for me. sub sillyness { my $zz; $zz = $::db; $zz = $::dbwritesallowed; } my $verbose = 0; my $syncall = 0; sub Usage { print "Usage: syncshadowdb [-v] [-syncall]\n"; exit; } foreach my $opt (@ARGV) { if ($opt eq '-v') { $verbose = 1; } elsif ($opt eq '-syncall') { $syncall = 1; $verbose = 1; } else { Usage(); } } $| = 1; my $logtostderr = 0; sub Verbose ($) { my ($str) = (@_); if ($verbose) { if ($logtostderr) { print STDERR $str, "\n"; } else { print $str, "\n"; } } } my $db_name = "bugs"; require "localconfig"; if (!Param("shadowdb")) { Verbose("We don't have shadow databases turned on; no syncing performed."); exit; } my $wasusing = Param("queryagainstshadowdb"); $::param{'queryagainstshadowdb'} = 1; # Force us to be able to use the # shadowdb, even if other processes # are not supposed to. ConnectToDatabase(1); Verbose("Acquiring lock"); if ( $syncall == 1) { SendSQL("SELECT GET_LOCK('synclock', 2700)"); } else { SendSQL("SELECT GET_LOCK('synclock', 1)"); } if (!FetchOneColumn()) { Verbose("Couldn't get the lock to do the shadow database syncing."); exit; } my $shadowtable = "$db_name.shadowlog"; if (!$syncall) { Verbose("Looking for requests to sync the whole database."); SendSQL("SELECT id FROM $shadowtable " . "WHERE reflected = 0 AND command = 'SYNCUP'"); if (FetchOneColumn()) { $syncall = 1; } } if ($syncall) { Verbose("Syncing up the shadow database by copying entire database in."); if ($wasusing) { $::param{'queryagainstshadowdb'} = 0; WriteParams(); Verbose("Disabled reading from the shadowdb. Sleeping 10 seconds to let other procs catch up."); sleep(10); $::param{'queryagainstshadowdb'} = 1; } my @tables; SendSQL("SHOW TABLES"); my $query = ""; while (MoreSQLData()) { my $table = FetchOneColumn(); push(@tables, $table); if ($query) { $query .= ", $table WRITE"; } else { $query = "LOCK TABLES $table WRITE"; } } if (@tables) { Verbose("Locking entire shadow database"); SendSQL($query); foreach my $table (@tables) { Verbose("Dropping old shadow table $table"); SendSQL("DROP TABLE $table"); } SendSQL("UNLOCK TABLES"); } # Carefully lock the whole real database for reading, except for the # shadowlog table, which we lock for writing. Then dump everything # into the shadowdb database. Then mark everything in the shadowlog # as reflected. Only then unlock everything. This sequence causes # us to be sure not to miss anything or get something twice. SendSQL("USE $db_name"); SendSQL("SHOW TABLES"); @tables = (); $query = "LOCK TABLES shadowlog WRITE"; while (MoreSQLData()) { my $table = FetchOneColumn(); if ($table ne "shadowlog") { $query .= ", $table READ"; push(@tables, $table); } } Verbose("Locking entire database"); SendSQL($query); my $tablelist = join(' ', @tables); my $tempfile = "data/tmpsyncshadow.$$"; Verbose("Dumping database to a temp file ($tempfile)."); system("mysqldump -l -e $db_name $tablelist > $tempfile"); Verbose("Restoring from tempfile into shadowdb"); my $extra = ""; if ($verbose) { $extra = "-v"; } open(MYSQL, "cat $tempfile | mysql $extra " . Param("shadowdb") . "|") || die "Couldn't do db copy"; my $count = 0; while () { print "."; $count++; if ($count % 70 == 0) { print "\n"; } } close(MYSQL); unlink($tempfile); Verbose(""); $::dbwritesallowed = 1; # SendSQL("UPDATE shadowlog SET reflected = 1 WHERE reflected = 0", 1); SendSQL("DELETE FROM shadowlog", 1); SendSQL("UNLOCK TABLES"); if ($wasusing) { Verbose("Reenabling other processes to read from the shadow db"); $::param{'queryagainstshadowdb'} = 1; WriteParams(); } Verbose("OK, done."); } Verbose("Looking for commands to execute."); $::dbwritesallowed = 1; # Make us low priority, to not block anyone who is trying to actually use # the shadowdb. Note that this is carefully coded to ignore errors; we want # to keep going even on older mysqld's that don't have the # SQL_LOW_PRIORITY_UPDATES option. $::db->do("SET OPTION SQL_LOW_PRIORITY_UPDATES = 1"); while (1) { SendSQL("SELECT id, command FROM $shadowtable WHERE reflected = 0 " . "ORDER BY id LIMIT 1"); my ($id, $command) = (FetchSQLData()); if (!$id) { last; } Verbose("Executing command in shadow db: $command"); SendSQL($command, 1); SendSQL("UPDATE $shadowtable SET reflected = 1 WHERE id = $id", 1); } Verbose("Releasing lock."); SendSQL("SELECT RELEASE_LOCK('synclock')");