File Coverage

blib/lib/Class/TLB.pm
Criterion Covered Total %
statement 70 77 90.9
branch 18 24 75.0
condition 2 4 50.0
subroutine 11 12 91.6
pod 7 7 100.0
total 108 124 87.1


line stmt bran cond sub pod time code
1             package Class::TLB;
2              
3 2     2   94228 use warnings;
  2         6  
  2         82  
4 2     2   28 use strict;
  2         4  
  2         83  
5              
6 2     2   11 use Carp;
  2         10  
  2         1079  
7              
8             #use Time::HiRes ;
9 2     2   3424 use List::PriorityQueue ;
  2         5730  
  2         3288  
10              
11             =head1 NAME
12              
13             Class::TLB - Transparent load balancing for any resource class.
14              
15             =head1 VERSION
16              
17             Version 0.01
18              
19             =cut
20              
21             our $VERSION = '0.01';
22              
23             =head1 SYNOPSIS
24              
25             my $tlb = Class::TLB->new() ;
26              
27             build a set of resource (dummy for instance) and register them
28              
29             foreach my $i ( 1 .. 3 ){
30             $tlb->tlb_register(Class::TLB::Dummy->new($i)) ;
31             }
32              
33             You can now use the object $tlb the same way you would use a single instance of resource.
34              
35             =head2 Example with instances of Class::TLB::Dummy:
36              
37             # doSomething, oneFail and doFail are implemented in the Dummy class.
38             $tlb->doSomething() ;
39              
40             The $tlb object will automatically balance the usage on the set of resources given and will avoid temporary resource failures:
41              
42             $tlb->oneFail() ; # This call is ok because only one resource will fail.
43              
44             $tlb->doFail() ; # This call will confess an error because there is an
45             # implementation error in the resource that makes it fail all the time.
46              
47              
48             =head2 Usage scenario:
49              
50             You can use a Class::TLB wrapper to balance the usage of a set of similar distant resources.
51              
52             In case the distant connection breaks in one of them, your client code will not suffer from it since
53             Class::TLB will avoid single resources failures.
54              
55             For this to work, your resource must die or confess in case of disconnection.
56              
57             In case there is a logical flaw in a resource method, Class::TLB will die with the error when you call it.
58              
59             Because Class::TLB will attempt to use each resource instance and fail if all of them are failing.
60              
61             =head1 BEST PRACTICES
62              
63             =head2 Fail, but fail fast
64              
65             If your resources represent a distant service accessed through the network, make sure that the connection failure dies quickly.
66              
67             Long connection timeouts can cause waiting queries to accumulate in your application and can lead to an interruption of service, even if the other resources of the pool are perfectly healthy.
68              
69             In particular, if your resources use cURL to connect to the distant service, make sure you set a short CURLOPT_CONNECTTIMEOUT (or CURLOPT_CONNECTTIMEOUT_MS) option.
70              
71             =head1 CAVEATS
72              
73             Your managed resources can not implement any of the methods implemented in Class::TLB.
74              
75             All Class::TLB methods are prefixed with 'tlb_', making a collision very unlikely.
76              
77             =head1 FUNCTIONS
78              
79             =head2 new
80              
81             =cut
82              
83             sub new {
84 1     1 1 81 my ( $class , $opts ) = @_ ;
85 1   50     10 $opts ||= {} ;
86 1   50     29 my $self = {
87             '_tlb_queue' => List::PriorityQueue->new(), # The queue
88             '_tlb_class' => undef , # The class of objects managed by this
89             '_tlb_prototype' => undef , # The prototype of a resource. Typically the first instance given.
90             '_tlb_usecount' => {} , # The usage count of each register object
91             '_tlb_rcount' => 0 ,
92             '_tlb_failpenalty' => $opts->{'failpenalty'} || 2 , # Delay a failed resource by 2 seconds
93             } ;
94 1         23 return bless $self , $class ;
95             }
96              
97             =head2 isa
98              
99             Overrides the UNIVERSAL::isa method to allow client code to transparently call isa method
100             on balanced resources.
101              
102             Usage:
103             if ( $this->isa('Class::TLB::Dummy')){
104             ...
105             }
106              
107             =cut
108              
109             sub isa{
110 3     3 1 584 my $o = shift;
111 3 50       12 unless( ref $o ){
112 0         0 return UNIVERSAL::isa($o , @_ );
113             }
114 3 100       12 if ( $o->tlb_prototype() ){
115 1         4 return $o->tlb_prototype()->isa(@_);
116             }
117 2         14 return UNIVERSAL::isa($o , @_ );
118             }
119              
120             =head2 can
121              
122             Overrides the UNIVERSAL::can method to allow client code to transparently call can method
123             on balanced resources.
124              
125             Usage:
126             if ( $this->can('doSomething')){
127             ...
128             }
129              
130             =cut
131              
132             sub can{
133 5     5 1 10 my $o = shift;
134 5 50       14 unless( ref $o ){
135 0         0 return UNIVERSAL::can($o , @_ );
136             }
137 5 100       81 if ( $o->tlb_prototype() ){
138 3         7 return $o->tlb_prototype()->can(@_);
139             }
140 2         36 return UNIVERSAL::can($o , @_ );
141             }
142              
143              
144              
145              
146             =head2 tlb_class
147              
148             Returns the class of resources being load balanced.
149              
150             usage:
151             my $class = $tlb->tlb_class() ;
152              
153             =cut
154              
155             sub tlb_class{
156 1     1 1 3 my ($self) =@_ ;
157 1         6 return $self->{'_tlb_class'} ;
158             }
159              
160             =head2 tlb_prototype
161              
162             Returns an instance of resources being load balanced.
163              
164             =cut
165              
166             sub tlb_prototype{
167 12     12 1 22 my ($self) = @_ ;
168 12         89 return $self->{'_tlb_prototype'} ;
169             }
170              
171             =head2 tlb_usecount
172              
173             Returns the usage statistic hash of all sources.
174              
175             usage:
176             my $hcount = $tlb->tlb_usecount() ;
177              
178             =cut
179              
180             sub tlb_usecount{
181 0     0 1 0 my ($self) = @_ ;
182 0         0 return $self->{'_tlb_usecount'} ;
183             }
184              
185              
186             =head2 tlb_register
187              
188             Registers a new resource to be managed by this load balancer.
189              
190             The first call of this methods records the expected resource class.
191             Subsequent calls will fail if the given resource is from a different class.
192              
193              
194             Usage:
195             $tlb->tlb_register($resource);
196              
197             =cut
198              
199             sub tlb_register{
200 4     4 1 715 my ( $self , $resource ) = @_ ;
201 4 100       14 unless( $resource ){
202 1         182 confess("Please give a resource");
203             }
204              
205 3         6 my $rclass = ref $resource ;
206 3 50       8 unless( $rclass ){
207 0         0 confess( $resource." must be a reference");
208             }
209 3         189 eval "require $rclass;";
210 3 50       12 if ( $@ ){
211 0         0 confess( $rclass." cannot be required: $@");
212             }
213             # Register the class
214 3 100       10 unless( $self->{'_tlb_class'} ){
215 1         2 $self->{'_tlb_class'} = $rclass ;
216 1         3 $self->{'_tlb_prototype'} = $resource ;
217             }else{
218             # Check it is the same class of resource
219 2 50       35 unless( $resource->isa($self->{'_tlb_class'}) ){
220 0         0 confess( $rclass." invalid. Please provide only ".$self->{'_tlb_class'}."'s");
221             }
222             }
223              
224             # All is fine
225             # The new resource is given the highest priority
226 3         14 $self->{'_tlb_queue'}->insert($resource, 0 );
227 3         75 $self->{'_tlb_usecount'}->{$resource} = 0 ;
228 3         6 $self->{'_tlb_rcount'} ++ ;
229 3         10 return $resource ;
230             }
231              
232              
233              
234             our $AUTOLOAD;
235             sub AUTOLOAD{
236 1004     1004   7635 my $self = shift ;
237 1004         1492 my @args = @_ ;
238             # Avoid implicit overriding of destroy method.
239 1004 50       2524 return if $AUTOLOAD =~ /::DESTROY$/ ;
240              
241 1004         1367 my $mname = $AUTOLOAD;
242 1004         3436 $mname =~ s/.*::// ;
243              
244 1004         1337 my $res = undef ;
245 1004         1068 my $error = undef ;
246              
247 1004         1578 my $ntry = $self->{'_tlb_rcount'} ;
248 1004         1355 my $tried = {} ;
249              
250              
251 1004         2695 while( keys %$tried < $ntry ){
252             # Pick a resource
253 1007         2986 my $r = $self->{'_tlb_queue'}->pop();
254 1007         10515 $tried->{$r} = 1 ;
255              
256 1007         1186 my $penalty = 0 ;
257             # Call the method with the rest of arguments
258 1007         1192 eval{
259 1007         2977 $res = $r->$mname(@args);
260             };
261 1007 100       2002 if ( $@ ){
262 4         6 $error = $@ ;
263 4         10 $penalty = $self->{'_tlb_failpenalty'} ;
264             }else{
265 1003         1351 $error = undef ;
266             }
267              
268 1007         1289 my $calltime = time() + $penalty ;
269              
270 1007         2338 $self->{'_tlb_usecount'}->{$r}++ ;
271              
272 1007         3315 $self->{'_tlb_queue'}->insert($r , $calltime);
273 1007 100       23253 unless( $error ){
274 1003         4882 return $res ;
275             }
276             }
277             # If we reach this without returning the result, it means an error has occured on all resources.
278 1         170 confess( $error ) ;
279             }
280              
281              
282              
283              
284             =head1 AUTHOR
285              
286             Jerome Eteve, C<< >>
287              
288             =head1 BUGS
289              
290             Please report any bugs or feature requests to
291             C, or through the web interface at
292             L.
293             I will be notified, and then you'll automatically be notified of progress on
294             your bug as I make changes.
295              
296             =head1 SUPPORT
297              
298             You can find documentation for this module with the perldoc command.
299              
300             perldoc Class::TLB
301              
302             You can also look for information at:
303              
304             =over 4
305              
306             =item * AnnoCPAN: Annotated CPAN documentation
307              
308             L
309              
310             =item * CPAN Ratings
311              
312             L
313              
314             =item * RT: CPAN's request tracker
315              
316             L
317              
318             =item * Search CPAN
319              
320             L
321              
322             =back
323              
324             =head1 ACKNOWLEDGEMENTS
325              
326             =head1 COPYRIGHT & LICENSE
327              
328             Copyright 2010 Jerome Eteve, all rights reserved.
329              
330             This program is free software; you can redistribute it and/or modify it
331             under the same terms as Perl itself.
332              
333             =cut
334              
335             1; # End of Class::TLB