File Coverage

blib/lib/Perlbal/Pool.pm
Criterion Covered Total %
statement 64 163 39.2
branch 4 42 9.5
condition 0 7 0.0
subroutine 15 28 53.5
pod 0 16 0.0
total 83 256 32.4


line stmt bran cond sub pod time code
1             ######################################################################
2             # Pool class
3             ######################################################################
4             #
5             # Copyright 2004, Danga Interactive, Inc.
6             # Copyright 2005-2007, Six Apart, Ltd.
7             #
8              
9             package Perlbal::Pool;
10 22     22   126 use strict;
  22         48  
  22         1324  
11 22     22   119 use warnings;
  22         44  
  22         834  
12              
13 22     22   124 use Perlbal::BackendHTTP;
  22         39  
  22         515  
14              
15             # how often to reload the nodefile
16 22     22   167 use constant NODEFILE_RELOAD_FREQ => 3;
  22         232  
  22         1962  
17              
18             # balance methods we support (note: sendstats mode is now removed)
19 22     22   126 use constant BM_ROUNDROBIN => 2;
  22         47  
  22         993  
20 22     22   127 use constant BM_RANDOM => 3;
  22         61  
  22         1458  
21              
22             use fields (
23 22         245 'name', # string; name of this pool
24             'use_count', # int; number of services using us
25             'nodes', # arrayref; [ip, port] values (port defaults to 80)
26             'node_count', # int; number of nodes
27             'node_used', # hashref; { ip:port => use count }
28             'balance_method', # int; BM_ constant from above
29              
30             # used in nodefile mode
31             'nodefile', # string; filename to read nodes from
32             'nodefile.lastmod', # unix time nodefile was last modified
33             'nodefile.lastcheck', # unix time nodefile was last stated
34             'nodefile.checking', # boolean; if true AIO is stating the file for us
35 22     22   147 );
  22         66  
36              
37             sub new {
38 9     9 0 31 my Perlbal::Pool $self = shift;
39 9 50       66 $self = fields::new($self) unless ref $self;
40              
41 9         998 my ($name) = @_;
42              
43 9         29 $self->{name} = $name;
44 9         31 $self->{use_count} = 0;
45              
46 9         33 $self->{nodes} = [];
47 9         23 $self->{node_count} = 0;
48 9         23 $self->{node_used} = {};
49              
50 9         29 $self->{nodefile} = undef;
51 9         22 $self->{balance_method} = BM_RANDOM;
52              
53 9         47 return $self;
54             }
55              
56             sub set {
57 0     0 0 0 my Perlbal::Pool $self = shift;
58              
59 0         0 my ($key, $val, $mc) = @_;
60 0     0   0 my $set = sub { $self->{$key} = $val; return $mc->ok; };
  0         0  
  0         0  
61              
62 0 0       0 if ($key eq 'nodefile') {
63             # allow to unset it, which stops us from checking it further,
64             # but doesn't clear our current list of nodes
65 0 0       0 if ($val =~ /^(?:none|undef|null|""|'')$/) {
66 0         0 $self->{'nodefile'} = undef;
67 0         0 $self->{'nodefile.lastmod'} = 0;
68 0         0 $self->{'nodefile.checking'} = 0;
69 0         0 $self->{'nodefile.lastcheck'} = 0;
70 0         0 return $mc->ok;
71             }
72              
73             # enforce that it exists from here on out
74 0 0       0 return $mc->err("File not found")
75             unless -e $val;
76              
77             # force a reload
78 0         0 $self->{'nodefile'} = $val;
79 0         0 $self->{'nodefile.lastmod'} = 0;
80 0         0 $self->{'nodefile.checking'} = 0;
81 0         0 $self->load_nodefile;
82 0         0 $self->{'nodefile.lastcheck'} = time;
83 0         0 return $mc->ok;
84             }
85              
86 0 0       0 if ($key eq "balance_method") {
87 0         0 $val = {
88             'random' => BM_RANDOM,
89             }->{$val};
90 0 0       0 return $mc->err("Unknown balance method")
91             unless $val;
92 0         0 return $set->();
93             }
94              
95             }
96              
97             sub dumpconfig {
98 0     0 0 0 my Perlbal::Pool $self = shift;
99 0         0 my $name = $self->{name};
100              
101 0         0 my @return;
102              
103 0 0       0 if (my $nodefile = $self->{'nodefile'}) {
104 0         0 push @return, "SET nodefile = $nodefile";
105             } else {
106 0         0 foreach my $node (@{$self->{nodes}}) {
  0         0  
107 0         0 my ($ip, $port) = @$node;
108 0         0 push @return, "POOL ADD $name $ip:$port";
109             }
110             }
111 0         0 return @return;
112             }
113              
114             # returns string of balance method
115             sub balance_method {
116 0     0 0 0 my Perlbal::Pool $self = $_[0];
117 0         0 my $methods = {
118             &BM_ROUNDROBIN => "round_robin",
119             &BM_RANDOM => "random",
120             };
121 0   0     0 return $methods->{$self->{balance_method}} || $self->{balance_method};
122             }
123              
124             sub load_nodefile {
125 0     0 0 0 my Perlbal::Pool $self = shift;
126 0 0       0 return 0 unless $self->{'nodefile'};
127              
128 0 0       0 if ($Perlbal::OPTMOD_LINUX_AIO) {
129 0         0 return $self->_load_nodefile_async;
130             } else {
131 0         0 return $self->_load_nodefile_sync;
132             }
133             }
134              
135             sub _parse_nodefile {
136 0     0   0 my Perlbal::Pool $self = shift;
137 0         0 my $dataref = shift;
138              
139 0         0 my @nodes = split(/\r?\n/, $$dataref);
140              
141             # prepare for adding nodes
142 0         0 $self->{nodes} = [];
143 0         0 $self->{node_used} = {};
144              
145 0         0 foreach (@nodes) {
146 0         0 s/\#.*//;
147 0 0       0 if (/(\d+\.\d+\.\d+\.\d+)(?::(\d+))?/) {
148 0         0 my ($ip, $port) = ($1, $2);
149 0   0     0 $port ||= 80;
150 0   0     0 $self->{node_used}->{"$ip:$port"} ||= 0; # set to 0 if not set
151 0         0 push @{$self->{nodes}}, [ $ip, $port ];
  0         0  
152             }
153             }
154              
155             # setup things using new data
156 0         0 $self->{node_count} = scalar @{$self->{nodes}};
  0         0  
157             }
158              
159             sub _load_nodefile_sync {
160 0     0   0 my Perlbal::Pool $self = shift;
161              
162 0         0 my $mod = (stat($self->{nodefile}))[9];
163 0 0       0 return if $mod == $self->{'nodefile.lastmod'};
164 0         0 $self->{'nodefile.lastmod'} = $mod;
165              
166 0 0       0 open NODEFILE, $self->{nodefile} or return;
167 0         0 my $nodes;
168 0         0 { local $/ = undef; $nodes = ; }
  0         0  
  0         0  
169 0         0 close NODEFILE;
170 0         0 $self->_parse_nodefile(\$nodes);
171             }
172              
173             sub _load_nodefile_async {
174 0     0   0 my Perlbal::Pool $self = shift;
175              
176 0 0       0 return if $self->{'nodefile.checking'};
177 0         0 $self->{'nodefile.checking'} = 1;
178              
179             Perlbal::AIO::aio_stat($self->{nodefile}, sub {
180 0     0   0 $self->{'nodefile.checking'} = 0;
181              
182             # this might have gotten unset while we were out statting the file, which
183             # means that the user has instructed us not to use a node file, and may
184             # have changed the nodes in the pool, so we should do nothing and return
185 0 0       0 return unless $self->{'nodefile'};
186              
187             # ignore if the file doesn't exist
188 0 0       0 return unless -e _;
189              
190 0         0 my $mod = (stat(_))[9];
191 0 0       0 return if $mod == $self->{'nodefile.lastmod'};
192 0         0 $self->{'nodefile.lastmod'} = $mod;
193              
194             # construct a filehandle (we only have a descriptor here)
195 0 0       0 open NODEFILE, $self->{nodefile}
196             or return;
197 0         0 my $nodes;
198 0         0 { local $/ = undef; $nodes = ; }
  0         0  
  0         0  
199 0         0 close NODEFILE;
200              
201 0         0 $self->_parse_nodefile(\$nodes);
202 0         0 return;
203 0         0 });
204              
205 0         0 return 1;
206             }
207              
208             sub add {
209 14     14 0 40 my Perlbal::Pool $self = shift;
210 14         44 my ($ip, $port) = @_;
211              
212 14         69 $self->remove($ip, $port); # no dupes
213              
214 14         65 $self->{node_used}->{"$ip:$port"} = 0;
215 14         53 push @{$self->{nodes}}, [ $ip, $port ];
  14         65  
216 14         28 $self->{node_count} = scalar(@{$self->{nodes}});
  14         111  
217             }
218              
219             sub remove {
220 14     14 0 30 my Perlbal::Pool $self = shift;
221 14         37 my ($ip, $port) = @_;
222              
223 14         62 delete $self->{node_used}->{"$ip:$port"};
224 14         109 @{$self->{nodes}} = grep { "$_->[0]:$_->[1]" ne "$ip:$port" } @{$self->{nodes}};
  14         48  
  8         54  
  14         51  
225 14         31 $self->{node_count} = scalar(@{$self->{nodes}});
  14         58  
226             }
227              
228             sub get_backend_endpoint {
229 20     20 0 39 my Perlbal::Pool $self = $_[0];
230              
231 20         34 my @endpoint; # (IP,port)
232              
233             # re-load nodefile if necessary
234 20 50       88 if ($self->{nodefile}) {
235 0         0 my $now = time;
236 0 0       0 if ($now > $self->{'nodefile.lastcheck'} + NODEFILE_RELOAD_FREQ) {
237 0         0 $self->{'nodefile.lastcheck'} = $now;
238 0         0 $self->load_nodefile;
239             }
240             }
241              
242             # no nodes?
243 20 50       73 return () unless $self->{node_count};
244              
245             # pick one randomly
246 20         31 return @{$self->{nodes}[int(rand($self->{node_count}))]};
  20         191  
247             }
248              
249             sub backend_should_live {
250 120     120 0 234 my Perlbal::Pool $self = $_[0];
251 120         244 my Perlbal::BackendHTTP $be = $_[1];
252              
253             # a backend stays alive if we still have users. eventually this whole
254             # function might do more and actually take into account the individual
255             # backend, but for now, this suits us.
256 120 50       1022 return 1 if $self->{use_count};
257 0         0 return 0;
258             }
259              
260             sub node_count {
261 171     171 0 343 my Perlbal::Pool $self = $_[0];
262 171         1113 return $self->{node_count};
263             }
264              
265             sub nodes {
266 0     0 0 0 my Perlbal::Pool $self = $_[0];
267 0         0 return $self->{nodes};
268             }
269              
270             sub node_used {
271 0     0 0 0 my Perlbal::Pool $self = $_[0];
272 0         0 return $self->{node_used}->{$_[1]};
273             }
274              
275             sub mark_node_used {
276 135     135 0 263 my Perlbal::Pool $self = $_[0];
277 135         753 $self->{node_used}->{$_[1]}++;
278             }
279              
280             sub increment_use_count {
281 9     9 0 29 my Perlbal::Pool $self = $_[0];
282 9         34 $self->{use_count}++;
283             }
284              
285             sub decrement_use_count {
286 0     0 0   my Perlbal::Pool $self = $_[0];
287 0           $self->{use_count}--;
288             }
289              
290             sub name {
291 0     0 0   my Perlbal::Pool $self = $_[0];
292 0           return $self->{name};
293             }
294              
295             1;
296              
297             # Local Variables:
298             # mode: perl
299             # c-basic-indent: 4
300             # indent-tabs-mode: nil
301             # End: