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 |