package DBIx::Class::Storage::DBI::Replicated::Pool;
-use Moose;
-use MooseX::AttributeHelpers;
-use DBIx::Class::Storage::DBI::Replicated::Replicant;
-use List::Util 'sum';
-use Scalar::Util 'reftype';
+use Moo;
+use Role::Tiny ();
+use List::Util ();
+use Scalar::Util qw(reftype);
use DBI ();
use Carp::Clan qw/^DBIx::Class/;
-use MooseX::Types::Moose qw/Num Int ClassName HashRef/;
-
-use namespace::clean -except => 'meta';
+use Try::Tiny;
+use DBIx::Class::Storage::DBI::Replicated::Types
+ qw(PositiveInteger Number DBICStorageDBI ClassName HashRef);
=head1 NAME
=head1 DESCRIPTION
In a replicated storage type, there is at least one replicant to handle the
-read only traffic. The Pool class manages this replicant, or list of
+read-only traffic. The Pool class manages this replicant, or list of
replicants, and gives some methods for querying information about their status.
=head1 ATTRIBUTES
has 'maximum_lag' => (
is=>'rw',
- isa=>Num,
- required=>1,
+ isa=>Number,
lazy=>1,
- default=>0,
+ default=>sub {0},
);
=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 L<time|perlfunc/time>
-builtin.
+built-in.
=cut
has 'last_validated' => (
is=>'rw',
- isa=>Int,
- reader=>'last_validated',
- writer=>'_last_validated',
+ isa=>PositiveInteger,
lazy=>1,
- default=>0,
+ default=>sub {0},
);
=head2 replicant_type ($classname)
has 'replicant_type' => (
is=>'ro',
isa=>ClassName,
- required=>1,
- default=>'DBIx::Class::Storage::DBI',
+ default=> sub{'DBIx::Class::Storage::DBI'},
handles=>{
'create_replicant' => 'new',
},
=head2 replicants
-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:
+A hashref of replicants, 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"
$schema->storage->replicants->{'dbname=dbfile'}
-This attributes also supports the following helper methods:
-
-=over 4
-
-=item set_replicant($key=>$storage)
-
-Pushes a replicant onto the HashRef under $key
-
-=item get_replicant($key)
-
-Retrieves the named replicant
-
-=item has_replicants
-
-Returns true if the Pool defines replicants.
+=cut
-=item num_replicants
+has 'replicants' => (
+ is => 'rw',
+ isa => HashRef,
+ default => sub { +{} },
+);
-The number of replicants in the pool
+has next_unknown_replicant_id => (
+ is => 'rw',
+ isa=>PositiveInteger
+ default => sub { 1 },
+);
-=item delete_replicant ($key)
+sub inc_unknown_replicant_id {
+ my $self = shift;
+ my $next = $self->next_unknown_replicant_id + 1;
+ $self->next_unknown_replicant_id($next);
+ return $next;
+}
-removes the replicant under $key from the pool
+=head2 master
-=back
+Reference to the master Storage.
=cut
-has 'replicants' => (
- is=>'rw',
- metaclass => 'Collection::Hash',
- isa=>HashRef['Object'],
- default=>sub {{}},
- provides => {
- 'set' => 'set_replicant',
- 'get' => 'get_replicant',
- 'empty' => 'has_replicants',
- 'count' => 'num_replicants',
- 'delete' => 'delete_replicant',
- 'values' => 'all_replicant_storages',
- },
+has master => (
+ is => 'rw',
+ isa =>DBICStorageDBI,
+ weak_ref => 1,
);
=head1 METHODS
$connect_info = [ $connect_info ]
if reftype $connect_info ne 'ARRAY';
- my $replicant = $self->connect_replicant($schema, $connect_info);
-
my $connect_coderef =
(reftype($connect_info->[0])||'') eq 'CODE' ? $connect_info->[0]
: (reftype($connect_info->[0])||'') eq 'HASH' &&
$connect_info->[0]->{dbh_maker};
my $dsn;
- if (not $connect_coderef) {
- $dsn = $connect_info->[0];
- $dsn = $dsn->{dsn} if (reftype($dsn)||'') eq 'HASH';
- }
- else {
-# yes this is evil, but it only usually happens once
+ my $replicant = do {
+ ## yes this is evil, but it only usually happens once (for coderefs)
+ ## this will fail if the coderef does not actually DBI::connect
no warnings 'redefine';
my $connect = \&DBI::connect;
local *DBI::connect = sub {
$dsn = $_[1];
goto $connect;
};
- $connect_coderef->();
+ $self->connect_replicant($schema, $connect_info);
+ };
+
+ my $key;
+
+ if (!$dsn) {
+ if (!$connect_coderef) {
+ $dsn = $connect_info->[0];
+ $dsn = $dsn->{dsn} if (reftype($dsn)||'') eq 'HASH';
+ }
+ else {
+ # all attempts to get the DSN failed
+ $key = "UNKNOWN_" . $self->next_unknown_replicant_id;
+ $self->inc_unknown_replicant_id;
+ }
+ }
+ if ($dsn) {
+ $replicant->dsn($dsn);
+ ($key) = ($dsn =~ m/^dbi\:.+\:(.+)$/i);
}
- $replicant->dsn($dsn);
- my ($key) = ($dsn =~ m/^dbi\:.+\:(.+)$/i);
- $self->set_replicant($key => $replicant);
+ if($key) {
+ $replicant->id($key);
+ } else {
+ $replicant->debugobj->print("Could not create an ID for the replicant!");
+ }
+
+ ## Add the new replicant to the list
+ $self->replicants({
+ $key => $replicant,
+ %{$self->replicants},
+ });
+
push @newly_created, $replicant;
}
my $replicant = $self->create_replicant($schema);
$replicant->connect_info($connect_info);
-## It is undesirable for catalyst to connect at ->conect_replicants time, as
+## It is undesirable for catalyst to connect at ->connect_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
$replicant->_determine_driver
});
- DBIx::Class::Storage::DBI::Replicated::Replicant->meta->apply($replicant);
+ Role::Tiny->apply_roles_to_object($replicant, 'DBIx::Class::Storage::DBI::Replicated::Replicant');
+
+ # link back to master
+ $replicant->master($self->master);
+
return $replicant;
}
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
+way a slave can go completely offline (e.g. the box itself can die) without
bringing down your entire pool of databases.
=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;
+ return try {
+ $code->();
+ 1;
+ } catch {
+ $replicant->debugobj->print(sprintf(
+ "Exception trying to $name for replicant %s, error is %s",
+ $replicant->_dbi_connect_info->[0], $_)
+ );
+ undef;
+ };
}
=head2 connected_replicants
sub connected_replicants {
my $self = shift @_;
- return sum( map {
+ return List::Util::sum( map {
$_->connected ? 1:0
} $self->all_replicants );
}
inactive, and thus removed from the replication pool.
This tests L<all_replicants>, since a replicant that has been previous marked
-as inactive can be reactived should it start to pass the validation tests again.
+as inactive can be reactivated should it start to pass the validation tests again.
See L<DBIx::Class::Storage::DBI> for more about checking if a replicating
connection is not following a master or is lagging.
}
}
## Mark that we completed this validation.
- $self->_last_validated(time);
+ $self->last_validated(time);
}
=head1 AUTHOR
-John Napiorkowski <john.napiorkowski@takkle.com>
+John Napiorkowski <jjnapiork@cpan.org>
=head1 LICENSE
=cut
-__PACKAGE__->meta->make_immutable;
-
1;