::DBI:Replicated - merge connect_info from master to replicants
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / DBI / Replicated / Pool.pm
index b1cdc82..1b50a70 100644 (file)
@@ -3,7 +3,12 @@ package DBIx::Class::Storage::DBI::Replicated::Pool;
 use Moose;
 use MooseX::AttributeHelpers;
 use DBIx::Class::Storage::DBI::Replicated::Replicant;
-use List::Util qw(sum);
+use List::Util 'sum';
+use Scalar::Util 'reftype';
+use Carp::Clan qw/^DBIx::Class/;
+use MooseX::Types::Moose qw/Num Int ClassName HashRef/;
+
+use namespace::clean -except => 'meta';
 
 =head1 NAME
 
@@ -37,7 +42,7 @@ return a number of seconds that the replicating database is lagging.
 
 has 'maximum_lag' => (
   is=>'rw',
-  isa=>'Num',
+  isa=>Num,
   required=>1,
   lazy=>1,
   default=>0,
@@ -53,7 +58,7 @@ builtin.
 
 has 'last_validated' => (
   is=>'rw',
-  isa=>'Int',
+  isa=>Int,
   reader=>'last_validated',
   writer=>'_last_validated',
   lazy=>1,
@@ -70,7 +75,7 @@ just leave this alone.
 
 has 'replicant_type' => (
   is=>'ro',
-  isa=>'ClassName',
+  isa=>ClassName,
   required=>1,
   default=>'DBIx::Class::Storage::DBI',
   handles=>{
@@ -120,7 +125,7 @@ removes the replicant under $key from the pool
 has 'replicants' => (
   is=>'rw',
   metaclass => 'Collection::Hash',
-  isa=>'HashRef[DBIx::Class::Storage::DBI]',
+  isa=>HashRef['DBIx::Class::Storage::DBI'],
   default=>sub {{}},
   provides  => {
     'set' => 'set_replicant',
@@ -149,8 +154,18 @@ sub connect_replicants {
   
   my @newly_created = ();
   foreach my $connect_info (@_) {
+    $connect_info = [ $connect_info ]
+      if reftype $connect_info ne 'ARRAY';
+
+    croak "coderef replicant connect_info not supported"
+      if ref $connect_info->[0] && reftype $connect_info->[0] eq 'CODE';
+
     my $replicant = $self->connect_replicant($schema, $connect_info);
-    my ($key) = ($connect_info->[0]=~m/^dbi\:.+\:(.+)$/);
+
+    my $key = $connect_info->[0];
+    $key = $key->{dsn} if ref $key && reftype $key eq 'HASH';
+    ($key) = ($key =~ m/^dbi\:.+\:(.+)$/);
+
     $self->set_replicant( $key => $replicant);  
     push @newly_created, $replicant;
   }
@@ -168,14 +183,40 @@ and return it.
 sub connect_replicant {
   my ($self, $schema, $connect_info) = @_;
   my $replicant = $self->create_replicant($schema);
-    
-  $replicant->connect_info($connect_info);    
-  $replicant->ensure_connected;
-  DBIx::Class::Storage::DBI::Replicated::Replicant->meta->apply($replicant);
-    
+  $replicant->connect_info($connect_info);
+  $self->_safely_ensure_connected($replicant);
+  DBIx::Class::Storage::DBI::Replicated::Replicant->meta->apply($replicant);  
   return $replicant;
 }
 
+=head2 _safely_ensure_connected ($replicant)
+
+The standard ensure_connected method with throw an exception should it fail to
+connect.  For the master database this is desirable, but since replicants are
+allowed to fail, this behavior is not desirable.  This method wraps the call
+to ensure_connected in an eval in order to catch any generated errors.  That
+way a slave to go completely offline (ie, the box itself can die) without
+bringing down your entire pool of databases.
+
+=cut
+
+sub _safely_ensure_connected {
+  my ($self, $replicant, @args) = @_;
+  eval {
+    $replicant->ensure_connected(@args);
+  }; 
+  if ($@) {
+    $replicant
+      ->debugobj
+      ->print(
+        sprintf( "Exception trying to ->ensure_connected for replicant %s, error is %s",
+          $replicant->_dbi_connect_info->[0], $@)
+        );
+       return;
+  }
+  return 1;
+}
+
 =head2 connected_replicants
 
 Returns true if there are connected replicants.  Actually is overloaded to
@@ -242,23 +283,43 @@ connection is not following a master or is lagging.
 Calling this method will generate queries on the replicant databases so it is
 not recommended that you run them very often.
 
+This method requires that your underlying storage engine supports some sort of
+native replication mechanism.  Currently only MySQL native replication is
+supported.  Your patches to make other replication types work are welcomed.
+
 =cut
 
 sub validate_replicants {
   my $self = shift @_;
   foreach my $replicant($self->all_replicants) {
-    if(
-      $replicant->is_replicating &&
-      $replicant->lag_behind_master <= $self->maximum_lag &&
-      $replicant->ensure_connected
-    ) {
-      $replicant->active(1)
+    if($self->_safely_ensure_connected($replicant)) {
+      my $is_replicating = $replicant->is_replicating;
+      unless(defined $is_replicating) {
+        $replicant->debugobj->print("Storage Driver ".ref($self)." Does not support the 'is_replicating' method.  Assuming you are manually managing.\n");
+        next;
+      } else {
+        if($is_replicating) {
+          my $lag_behind_master = $replicant->lag_behind_master;
+          unless(defined $lag_behind_master) {
+            $replicant->debugobj->print("Storage Driver ".ref($self)." Does not support the 'lag_behind_master' method.  Assuming you are manually managing.\n");
+            next;
+          } else {
+            if($lag_behind_master <= $self->maximum_lag) {
+              $replicant->active(1);
+            } else {
+              $replicant->active(0);  
+            }
+          }    
+        } else {
+          $replicant->active(0);
+        }
+      }
     } else {
       $replicant->active(0);
     }
   }
   ## Mark that we completed this validation.  
-  $self->_last_validated(time);
+  $self->_last_validated(time);  
 }
 
 =head1 AUTHOR