X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FStorage%2FDBI%2FReplicated%2FPool.pm;h=44481c4452a4b3431dafbd0860a5a03f390ce3ec;hb=28cea3aa579791963ea117bdbfdbd4ee3e948555;hp=b1cdc821713f6e34043597235cfd241f5ba5cb1e;hpb=21fc471939bf782a3ae38f8c71a4563fe50592d3;p=dbsrgits%2FDBIx-Class-Historic.git diff --git a/lib/DBIx/Class/Storage/DBI/Replicated/Pool.pm b/lib/DBIx/Class/Storage/DBI/Replicated/Pool.pm index b1cdc82..44481c4 100644 --- a/lib/DBIx/Class/Storage/DBI/Replicated/Pool.pm +++ b/lib/DBIx/Class/Storage/DBI/Replicated/Pool.pm @@ -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 @@ -13,7 +18,7 @@ DBIx::Class::Storage::DBI::Replicated::Pool - Manage a pool of replicants This class is used internally by L. You shouldn't need to create instances of this class. - + =head1 DESCRIPTION In a replicated storage type, there is at least one replicant to handle the @@ -29,7 +34,7 @@ This class defines the following attributes. This is a number which defines the maximum allowed lag returned by the L method. The default is 0. In general, this should return a larger number when the replicant is lagging -behind it's master, however the implementation of this is database specific, so +behind its master, however the implementation of this is database specific, so don't count on this number having a fixed meaning. For example, MySQL will return a number of seconds that the replicating database is lagging. @@ -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, @@ -46,14 +51,14 @@ has 'maximum_lag' => ( =head2 last_validated This is an integer representing a time since the last time the replicants were -validated. It's nothing fancy, just an integer provided via the perl time +validated. It's nothing fancy, just an integer provided via the perl L builtin. =cut 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=>{ @@ -84,11 +89,11 @@ A hashref of replicant, with the key being the dsn and the value returning the actual replicant storage. For example if the $dsn element is something like: "dbi:SQLite:dbname=dbfile" - + You could access the specific replicant via: $schema->storage->replicants->{'dbname=dbfile'} - + This attributes also supports the following helper methods: =over 4 @@ -120,14 +125,15 @@ removes the replicant under $key from the pool has 'replicants' => ( is=>'rw', metaclass => 'Collection::Hash', - isa=>'HashRef[DBIx::Class::Storage::DBI]', + isa=>HashRef['Object'], default=>sub {{}}, provides => { 'set' => 'set_replicant', - 'get' => 'get_replicant', + 'get' => 'get_replicant', 'empty' => 'has_replicants', 'count' => 'num_replicants', 'delete' => 'delete_replicant', + 'values' => 'all_replicant_storages', }, ); @@ -137,24 +143,34 @@ This class defines the following methods. =head2 connect_replicants ($schema, Array[$connect_info]) -Given an array of $dsn suitable for connected to a database, create an -L object and store it in the -L attribute. +Given an array of $dsn or connect_info structures suitable for connected to a +database, create an L object +and store it in the L attribute. =cut sub connect_replicants { my $self = shift @_; my $schema = shift @_; - + 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; } - + return @newly_created; } @@ -168,14 +184,71 @@ 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); + +## It is undesirable for catalyst to connect at ->conect_replicants time, as +## connections should only happen on the first request that uses the database. +## So we try to set the driver without connecting, however this doesn't always +## work, as a driver may need to connect to determine the DB version, and this +## may fail. +## +## Why this is necessary at all, is that we need to have the final storage +## class to apply the Replicant role. + + $self->_safely($replicant, '->_determine_driver', sub { + $replicant->_determine_driver + }); + + 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 can 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) = @_; + + return $self->_safely($replicant, '->ensure_connected', sub { + $replicant->ensure_connected(@args) + }); +} + +=head2 _safely ($replicant, $name, $code) + +Execute C<$code> for operation C<$name> catching any exceptions and printing an +error message to the C<<$replicant->debugobj>>. + +Returns 1 on success and undef on failure. + +=cut + +sub _safely { + my ($self, $replicant, $name, $code) = @_; + + eval { + $code->() + }; + if ($@) { + $replicant + ->debugobj + ->print( + sprintf( "Exception trying to $name 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 +315,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