File Coverage

blib/lib/DBIx/HA.pm
Criterion Covered Total %
statement 227 382 59.4
branch 47 146 32.1
condition 12 50 24.0
subroutine 30 36 83.3
pod 2 3 66.6
total 318 617 51.5


line stmt bran cond sub pod time code
1             # High Availability package for DBI
2             #
3             # Copyright (c) 2003-2006 Henri Asseily . All rights reserved.
4             # This program is free software; you can redistribute it and/or
5             # modify it under the same terms as Perl itself.
6              
7             {
8             package DBIx::HA;
9              
10 2     2   47766 use 5.006000;
  2         8  
  2         110  
11              
12 2   50 2   12 use constant DBIx_HA_DEBUG => $ENV{DBIX_HA_DEBUG} || 0;
  2         2  
  2         134  
13 2     2   2633 use Data::Dumper;
  2         13783  
  2         189  
14 2     2   4874 use DBI 1.49 ();
  2         45402  
  2         86  
15 2     2   2938 use Sys::SigAction qw( set_sig_handler );
  2         54537  
  2         218  
16 2     2   17 use Exporter ();
  2         4  
  2         52  
17 2     2   9 use strict;
  2         4  
  2         66  
18 2     2   9 use vars qw ( @ISA $prefix );
  2         4  
  2         266  
19             @ISA = qw ( DBI );
20              
21             our $loaded_Apache = 0;
22             our $loaded_Apache_DBI = 0;
23             our $logdir;
24              
25             BEGIN {
26 2     2   7410 $DBIx::HA::VERSION = 1.10;
27              
28             # Sample Fail Test Functions for different Database Servers
29             # They are used in the configuration
30             # Input is ($ErrorID, $ErrorString)
31             # Output is boolean: If true, then we'll consider the error a critical condition, ok to failover
32             # If false, then DBIx::HA will not act on it and pass it straight through to the client
33 0 0   0 0 0 sub FTF_SybaseASE { $_[0] > 10 ? 0 : 1 }; # Fail Test Function for Sybase ASE
34             }
35              
36             our $prefix = "[$$] DBIx::HA: ";
37              
38             sub initialize {
39 2 50   2 1 1677 if ($Apache::VERSION) {
40 0         0 $loaded_Apache = 1;
41             }
42 2 50       11 if ($Apache::DBI::VERSION) {
43 0         0 $loaded_Apache_DBI = 1;
44             }
45 2 50       7 if ($loaded_Apache_DBI) {
46 0         0 $Apache::DBI::DEBUG = DBIx_HA_DEBUG; # If we're debugging here, we should also debug Apache::DBI
47             }
48 2         4 if (DBIx_HA_DEBUG > 1) {
49             warn "$prefix in initialize:\n";
50             warn Dumper %DATABASE::conf;
51             }
52 2         3 my $dbname;
53 2         10 foreach $dbname (keys %DATABASE::conf) {
54             # set default failover to process (i.e. each process is independent from others
55             # choices are : process, application
56 2 50       9 if (! $DATABASE::conf{$dbname}->{'failoverlevel'}) {
57 0         0 $DATABASE::conf{$dbname}->{'failoverlevel'} = 'process';
58             }
59             # add default timeouts for connection and execution
60 2 50       9 if (! $DATABASE::conf{$dbname}->{'connecttimeout'}) {
61 0         0 $DATABASE::conf{$dbname}->{'connecttimeout'} = 30;
62             }
63 2 50       26 if (! $DATABASE::conf{$dbname}->{'executetimeout'}) {
64 0         0 $DATABASE::conf{$dbname}->{'executetimeout'} = 30;
65             }
66             # add default failover rule based on DBI error
67             # default is to not request failover, whatever the error
68             # do not ever set the default to always request failover, as any SQL error would trigger failover!
69 2 50       12 if (! $DATABASE::conf{$dbname}->{'failtest_function'}) {
70 2     0   13 $DATABASE::conf{$dbname}->{'failtest_function'} = sub { 0 };
  0         0  
71             }
72 2         6 $DATABASE::conf{$dbname}->{'end_of_stack'} = 0; # error condition reaching end of stack
73 2         3 for my $conn_aref (@{$DATABASE::conf{$dbname}->{'db_stack'}}) {
  2         6  
74 6         12 my $dsn = $conn_aref->[0];
75             # create an easy reverse-lookup table for finding the db server name from the dsn
76 6         16 $DBIx::HA::finddbserver{$dsn} = $dbname;
77             # add timeout when within Apache::DBI
78             # default to no ping (-1)
79 6 50       18 if ($loaded_Apache_DBI) {
80 0 0       0 if ($Apache::DBI::VERSION < 0.89) {
81 0         0 die "$prefix Requirement unmet. Apache::DBI must be at version 0.89 or above";
82             }
83             # create a cached lookup table for finding the Apache::DBI cache key index from the dsn
84 0         0 $DBIx::HA::ApacheDBIidx{$dsn} = _getApacheDBIidx(@$conn_aref);
85 0   0     0 Apache::DBI->setPingTimeOut($dsn, $DATABASE::conf{$dbname}->{'pingtimeout'} || -1);
86             }
87             };
88             # set the active database to be the first in the stack
89 2 50       14 _writesharedfile($dbname, 0) unless ($DATABASE::conf{$dbname}->{'active_db'});
90              
91             # hook up the child initialization routine
92 2 50 33     40 if(Apache->can('push_handlers') && ($Apache::ServerStarting == 1)) {
93 0         0 Apache->push_handlers(PerlChildInitHandler => \&_init_child);
94             }
95             # do not force a connection here
96             # as we may be in the parent process. Connect in _init_child instead.
97             };
98             }
99              
100             sub _init_child {
101             # Set up debugging PID for children
102 0     0   0 $DBIx::HA::prefix = "[$$] DBIx::HA: ";
103 0         0 $DBIx::HA::st::prefix = "[$$] DBIx::HA:st: ";
104 0         0 $DBIx::HA::db::prefix = "[$$] DBIx::HA:db: ";
105 0         0 if (DBIx_HA_DEBUG > 1) {
106             warn "$prefix in init_child:\n";
107             }
108 0         0 my $dbname;
109 0         0 foreach $dbname (keys %DATABASE::conf) {
110             # under application failover, maybe we already have an active db.
111             # set the active database to be the first in the stack unless we got it earlier.
112 0         0 _readsharedfile($dbname);
113 0 0       0 _writesharedfile($dbname, 0) unless ($DATABASE::conf{$dbname}->{'active_db'});
114              
115             # allow for connect on initialization
116 0 0 0     0 if ($DATABASE::conf{$dbname}->{'connectoninit'} && $loaded_Apache_DBI) {
117 0         0 warn "$prefix Connecting to $dbname on init_child\n" if (DBIx_HA_DEBUG);
118 0         0 DBIx::HA->connect($dbname);
119             }
120             };
121             }
122              
123              
124             sub _readsharedfile {
125             # reads from file-based shared memory to get active database under Apache
126 10     10   13 my $dbname = shift;
127 10 50       31 if ($DATABASE::conf{$dbname}->{'failoverlevel'} eq 'application') {
128             # do this only if we're doing application failover and not process failover
129 0 0 0     0 if ($loaded_Apache && (-f "$logdir/DBIxHA_activedb_$dbname")) {
130 0         0 open IN, "$logdir/DBIxHA_activedb_$dbname";
131 0         0 my $dbidx = ;
132 0         0 chomp $dbidx;
133 0         0 close IN;
134 0 0       0 if ($dbidx == -1) { # we're told that we're at the end of the stack. No db server is available.
135 0         0 $DATABASE::conf{$dbname}->{'end_of_stack'} = 1;
136 0         0 return 0;
137             }
138 0         0 $DATABASE::conf{$dbname}->{'end_of_stack'} = 0;
139 0 0 0     0 if (($dbidx =~ /^\d+$/o) && $DATABASE::conf{$dbname}->{'db_stack'}->[$dbidx]) {
140 0         0 $DATABASE::conf{$dbname}->{'active_db'} = $DATABASE::conf{$dbname}->{'db_stack'}->[$dbidx];
141 0         0 $DBIx::HA::activeserver{$dbname} = $dbidx;
142 0 0       0 unless ($Apache::ServerStarting == 1) {
143 0         0 my $r = Apache->request;
144 0 0       0 $r->notes("activedb_$dbname", $dbidx) if (ref $r);
145             }
146             } else {
147 0         0 warn "$prefix in _readsharedfile: $dbname shared file has erroneous content, overwriting.\n";
148 0         0 _writesharedfile($dbname, $DBIx::HA::activeserver{$dbname});
149 0         0 return 0;
150             }
151             }
152             }
153 10         16 return 1;
154             }
155              
156             sub _writesharedfile {
157 3     3   5 my $dbname = shift;
158 3         5 my $dbidx = shift;
159             # updates the active handle
160             # and writes to file-based shared memory for active database under Apache
161 3         4 warn "$prefix in _writesharedfile: activating index $dbidx for database $dbname\n" if (DBIx_HA_DEBUG);
162 3         10 $DATABASE::conf{$dbname}->{'active_db'} = $DATABASE::conf{$dbname}->{'db_stack'}->[$dbidx];
163 3         7 $DBIx::HA::activeserver{$dbname} = $dbidx;
164              
165 3 50       14 if ($DATABASE::conf{$dbname}->{'failoverlevel'} eq 'application') {
166             # do this only if we're doing application failover and not process failover
167 0 0       0 if ($loaded_Apache) {
168 0 0       0 unless ($Apache::ServerStarting == 1) {
169 0         0 my $r = Apache->request;
170 0 0       0 $r->notes("activedb_$dbname", $dbidx) if (ref $r);
171             }
172 0         0 $logdir = Apache::server_root_relative(undef,'logs');
173 0   0     0 open IN, ">/$logdir/DBIxHA_activedb_$dbname" || return 0;
174 0         0 print IN $DBIx::HA::activeserver{$dbname};
175 0         0 close IN;
176 0 0       0 if ($Apache::ServerStarting == 1) {
177 0         0 chmod 0666, "$logdir/DBIxHA_activedb_$dbname";
178             }
179             }
180             }
181 3         6 return 1;
182             }
183              
184             sub _getdbname {
185             # returns the db server name when given the dsn string
186 19     19   26 my $dsn = shift;
187 19         20 warn "$prefix in _getdbname: $DBIx::HA::finddbserver{$dsn} \n" if (DBIx_HA_DEBUG > 2);
188 19         80 return $DBIx::HA::finddbserver{$dsn};
189             }
190              
191             sub _isactivedb {
192             # returns true if the db server in use is the one that should be active
193 10     10   21 my $dsn = shift;
194 10         18 my $dbname = _getdbname ($dsn);
195 10         27 _readsharedfile($dbname);
196 10 50       28 if ($DATABASE::conf{$dbname}->{'end_of_stack'}) {
197             # we're not in the active db, because there is no active database, the end of stack is reached.
198 0         0 return 0;
199             }
200 10 100       38 if ($dsn eq $DATABASE::conf{$dbname}->{'active_db'}->[0]) {
201 7         12 warn "$prefix in _isactivedb: ".$dsn." is the active one \n" if (DBIx_HA_DEBUG > 2);
202 7         31 return 1;
203             }
204 3         4 warn "$prefix in _isactivedb: ".$dsn." is NOT active \n" if (DBIx_HA_DEBUG > 2);
205 3         8 $DATABASE::retries{$DATABASE::conf{$dbname}->{'active_db'}->[0]} = 0; # reset the active db's retries for this process
206 3         12 return 0;
207             }
208              
209             sub _getnextdb {
210             # returns the proper db server arrayref to use if the current one is dead
211 1     1   2 my $dsn = shift;
212 1         7 my $dbname = _getdbname ($dsn);
213 1 50       4 if (_isactivedb ($dsn)) {
    50          
214             # do this only if we are the first to look for a good db server
215             # otherwise just return the active db server
216 0         0 my $foundmatch = 0;
217 0         0 my $idxnextdb = 0;
218 0         0 my $stackcount = scalar(@{$DATABASE::conf{$dbname}->{'db_stack'}});
  0         0  
219 0         0 foreach (@{$DATABASE::conf{$dbname}->{'db_stack'}}) {
  0         0  
220 0         0 $idxnextdb++;
221 0 0       0 if ($dsn eq $_->[0]) {
222             # we got to the current db server in the stack
223             # next db server in the stack is the correct one
224 0         0 $foundmatch = 1;
225 0         0 last;
226             }
227             }
228 0 0       0 if (! $foundmatch) { # didn't find a match, current dsn is invalid
    0          
229 0         0 warn "$prefix in _getnextdb: current dsn is invalid for $dbname: $dsn \n" if (DBIx_HA_DEBUG);
230 0         0 $idxnextdb = 0;
231             } elsif ($idxnextdb > ($stackcount - 1)) {
232 0         0 warn "$prefix in _getnextdb: Reached end of db server stack for $dbname. Staying there.\n" if (DBIx_HA_DEBUG);
233 0         0 $DATABASE::conf{$dbname}->{'end_of_stack'} = 1;
234 0         0 _writesharedfile($dbname, -1);
235 0         0 return undef;
236             }
237 0         0 _writesharedfile($dbname, $idxnextdb);
238 0         0 warn "$prefix in _getnextdb: activated ".$DATABASE::conf{$dbname}->{'active_db'}->[0]." \n" if (DBIx_HA_DEBUG);
239             } elsif ($DATABASE::conf{$dbname}->{'end_of_stack'}) {
240 0         0 return undef;
241             } else {
242 1         13 warn "$prefix in _getnextdb: found different active db server, switching to ".$DATABASE::conf{$dbname}->{'active_db'}->[0]."\n" if (DBIx_HA_DEBUG);
243             }
244 1         4 return $DATABASE::conf{$dbname}->{'active_db'}->[0];
245             }
246              
247             sub _getApacheDBIidx {
248             # generates the ApacheDBI cache idx key from the passed dsn info
249 0 0   0   0 if (! $loaded_Apache_DBI) {
250             # Apache::DBI isn't loaded, exit.
251 0         0 return undef;
252             }
253             # first generate the same $idx key entry as ApacheDBI does
254 0 0       0 my @args = map { defined $_ ? $_ : "" } @_;
  0         0  
255 0 0       0 if ($args[0] =~ /^dbi:/i) { $args[0] =~ s/^dbi:[^:]+://io; }; # remove the dbi:driver: piece
  0         0  
256 0         0 my $idx = join $;, $args[0], $args[1], $args[2];
257              
258 0 0 0     0 if (3 == $#args and ref $args[3] eq "HASH") {
259 0         0 map { $idx .= "$;$_=$args[3]->{$_}" } sort keys %{$args[3]};
  0         0  
  0         0  
260             }
261 0         0 warn "$prefix in getApacheDBIidx: generated idx: $idx , from dsn $args[0]\n" if (DBIx_HA_DEBUG > 1);
262 0         0 return $idx;
263             }
264              
265             sub _reconnect {
266 1     1   2 my $currdsn = shift;
267 1   50     4 my $dbh = shift || undef;
268 1         2 my $olddsn = $currdsn; # old dsn to delete from Apache::DBI
269 1         1 my $conn_str;
270             my $selrow;
271 1         3 my $dbname = _getdbname ($currdsn);
272 1         2 my $newdbh;
273             my $i;
274              
275 1 50       2 if (! _isactivedb ($currdsn)) { # wrong database server, use the active one
276 1         6 $currdsn = _getnextdb ($currdsn);
277             }
278 1 50       4 if (! $currdsn) {
279 0         0 warn "$prefix in _reconnect: No data source available, end of stack is reached. dbh is undefined. \n";
280 0 0 0     0 eval { $dbh->disconnect if ((defined $dbh) && (ref $dbh)); undef $dbh; };
  0         0  
  0         0  
281 0         0 return ($currdsn, undef); # bad dbh and dsn
282             }
283              
284             FINDDB: {
285 1         2 my $dbstackindex = 0; # pointer to position in the stack
  1         1  
286 1         2 foreach $selrow (@{$DATABASE::conf{$dbname}->{'db_stack'}}) { # loop through the stack
  1         3  
287 2 100       7 if ($currdsn eq $selrow->[0]) { # found the proper db server in the stack
288 1 50       3 if ($loaded_Apache_DBI) { # delete the cached ApacheDBI entry
289 0         0 my $ApacheDBIConnections = Apache::DBI::all_handlers();
290 0 0       0 delete $$ApacheDBIConnections{$DBIx::HA::ApacheDBIidx{$olddsn}} if ($DBIx::HA::ApacheDBIidx{$olddsn});
291 0         0 warn "$prefix in _reconnect: deleted cached ApacheDBI entry ".$DBIx::HA::ApacheDBIidx{$olddsn}."\n" if (DBIx_HA_DEBUG);
292             }
293 1         3 warn "$prefix in _reconnect: retrying ".$selrow->[0]."\n" if (DBIx_HA_DEBUG);
294 1         1 $i=0;
295 1 50       40 $DATABASE::retries{$currdsn} = 0 if (! $DATABASE::retries{$currdsn});
296 1         6 for ($i=$DATABASE::retries{$currdsn}; $i < $DATABASE::conf{$dbname}->{'max_retries'}; $i++) { # retry max_retries
297 1         2 $DATABASE::retries{$currdsn}++;
298             # now we're going to create a new good database handle and swap it with the old bad one
299 1         3 $newdbh = _connect_with_timeout (@$selrow);
300 1 50       5 if (defined $newdbh) {
301             # we managed to create a new database handle
302 1 50 33     7 if ((defined $dbh) && (ref $dbh)) {
303             # the old one still exists, so we're going to swap it and then destroy it
304 1         2 warn "$prefix in _reconnect: Pointing dbh to newdbh\n" if (DBIx_HA_DEBUG);
305 1         14 $dbh->swap_inner_handle($newdbh);
306             # wipe the old database handle (which in turn finishes all its children sth's)
307             # If we're using DBD::Sybase, make sure syb_flush_finish is off so we don't get remaining results
308 1 50 33     6 $newdbh->{syb_flush_finish} = 0 if $newdbh->{syb_flush_finish}
309             and $newdbh->{Driver}{Name} ne 'Gofer';
310 1         11 eval { undef $newdbh; };
  1         3  
311             } else {
312             # there was no previous active database handle, so that's easy
313 0         0 $dbh = $newdbh;
314             }
315 1         124 warn "$prefix Successfully reconnected to $currdsn\n";
316 1         4 $DATABASE::retries{$currdsn} = 0; # reset the retries counter
317 1         4 _writesharedfile($dbname, $dbstackindex);
318             # Do callback if it exists
319 1 50       6 if ( ref $DATABASE::conf{$dbname}->{'callback_function'}) {
320 1         1 &{$DATABASE::conf{$dbname}->{'callback_function'}}($dbh, $dbname);
  1         6  
321             }
322 1         7 return ($currdsn, $dbh);
323             } #if
324 0         0 warn "$prefix in _reconnect: failed ".($i+1)." times to connect to $currdsn\n" if (DBIx_HA_DEBUG > 1);
325 0         0 select undef, undef, undef, 0.2; # wait a fraction of a second
326             } #for
327             # we found our db server in the stack, but couldn't connect to it
328             # Get another one, and try again, assuming we've not exhausted the stack!
329 0         0 $olddsn = $currdsn; # remember the old one to delete it from Apache::DBI
330 0         0 $currdsn = _getnextdb ($currdsn); # go to next dsn
331 0 0       0 if (! $currdsn) { # we reached the end of the stack!
332 0         0 warn "$prefix *** ERROR: Exhausted DBI failover stack. Last DSN is: $olddsn \n";
333 0         0 return ($olddsn, undef);
334             }
335 0         0 warn "$prefix in _reconnect: dbstackindex: $dbstackindex; Trying another db server: $currdsn \n";
336 0         0 goto FINDDB;
337             } #if
338 1         2 $dbstackindex++;
339             } #foreach
340             } # FINDDB
341 0         0 warn "$prefix in _reconnect: Couldn't find a good data source, dbh is undefined. Pointing to $currdsn\n";
342 0         0 return ($currdsn, undef); # bad dbh! (multiple tries failed)
343             }
344              
345             sub connect {
346 2     2 1 3 warn "$prefix Apache::DBI handlers are: \n" if (DBIx_HA_DEBUG > 1);
347 2         3 warn Dumper Apache::DBI::all_handlers() if (DBIx_HA_DEBUG > 1 && $loaded_Apache_DBI);
348 2         5 my $class = shift;
349 2         3 my $dbname = shift;
350 2 50       9 my $conf = $DATABASE::conf{$dbname}
351             or Carp::croak("No entry for '$dbname' in %DATABASE::conf");
352 2 50       11 my $active_db = $conf->{'active_db'}
353             or Carp::croak("No active_db for '$dbname' (did you call initialize?)");
354 2         5 my ($dsn, $username, $auth, $attrs) = @$active_db;
355              
356             # Update the active db. If it's been updated, switch to it
357 2 50       10 if (! _isactivedb($dsn)) {
358 0         0 ($dsn, $username, $auth, $attrs) = @{$DATABASE::conf{$dbname}->{'active_db'}};
  0         0  
359 0         0 warn "$prefix in connect: switching to active db $dsn" if (DBIx_HA_DEBUG);
360             }
361              
362             # now we've got the right data source. Go ahead.
363 2         6 $DATABASE::retries{$dsn} = 0; # initialize # of retries for the dsn
364 2         8 my $dbh = _connect_with_timeout($dsn, $username, $auth, $attrs);
365 2 50       9 if (defined $dbh) {
366 2         5 warn "$prefix in connect: first try worked for $dsn\n" if (DBIx_HA_DEBUG);
367             } else {
368 0         0 warn "$prefix in connect: retrying connect of $dsn\n" if (DBIx_HA_DEBUG);
369 0         0 ($dsn, $dbh) = _reconnect ($dsn, $dbh);
370             }
371 2         7 return $dbh;
372             }
373              
374             sub _connect_with_timeout {
375 3     3   5 my ($dsn, $username, $auth, $attrs) = @_;
376 3         5 warn "$prefix in _connect_with_timeout: dsn: $dsn \n" if (DBIx_HA_DEBUG > 1);
377 3         4 my $res;
378             my $dbh;
379 3         4 my $timeout = 0;
380 3         7 eval {
381 2     2   17 no strict;
  2         13  
  2         512  
382             my $h = set_sig_handler(
383             'ALRM',
384 0     0   0 sub { $timeout = 1; die 'TIMEOUT'; },
  0         0  
385 3         43 { mask=>['ALRM'], safe=>1 }
386             );
387 3         486 alarm($DATABASE::conf{_getdbname($dsn)}->{'connecttimeout'});
388 3         22 $dbh = DBI->connect($dsn, $username, $auth, $attrs);
389 3         6522 alarm(0);
390             };
391 3         133 alarm(0);
392 3 50 33     25 if ($@ or $timeout) { # there's a problem above
393 0 0       0 if ($timeout) { # it's a timeout
394 0         0 warn "$prefix *** CONNECT TIMED OUT in $dsn";
395 0         0 eval { $dbh->disconnect };
  0         0  
396 0         0 $dbh = undef;
397             } else { # problem in the connection
398 0 0       0 warn "$prefix Error in DBI::connect: $@\n" if $@;
399             }
400             }
401 3 50       20 $dbh->{private_dbixha_dsn} = $dsn if $dbh;
402 3         33 return $dbh;
403             }
404             } # end package DBIx::HA
405              
406             {
407             package DBIx::HA::db;
408 2     2   11 use strict;
  2         5  
  2         76  
409 2     2   11 use constant DBIx_HA_DEBUG => DBIx::HA::DBIx_HA_DEBUG;
  2         3  
  2         155  
410 2     2   11 use vars qw ( @ISA );
  2         5  
  2         743  
411             @ISA = qw(DBI::db DBIx::HA);
412             our $prefix = "[$$] DBIx::HA:db: ";
413              
414             # note that the DBI::db methods do not fail if the database connection is dead
415             sub prepare {
416 3     3   1208 my $dbh = shift;
417 3         6 my $sql = shift;
418 3         5 my $sth;
419 3   50     42 my $dsn = $dbh->{private_dbixha_dsn} || die "panic: no private_dbixha_dsn";
420 3         8 warn "$prefix in prepare: dsn: $dsn ; sql: $sql \n" if (DBIx_HA_DEBUG > 1);
421 3 50       8 if (DBIx::HA::_isactivedb ($dsn)) {
422 3 50       18 warn join "\n", "$prefix Statement handle being prepared while existing statement handle still open!",
423             "\tdbh:\t\t$dsn",
424             "\tprevious statement:\t".$dbh->{Statement},
425             "\tcurrent statement:\t$sql",
426             "\tACTIVE KIDS: ".$dbh->{ActiveKids}."\n"
427             if $dbh->{ActiveKids};
428             } else {
429 0         0 my $dbname = DBIx::HA::_getdbname($dsn);
430 0         0 ($dsn, $dbh) = DBIx::HA::_reconnect ($dsn, $dbh);
431 0 0       0 if (! defined $dbh) { # we couldn't connect at all
432 0         0 warn "$prefix in prepare: couldn't prepare sql: $sql\n";
433 0         0 return undef;
434             }
435             }
436 3         71 $sth = $dbh->SUPER::prepare($sql,@_);
437 3         110 return $sth;
438             }
439            
440             } # end package DBIx::HA::db
441              
442             {
443             package DBIx::HA::st;
444 2     2   11 use strict;
  2         13  
  2         80  
445 2     2   14 use constant DBIx_HA_DEBUG => DBIx::HA::DBIx_HA_DEBUG;
  2         3  
  2         108  
446 2     2   10 use Sys::SigAction qw( set_sig_handler );
  2         5  
  2         110  
447 2     2   9 use vars qw ( @ISA $prefix );
  2         4  
  2         2514  
448             @ISA = qw(DBI::st DBIx::HA);
449             our $prefix = "[$$] DBIx::HA:st: ";
450              
451             sub execute {
452 2     2   1033 my $sth = shift;
453 2         48 my $dbh = $sth->{Database};
454 2         34 my $sql = $dbh->{Statement};
455 2   50     19 my $dsn = $dbh->{private_dbixha_dsn} || die "panic: no private_dbixha_dsn";
456 2         9 my $dbname = DBIx::HA::_getdbname($dsn);
457 2         5 my $res;
458             my $to; # did we trip a timeout on the execution?
459 0         0 my $orig_error_code;
460 0         0 my $orig_error_string;
461 2         5 my $max_executions = $DATABASE::conf{$dbname}->{'max_retries'} * scalar(@{$DATABASE::conf{$dbname}->{'db_stack'}});
  2         9  
462              
463 2         3 warn "=================\n" if (DBIx_HA_DEBUG > 1);
464 2         4 warn "$prefix in execute: dsn: $dsn ; sql: $sql \n" if (DBIx_HA_DEBUG > 1);
465 2 100       6 if (DBIx::HA::_isactivedb ($dsn)) {
466 1         4 ($res, $to) = &_execute_with_timeout ($dsn, $sth);
467 1         6 $orig_error_code = $DBI::err;
468 1         10 $orig_error_string = $DBI::errstr;
469 1 50 33     27 if ($to || ((! defined $res) && &{$DATABASE::conf{$dbname}->{'failtest_function'}}($orig_error_code, $orig_error_string))) {
  0 50 33     0  
470             # It was a timeout error or a critical network library error (connection in a bad state)
471 0         0 warn "$prefix in execute: timeout error or network lib error, reexecuting: $sql ; dsn: $dsn \n" if (DBIx_HA_DEBUG);
472 0         0 for (my $count_execs = 0; $count_execs < $max_executions; $count_execs++) {
473 0         0 ($dsn, $sth, $res) = _reexecute ($dsn, $sql, $sth);
474 0 0       0 last if (defined $res); # reexecution worked (or failed hard with -666)
475             }
476             } elsif (! defined $res) {
477             # We got an error code from the server upon statement execution.
478             # We will let the client decide what to do and let it be.
479 0         0 warn "$prefix *** ERROR: $orig_error_code; $orig_error_string \n" if (DBIx_HA_DEBUG);
480 0         0 warn "$prefix in execute: bad sql: $sql ; dsn: $dsn \n" if (DBIx_HA_DEBUG);
481             }
482             } else { # current db is not active
483 1         4 for (my $count_execs = 0; $count_execs < $max_executions; $count_execs++) {
484 1         4 ($dsn, $sth, $res) = _reexecute ($dsn, $sql, $sth);
485 1 50       6 last if (defined $res); # reexecution worked (or failed hard with -666)
486             }
487             }
488 2 50       10 if (! defined $res) { # Execution failed
489 0         0 warn "$prefix in execute: result is undefined, statement execution failed! statement: $sql ; dsn: $dsn \n" if (DBIx_HA_DEBUG);
490 0         0 warn "+++++++++++++++++\n" if (DBIx_HA_DEBUG > 1);
491 0         0 return undef;
492             }
493 2 50       9 if ($res == -666) { # the devil killed you! We couldn't connect to the db!
494 0         0 warn "$prefix in execute: statement couldn't be executed because connect failed abnormally. statement: $sql ; dsn: $dsn \n";
495 0         0 warn "+++++++++++++++++\n" if (DBIx_HA_DEBUG > 1);
496 0         0 return undef;
497             }
498 2         3 warn "$prefix in execute: statement executed successfully! statement: $sql ; dsn: $dsn \n" if (DBIx_HA_DEBUG);
499 2         3 warn "$prefix in execute: res: $res ; errstr: $DBI::errstr \n" if (DBIx_HA_DEBUG);
500 2         6 $DATABASE::retries{$dsn} = 0; # flush the retries to this dsn, since executing worked
501 2         4 undef $@; # don't make an upstream eval die because of what happened here, since we're fine now
502 2         5 warn "+++++++++++++++++\n" if (DBIx_HA_DEBUG > 1);
503 2         11 return $res;
504             }
505              
506             =begin private
507              
508             =head2
509              
510             ($execute_result,$timeout_triggered) = _execute_with_timeout($dsn,$sth);
511              
512             Calls "execute" on a DBI statement handle, and handles a possible timeout of the query.
513              
514             Args:
515             $dsn: a key in our internal lookup table of connection details
516             $sth: DBI statement handle
517              
518             Returns:
519             - result of execute() call
520             - boolean, true if timeout was triggered.
521            
522              
523             =end private
524              
525             =cut
526              
527              
528             sub _execute_with_timeout {
529 2     2   5 my $dsn = shift;
530 2         4 my $sth = shift;
531 2         19 my $sql = $sth->{Statement};
532 2         5 warn "$prefix in _execute_with_timeout: dsn: $dsn ; sql : $sql \n" if (DBIx_HA_DEBUG > 1);
533 2         5 my $res;
534 2         3 my $timeout = 0;
535 2         10 eval {
536             my $h = set_sig_handler(
537             'ALRM',
538 0     0   0 sub { $timeout = 1; die 'TIMEOUT'; },
  0         0  
539 2         31 { mask=>['ALRM'],
540             safe=>1 }
541             );
542 2         250 alarm($DATABASE::conf{DBIx::HA::_getdbname($dsn)}->{'executetimeout'});
543 2         24 $res = $sth->SUPER::execute;
544 2         101 alarm(0);
545             };
546 2         85 alarm(0);
547 2 50 33     18 if ($@ or $timeout) { # there's a problem above
548 0 0       0 if ($timeout) { # it's a timeout
549 0         0 warn "$prefix *** EXECUTION TIMED OUT in $dsn ; SQL: $sql";
550             } else { # problem in the execution
551 0 0       0 warn "$prefix Error in DBI::execute: $@\n" if $@;
552             }
553 0         0 eval { $sth->finish; };
  0         0  
554 0         0 $sth = undef;
555 0         0 $res = undef;
556             }
557 2         7 return ($res, $timeout);
558             }
559              
560             sub _reexecute {
561             # reexecute the statement in the following way:
562             # reconnect with a new dbh
563             # redo prepare and execute until it works
564 1     1   2 my $dsn = shift;
565 1         1 my $sql = shift;
566 1   50     13 my $sth = shift || undef;
567 1         1 my $dbh = undef;
568 1         2 my $newsth;
569             my $res;
570 0         0 my $to;
571              
572 1         2 warn "$prefix in _reexecute: dsn: $dsn \n" if (DBIx_HA_DEBUG > 1);
573 1         1 warn "$prefix Reexecuting statement: $sql" if (DBIx_HA_DEBUG > 1);
574 1 50       4 if (defined $sth) {
575 1         5 $dbh = $sth->{Database};
576             }
577 1         10 ($dsn, $dbh) = DBIx::HA::_reconnect ($dsn, $dbh);
578 1 50       5 if (! defined $dbh) { return ($dsn, $sth, -666); } # we couldn't connect at all
  0         0  
579 1         3 $newsth = $dbh->prepare($sql);
580 1         4 ($res, $to) = &_execute_with_timeout ($dsn, $newsth);
581 1 50       4 if (! $res) { # execute_with_timeout failed
582 0         0 warn "$prefix in _reexecute: reexecuting failed. dsn: $dsn ; statement: $sql\n" if (DBIx_HA_DEBUG);
583 0         0 eval { $sth->finish; };
  0         0  
584 0         0 return ($dsn, $sth, undef);
585             }
586 1 50       5 if (defined $sth) {
587 1         13 $sth->swap_inner_handle($newsth, 1); # allow reparenting of the statement handle
588 1         2 eval { $newsth->finish; };
  1         11  
589 1         2 undef $newsth;
590             } else {
591 0         0 $sth = $newsth;
592             }
593 1         71 return ($dsn, $sth, $res);
594             }
595              
596             } # end package DBIx::HA::st
597              
598             1;
599              
600             __END__