LVS
lvs-users
Google
 
Web LinuxVirtualServer.org

[PATCH] Re: Ldirector/MySQL -> Memory Leak

To: Pamcho <pamcho@xxxxxxxxxxx>, lvs-users@xxxxxxxxxxxxxxxxxxxxxx, Horms <horms@xxxxxxxxxxxx>
Subject: [PATCH] Re: Ldirector/MySQL -> Memory Leak
From: Roberto Nibali <ratz@xxxxxxxxxxxx>
Date: Wed, 14 Feb 2007 23:33:38 +0100
Gentlemen,

Happy Valentine, so don't forget your ladies!

        And it works fine, but I can see that the memory used by the
ldirector process grows linearly, until crash the server some days
after...

That is certainly not good. Is there any chance that you could
test a newer ldirectord to see if the problem has already been
resolved? The one shipped in the linux-ha (heartbeat) 2.6.8 tarball should be the latest.

I was bored for a couple of minutes and very interested in my freshly installed Eclipse environment under Windows, so I skimmed over ldirectord (for some reason my mercurial in eclipse didn't work, so I had to download ldirectord by hand, create a temporary CVS repository and diff it against my local CVS :)) for a while and came up with this little patch as a base for further discussions. Rough changelog:

o Probable whitespace damage fix.
o s/$dbh->err/$dbh-errstr()/ to get meaningful messages for
  all DBMS instead of numbers.
o Added error handling and db handle disconnect to
  the prepare query; yes, it can fail :).
o Updated comment regarding the number of fetched rows,
  since this is not a very reliable number from my little
  experience with perl->DBI.
o Explicit finish() and disconnect() calls for the place we
  should never get to and added comment, so we know if someone
  actually hits that undesired code path.
o Added support for Oracle DBMS checking (totally untested).
o Removed old code indicating that once the rows were actually
  fetched and maybe the original thought was to compare the rows
  to some string. Nevertheless, this simplifies the code a bit
  and stresses out why the undesired code path could be hit.
o Cosmetic function calling consistency brushup.

This has received no testing, since I don't run a LVS system, however I believe the changes are relatively safe and straight-forward. They could also give a hint or even address the memory leak observed by the original poster.

Please review carefully and consider applying the fragments that look fine to you, Horms.

Best regards,
Roberto Nibali, ratz
--
echo '[q]sa[ln0=aln256%Pln256/snlbx]sb3135071790101768542287578439snlbxq' | dc
### Eclipse Workspace Patch 1.0
#P ldirectord
Index: ldirectord.pl
===================================================================
RCS file: /data/CVSROOT/ldirectord/CVSROOT/ldirectord.pl,v
retrieving revision 1.1
diff -u -r1.1 ldirectord.pl
--- ldirectord.pl       14 Feb 2007 19:29:06 -0000      1.1
+++ ldirectord.pl       14 Feb 2007 22:26:29 -0000
@@ -294,7 +294,7 @@
 On means no checking will take place and real servers will always be
 activated. Default is I<negotiate>.
 
-B<service = 
ftp>|B<smtp>|B<http>|B<pop>|B<pops>|B<nntp>|B<imap>|B<imaps>|B<ldap>|B<https>|B<dns>|B<radius>|B<mysql>|B<pgsql>|B<sip>|B<none>
+B<service = 
ftp>|B<smtp>|B<http>|B<pop>|B<pops>|B<nntp>|B<imap>|B<imaps>|B<ldap>|B<https>|B<dns>|B<radius>|B<mysql>|B<pgsql>|B<oracle>|B<sip>|B<none>
 
 The type of service to monitor when using checktype=negotiate. None denotes
 a service that will not be monitored. 
@@ -326,6 +326,8 @@
 
 =item * Virtual server port is 995: pops
 
+=item * Virtual server port is 1521: oracle
+
 =item * Virtual server port is 1812: radius
 
 =item * Virtual server port is 3306: mysql
@@ -354,7 +356,7 @@
 For a DNS check this should the name of an A record, or the address
 of a PTR record to look up.
 
-For a MySQL or PostgeSQL checks, this should be a SQL query.
+For a MySQL, PostgeSQL or Oracle checks, this should be a SQL query.
 The data returned is not checked, only that the
 answer is one or more rows.  This is a required setting.
 
@@ -389,7 +391,7 @@
 
 B<login = ">I<username>B<">
 
-Username to use to login to FTP, IMAP, LDAP, MySQL, POP, PostgreSQL.
+Username to use to login to FTP, IMAP, LDAP, MySQL, POP, PostgreSQL, Oracle.
 
 For Radius the passwd is used for the attribute User-Name.
 
@@ -402,7 +404,7 @@
 
 =item * FTP: Anonymous
 
-=item * MySQL and PostgreSQL: Must be specified in the configuration
+=item * MySQL, PostgreSQL and Oracle: Must be specified in the configuration
 
 =item * SIP: ldirectord\@<hostname>, hostname is derived as per the passwd
         option below.
@@ -414,7 +416,7 @@
 
 B<passwd = ">I<password>B<">
 
-Password to use to login to FTP, IMAP, LDAP, MySQL, POP, PostgreSQL
+Password to use to login to FTP, IMAP, LDAP, MySQL, POP, PostgreSQL, Oracle
 and SIP servers.
 
 For Radius the passwd is used for the attribute User-Password.
@@ -428,14 +430,14 @@
        run time, or sourced from uname if unset.
 
 =item * Otherwise: empty string.
-       In the case of LDAP, MySQL and PostgreSQL this means
+       In the case of LDAP, MySQL, PostgreSQL and Oracle this means
        that authentication will not be performed.
 
 =back 4
 
 B<database = ">I<databasename>B<">
 
-Database to use for MySQL and PostgreSQL servers, this is the database that
+Database to use for MySQL, PostgreSQL or Oracle servers, this is the database 
that
 the query (set by B<receive> above) will be performed against.  This is a
 required setting.
 
@@ -843,7 +845,7 @@
 # If we get a sinal then log it and quit
 sub ld_handler_term
 {
-        my ($signal) = (@_);
+       my ($signal) = (@_);
 
        if (defined $DAEMON_TERM) {
                $SIG{'__DIE__'} = "IGNORE";
@@ -1145,8 +1147,8 @@
                                        }
                                } elsif ($rcmd =~ /^service\s*=\s*(.*)/) {
                                        lc($1);
-                                       $1 =~ /(\w+)/ && ($1 eq "http" || $1 eq 
"https" || $1 eq "ldap" || $1 eq "ftp" || $1 eq "none" || $1 eq "smtp" || $1 eq 
"pop" || $1 eq "pops" || $1 eq "imap" || $1 eq "imaps" || $1 eq "nntp" || $1 eq 
"dns" || $1 eq "mysql" || $1 eq "pgsql" || $1 eq "radius" || $1 eq "sip")
-                                           or &config_error($line, "service 
must be http, https, ftp, smtp, pop, pops, imap, imaps, ldap, nntp, dns, mysql, 
pgsql, radius, sip, or none");
+                                       $1 =~ /(\w+)/ && ($1 eq "http" || $1 eq 
"https" || $1 eq "ldap" || $1 eq "ftp" || $1 eq "none" || $1 eq "smtp" || $1 eq 
"pop" || $1 eq "pops" || $1 eq "imap" || $1 eq "imaps" || $1 eq "nntp" || $1 eq 
"dns" || $1 eq "mysql" || $1 eq "pgsql" || $1 eq "oracle" || $1 eq "radius" || 
$1 eq "sip")
+                                           or &config_error($line, "service 
must be http, https, ftp, smtp, pop, pops, imap, imaps, ldap, nntp, dns, mysql, 
pgsql, oracle, radius, sip, or none");
                                        $vsrv{service} = $1;
                                        if($vsrv{service} eq "ftp" and 
                                                        $vsrv{login} eq "") {
@@ -1384,10 +1386,13 @@
                        }
                        elsif ($vsrv->{port} eq "3306") {
                                $vsrv->{service} = "mysql";
-                       } 
+                       }
                        elsif ($vsrv->{port} eq "5432") {
                                $vsrv->{service} = "pgsql";
                        } 
+                       elsif ($vsrv->{port} eq "1521") {
+                               $vsrv->{service} = "oracle";
+                       }
                        elsif ($vsrv->{port} eq "5060") {
                                $vsrv->{service} = "sip";
                        } 
@@ -2061,6 +2066,8 @@
                                                $$r{num_connects} = 0 if 
(check_mysql($v, $r));
                                        } elsif ($$v{service} eq "pgsql") {
                                                $$r{num_connects} = 0 if 
(check_pgsql($v, $r));
+                                       } elsif ($$v{service} eq "oracle") {
+                                               $$r{num_connects} = 0 if 
(check_oracle($v, $r));
                                        } else {
                                                $$r{num_connects} = 0 if 
(check_none($v, $r));
                                        }
@@ -2460,6 +2467,11 @@
        return check_sql(@_, "Pg", "dbname");
 }
 
+sub check_oracle
+{
+       return check_sql(@_, "Oracle", "");
+}
+
 sub check_sql
 {
        require DBI;
@@ -2470,43 +2482,44 @@
        $query =~ s#^/##;
        unless ($$v{login} && $query) {
                service_set($v, $r, "down");
-               &ld_log("Error: Must specify a login and request string for 
mysql and postgresql checks. Not adding $$r{server}.\n");
+               &ld_log("Error: Must specify a login and request string for 
mysql, postgresql or oracle checks. Not adding $$r{server}.\n");
                return 1;
        }
        $result=2;   # Set result flag.  Only ok if ends up at zero.
        &ld_debug(2, "Checking $$v{server} server=$$r{server} port=$port\n");
        $dbh = 
DBI->connect("dbi:$dbd:$dbname=$$v{database};host=$$r{server};port=$port", 
$$v{login}, $$v{passwd});
        unless ($dbh) {
-               &ld_debug(4, "Failed to bind to $$r{server} with $dbh->err");
+               &ld_debug(4, "Failed to bind to $$r{server} with 
$dbh->errstr()");
                service_set($v, $r, "down");
                return 1;
        }
        $result--;
        $sth = $dbh->prepare($query);
-       $rows = $sth->execute;
-       ld_debug(4, "Database search returned $rows rows");
+       unless ($sth) {
+               &ld_log("Error preparing statement:" . $dbh->errstr() . ". Not 
adding $$r{server}.\n");
+               $dbh->disconnect();
+               return 1;
+       }
+       $rows = $sth->execute();
+       &ld_debug(4, "Database search returned $rows rows");
        if ($rows gt 0) {
                # If it returns with a number, it is ok.
                # Disallows query of an empty table.
+               # Only works with select and update statements, however
+               # most people will want to use select statements for
+               # health checks.
                $result--;
-       }
-       # If user defined a receive string (number of rows returned), only do
-       # the check if the previous fetchall_arrayref succeeded.
-       #if (defined $$r{receive} && $result eq 0) {
-       #       # Receive string specifies an exact number of rows
-       #       if ($rows ne $$r{receive}) {
-       #       ld_debug(2,"Service down, receive=$$r{receive}");
-       #               $result=1;
-       #       }
-       #}
-       if ($result == 1) {
-               # Should never get here
+       } else {
+               # Should never get here (why? --ratz)
+               &ld_log("Prior executing statement returned $rows rows: not 
adding $$r{server}.\n");
                service_set($v, $r, "down");
+               $sth->finish();
+               $dbh->disconnect();
                return 1;
        }
        service_set($v, $r, "up");
-       $sth->finish;
-       $dbh->disconnect;
+       $sth->finish();
+       $dbh->disconnect();
        return 0;
 }
 




<Prev in Thread] Current Thread [Next in Thread>