File Coverage

blib/lib/Aniki/Handler/WeightedRoundRobin.pm
Criterion Covered Total %
statement 26 53 49.0
branch 1 18 5.5
condition 0 3 0.0
subroutine 8 16 50.0
pod 2 10 20.0
total 37 100 37.0


line stmt bran cond sub pod time code
1             package Aniki::Handler::WeightedRoundRobin;
2 2     2   90228 use 5.014002;
  2         8  
3              
4 2     2   419 use namespace::autoclean;
  2         17214  
  2         9  
5 2     2   815 use Mouse;
  2         30479  
  2         15  
6             extends qw/Aniki::Handler/;
7              
8 2     2   2992 use DBI ();
  2         14784  
  2         57  
9 2     2   18 use Data::WeightedRoundRobin;
  2         5  
  2         52  
10 2     2   9 use Scalar::Util qw/refaddr/;
  2         4  
  2         1189  
11              
12             around BUILDARGS => sub {
13             my $orig = shift;
14             my $self = shift;
15             my %args = (@_ == 1 && ref $_[0] eq 'HASH') ? %{$_[0]} : @_;
16              
17             my $connect_info = delete $args{connect_info};
18             my $rr = Data::WeightedRoundRobin->new([
19             map {
20             +{
21             %$_,
22             key => refaddr($_->{value}),
23             }
24             } @$connect_info
25             ]);
26             return $self->$orig(rr => $rr);
27             };
28              
29             has rr => (
30             is => 'ro',
31             required => 1,
32             );
33              
34             has '+connect_info' => (
35             is => 'rw',
36             required => 0,
37             lazy => 1,
38             builder => sub { shift->rr->next },
39             clearer => '_reset_connect_info',
40             );
41              
42             sub is_connect_error {
43 1     1 0 656 my ($self, $e) = @_;
44 1         3 my ($dsn) = @{ $self->connect_info };
  1         6  
45 1         31 my (undef, $driver) = DBI->parse_dsn($dsn);
46              
47 1 50       33 if ($driver eq 'mysql') {
    0          
    0          
48 1         8 return $e =~ /\Qfailed: Can't connect to MySQL server on/m;
49             }
50             elsif ($driver eq 'Pg') {
51 0         0 return $e =~ /\Qfailed: could not connect to server: Connection refused/m;
52             }
53             elsif ($driver eq 'Oracle') {
54             # TODO: patches wellcome :p
55             }
56              
57 0         0 warn "Unsupported dirver: $driver";
58 0         0 return 0;
59             }
60              
61             sub disconnect {
62 101     101 0 2967 my $self = shift;
63 101         243 $self->_reset_connect_info();
64 101         295 $self->SUPER::disconnect();
65             }
66              
67             my %NO_OVERRIDE_PROXY_METHODS = (
68             trace_query_set_comment => 1,
69             in_txn => 1,
70             );
71              
72             for my $name (grep { !$NO_OVERRIDE_PROXY_METHODS{$_} } __PACKAGE__->_proxy_methods) {
73             # override
74             __PACKAGE__->meta->add_method($name => sub {
75 0     0 1   my $self = shift;
        0 0    
        0 0    
        0 0    
        0 0    
        0 1    
        0 0    
        0 0    
76 0           my $wantarray = wantarray;
77              
78             # context proxy
79 0           my @ret;
80 0           my $e = do {
81 0           local $@;
82              
83 0 0         if (not defined $wantarray) {
    0          
84 0           eval { $self->handler->$name(@_) };
  0            
85             }
86             elsif ($wantarray) {
87 0           @ret = eval { $self->handler->$name(@_) };
  0            
88             }
89             else {
90 0           $ret[0] = eval { $self->handler->$name(@_) };
  0            
91             }
92              
93 0           $@;
94             };
95              
96 0 0         if ($e) {
97 0           my $key = refaddr($self->connect_info);
98 0 0 0       if ($self->is_connect_error($e) && !$self->handler->in_txn) {
99 0           $self->disconnect;
100              
101             # retry
102 0           my $guard = $self->rr->save;
103 0           $self->rr->remove($key);
104 0 0         if ($self->rr->next) {
105 0           warn "RETRY: $e";
106 0           return $self->$name(@_);
107             }
108             }
109 0           die $e;
110             }
111              
112 0 0         return $wantarray ? @ret : $ret[0];
113             });
114             }
115              
116             __PACKAGE__->meta->make_immutable();
117             __END__
118              
119             =pod
120              
121             =encoding utf-8
122              
123             =head1 NAME
124              
125             Aniki::Handler::RoundRobin - Round robin database handler manager
126              
127             =head1 METHODS
128              
129             =head2 CLASS METHODS
130              
131             =head3 C<new(%args) : Aniki::Handler::RoundRobin>
132              
133             Create instance of Aniki::Handler.
134              
135             =head4 Arguments
136              
137             =over 4
138              
139             =item C<connect_info : ArrayRef[HashRef]>
140              
141             Auguments for L<Data::WeightedRoundRobin>'s C<new> method.
142              
143             Example:
144              
145             [
146             {
147             value => [...], # Auguments for DBI's connect method.
148             weight => 10,
149             },
150             ]
151              
152             =item on_connect_do : CodeRef|ArrayRef[Str]|Str
153             =item on_disconnect_do : CodeRef|ArrayRef[Str]|Str
154              
155             Execute SQL or CodeRef when connected/disconnected.
156              
157             =back