Commit 205bf2f5 authored by Andy Yates's avatar Andy Yates
Browse files

We can now set a reconnection timeout outside of the normal realms of MySQL...

We can now set a reconnection timeout outside of the normal realms of MySQL disconnect/reconnect. This means we have something closer to a pooled connection which stays active for as long as people need it & dies off at the required rate
parent 8c63460a
......@@ -28,6 +28,9 @@ for one backing connection to be used for multiple DBs
my $dbc = Bio::EnsEMBL::DBSQL::DBConnection->new(-HOST => 'host', -PORT => 3306, -USER => 'user');
my $p_h_dbc = Bio::EnsEMBL::DBSQL::ProxyDBConnection->new(-DBC => $dbc, -DBNAME => 'human');
my $p_m_dbc = Bio::EnsEMBL::DBSQL::ProxyDBConnection->new(-DBC => $dbc, -DBNAME => 'mouse');
# With a 10 minute timeout reconnection in milliseconds
my $p_h_rc_dbc = Bio::EnsEMBL::DBSQL::ProxyDBConnection->new(-DBC => $dbc, -DBNAME => 'human', -RECONNECT_INTERVAL => (10*60*1000));
=head1 DESCRIPTION
......@@ -48,16 +51,22 @@ package Bio::EnsEMBL::DBSQL::ProxyDBConnection;
use strict;
use warnings;
use Bio::EnsEMBL::Utils::Exception qw/warning/;
use base qw/Bio::EnsEMBL::Utils::Proxy/;
use Bio::EnsEMBL::Utils::Argument qw/rearrange/;
use Bio::EnsEMBL::Utils::Exception qw/warning throw/;
use Bio::EnsEMBL::Utils::SqlHelper;
use base qw/Bio::EnsEMBL::Utils::Proxy/;
use Time::HiRes qw/time/;
sub new {
my ($class, @args) = @_;
my ($dbc, $dbname) = rearrange([qw/DBC DBNAME/], @args);
my ($dbc, $dbname, $reconnect_interval) = rearrange([qw/DBC DBNAME RECONNECT_INTERVAL/], @args);
throw "No DBConnection -DBC given" unless $dbc;
throw "No database name -DBNAME given" unless $dbname;
my $self = $class->SUPER::new($dbc);
$self->dbname($dbname);
$self->reconnect_interval($reconnect_interval) if $reconnect_interval;
return $self;
}
......@@ -121,6 +130,82 @@ sub switch_database {
return $switch;
}
=head2 check_reconnection
Description : Looks to see if the last time we used the backing DBI
connection was greater than the reconnect_interval()
provided at construction or runtime. If enought time has
elapsed then a reconnection is attempted. We do not
attempt a reconnection if:
- No reconnect_interval was set
- The connection was not active
Exceptions : None apart from those raised from the reconnect() method
from DBConnection
=cut
sub check_reconnection {
my ($self) = @_;
#Return early if we had no reconnection interval
return unless $self->{reconnect_interval};
my $proxy = $self->__proxy();
#Only attempt it if we were connected; otherwise we can just skip
if($proxy->connected()) {
if($self->_require_reconnect()) {
$proxy->reconnect();
}
$self->_last_used();
}
return;
}
# Each time this is called we record the current time in seconds
# to be used by the _require_reconnect() method
sub _last_used {
my ($self) = @_;
$self->{_last_used} = int(time()*1000);
return;
}
# Uses the _last_used() time and the current reconnect_interval() to decide
# if the connection has been unused for long enough that we should attempt
# a reconnect
sub _require_reconnect {
my ($self) = @_;
my $interval = $self->reconnect_interval();
return unless $interval;
my $last_used = $self->{_last_used};
my $time_elapsed = int(time()*1000) - $last_used;
return $time_elapsed > $interval ? 1 : 0;
}
=head2 reconnect_interval
Arg[1] : Integer reconnection interval in milliseconds
Description : Accessor for the reconnection interval expressed in milliseconds
Returntype : Int miliseconds for a reconnection interval
=cut
sub reconnect_interval {
my ($self, $reconnect_interval) = @_;
$self->{'reconnect_interval'} = $reconnect_interval if defined $reconnect_interval;
return $self->{'reconnect_interval'};
}
=head2 dbname
Arg[1] : String DB name
Description : Accessor for the name of the database we should use whenever
a DBConnection request is made via this class
Returntype : String the name of the database which we should use
Exceptions : None
=cut
sub dbname {
my ($self, $dbname) = @_;
$self->{'dbname'} = $dbname if defined $dbname;
......@@ -136,13 +221,26 @@ my %SWITCH_METHODS = map { $_ => 1 } qw/
work_with_db_handle
/;
# Manual override of the SqlHelper accessor to ensure it always gets the Proxy
sub sql_helper {
my ($self) = @_;
if(! exists $self->{_sql_helper}) {
my $helper = Bio::EnsEMBL::Utils::SqlHelper->new(-DB_CONNECTION => $self);
$self->{_sql_helper} = $helper;
}
return $self->{_sql_helper};
}
sub __resolver {
my ($self, $package, $method) = @_;
if($self->__proxy()->can($method)) {
if($SWITCH_METHODS{$method}) {
return sub {
my ($local_self, @args) = @_;
$local_self->check_reconnection();
$local_self->switch_database();
$local_self->_last_used();
return $local_self->__proxy()->$method(@args);
};
}
......
use lib 't';
use strict;
use warnings;
use Test::More tests => 38;
use Test::More;
use Test::MockObject::Extends;
use Time::HiRes qw/usleep/;
use Bio::EnsEMBL::Test::MultiTestDB;
use Bio::EnsEMBL::DBSQL::SliceAdaptor;
use Bio::EnsEMBL::Test::TestUtils;
use Bio::EnsEMBL::DBSQL::DBConnection;
our $verbose = 0;
use Bio::EnsEMBL::DBSQL::ProxyDBConnection;
#
# 1 DBConnection compiles
......@@ -220,3 +219,35 @@ $dbc->disconnect_when_inactive();
my $quote_result = $dbc->quote_identifier(qw/a b c/, [undef, qw/db table/], [1]);
is_deeply($quote_result, [qw/`a` `b` `c`/, '`db`.`table`', '`1`'], 'Checking quote identifier will quote everything') or diag explain $quote_result;
#Testing reconnection via proxy
$db->dbc()->disconnect_if_idle();
#my $dbc_copy = bless({%{$db->dbc()}}, ref($db->dbc()));
#$dbc_copy = Test::MockObject::Extends->new($dbc_copy);
my $dbc_copy = mock_object($dbc);
{
my $pdbc = Bio::EnsEMBL::DBSQL::ProxyDBConnection->new(-DBC => $dbc_copy, -DBNAME => $dbc->dbname(), -RECONNECT_INTERVAL => 0);
is($pdbc->sql_helper()->execute_single_result(-SQL => 'select 1'), 1, 'Checking we get a 1 back from the DB');
usleep(1000); #sleep 1ms
is($pdbc->sql_helper()->execute_single_result(-SQL => 'select 1'), 1, 'Checking we get a 1 back from the DB');
$dbc_copy->__is_called('reconnect', 0, "No need to reconnect as we have no interval set");
#Disconnect, set the interval to 1ms and sleep for 10ms. Skips reconnection as the connection is not active
$dbc_copy->disconnect_if_idle();
$pdbc->reconnect_interval(1);
usleep((10*1000));
$pdbc->check_reconnection();
$dbc_copy->__is_called('connected', 1, "connected() was called since we had to check if the DBConnection was active");
$dbc_copy->__is_called('reconnect', 0, "No need to reconnect as we have no interval set");
#Connect, set the interval to 1ms and sleep for 10ms. Reconnect
$dbc_copy->connect();
$dbc_copy->__clear();
usleep((10*1000));
$pdbc->check_reconnection();
$dbc_copy->__is_called('connected', 1, "connected() was called since we had to check if the DBConnection was active");
$dbc_copy->__is_called('reconnect', 1, "reconnect() as we had gone beyond our normal timeout interval");
}
done_testing();
1;
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment