root/trunk/lib/Perlbal/Pool.pm

Revision 824, 7.6 kB (checked in by hachi, 2 months ago)

Rudimentary dumpconfig command, 90% working.

This reverts commit c5dc669c6250b46f82a6cc4da38f45be7f45c296.

(Yes, that would be a double-revert that I am doing.)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1######################################################################
2# Pool class
3######################################################################
4#
5# Copyright 2004, Danga Interactive, Inc.
6# Copyright 2005-2007, Six Apart, Ltd.
7#
8
9package Perlbal::Pool;
10use strict;
11use warnings;
12
13use Perlbal::BackendHTTP;
14
15# how often to reload the nodefile
16use constant NODEFILE_RELOAD_FREQ => 3;
17
18# balance methods we support (note: sendstats mode is now removed)
19use constant BM_ROUNDROBIN => 2;
20use constant BM_RANDOM => 3;
21
22use fields (
23            '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            );
36
37sub new {
38    my Perlbal::Pool $self = shift;
39    $self = fields::new($self) unless ref $self;
40
41    my ($name) = @_;
42
43    $self->{name} = $name;
44    $self->{use_count} = 0;
45
46    $self->{nodes} = [];
47    $self->{node_count} = 0;
48    $self->{node_used} = {};
49
50    $self->{nodefile} = undef;
51    $self->{balance_method} = BM_RANDOM;
52
53    return $self;
54}
55
56sub set {
57    my Perlbal::Pool $self = shift;
58
59    my ($key, $val, $mc) = @_;
60    my $set = sub { $self->{$key} = $val; return $mc->ok; };
61
62    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        if ($val =~ /^(?:none|undef|null|""|'')$/) {
66            $self->{'nodefile'} = undef;
67            $self->{'nodefile.lastmod'} = 0;
68            $self->{'nodefile.checking'} = 0;
69            $self->{'nodefile.lastcheck'} = 0;
70            return $mc->ok;
71        }
72
73        # enforce that it exists from here on out
74        return $mc->err("File not found")
75            unless -e $val;
76
77        # force a reload
78        $self->{'nodefile'} = $val;
79        $self->{'nodefile.lastmod'} = 0;
80        $self->{'nodefile.checking'} = 0;
81        $self->load_nodefile;
82        $self->{'nodefile.lastcheck'} = time;
83        return $mc->ok;
84    }
85
86    if ($key eq "balance_method") {
87        $val = {
88            'random' => BM_RANDOM,
89        }->{$val};
90        return $mc->err("Unknown balance method")
91            unless $val;
92        return $set->();
93    }
94
95}
96
97sub dumpconfig {
98    my Perlbal::Pool $self = shift;
99    my $name = $self->{name};
100
101    my @return;
102
103    if (my $nodefile = $self->{'nodefile'}) {
104        push @return, "SET nodefile = $nodefile";
105    } else {
106        foreach my $node (@{$self->{nodes}}) {
107            my ($ip, $port) = @$node;
108            push @return, "POOL ADD $name $ip:$port";
109        }
110    }
111    return @return;
112}
113
114# returns string of balance method
115sub balance_method {
116    my Perlbal::Pool $self = $_[0];
117    my $methods = {
118        &BM_ROUNDROBIN => "round_robin",
119        &BM_RANDOM => "random",
120    };
121    return $methods->{$self->{balance_method}} || $self->{balance_method};
122}
123
124sub load_nodefile {
125    my Perlbal::Pool $self = shift;
126    return 0 unless $self->{'nodefile'};
127
128    if ($Perlbal::OPTMOD_LINUX_AIO) {
129        return $self->_load_nodefile_async;
130    } else {
131        return $self->_load_nodefile_sync;
132    }
133}
134
135sub _parse_nodefile {
136    my Perlbal::Pool $self = shift;
137    my $dataref = shift;
138
139    my @nodes = split(/\r?\n/, $$dataref);
140
141    # prepare for adding nodes
142    $self->{nodes} = [];
143    $self->{node_used} = {};
144
145    foreach (@nodes) {
146        s/\#.*//;
147        if (/(\d+\.\d+\.\d+\.\d+)(?::(\d+))?/) {
148            my ($ip, $port) = ($1, $2);
149            $port ||= 80;
150            $self->{node_used}->{"$ip:$port"} ||= 0; # set to 0 if not set
151            push @{$self->{nodes}}, [ $ip, $port ];
152        }
153    }
154
155    # setup things using new data
156    $self->{node_count} = scalar @{$self->{nodes}};
157}
158
159sub _load_nodefile_sync {
160    my Perlbal::Pool $self = shift;
161
162    my $mod = (stat($self->{nodefile}))[9];
163    return if $mod == $self->{'nodefile.lastmod'};
164    $self->{'nodefile.lastmod'} = $mod;
165
166    open NODEFILE, $self->{nodefile} or return;
167    my $nodes;
168    { local $/ = undef; $nodes = <NODEFILE>; }
169    close NODEFILE;
170    $self->_parse_nodefile(\$nodes);
171}
172
173sub _load_nodefile_async {
174    my Perlbal::Pool $self = shift;
175
176    return if $self->{'nodefile.checking'};
177    $self->{'nodefile.checking'} = 1;
178
179    Perlbal::AIO::aio_stat($self->{nodefile}, sub {
180        $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        return unless $self->{'nodefile'};
186
187        # ignore if the file doesn't exist
188        return unless -e _;
189
190        my $mod = (stat(_))[9];
191        return if $mod == $self->{'nodefile.lastmod'};
192        $self->{'nodefile.lastmod'} = $mod;
193
194        # construct a filehandle (we only have a descriptor here)
195        open NODEFILE, $self->{nodefile}
196            or return;
197        my $nodes;
198        { local $/ = undef; $nodes = <NODEFILE>; }
199        close NODEFILE;
200
201        $self->_parse_nodefile(\$nodes);
202        return;
203    });
204
205    return 1;
206}
207
208sub add {
209    my Perlbal::Pool $self = shift;
210    my ($ip, $port) = @_;
211
212    $self->remove($ip, $port); # no dupes
213
214    $self->{node_used}->{"$ip:$port"} = 0;
215    push @{$self->{nodes}}, [ $ip, $port ];
216    $self->{node_count} = scalar(@{$self->{nodes}});
217}
218
219sub remove {
220    my Perlbal::Pool $self = shift;
221    my ($ip, $port) = @_;
222
223    delete $self->{node_used}->{"$ip:$port"};
224    @{$self->{nodes}} = grep { "$_->[0]:$_->[1]" ne "$ip:$port" } @{$self->{nodes}};
225    $self->{node_count} = scalar(@{$self->{nodes}});
226}
227
228sub get_backend_endpoint {
229    my Perlbal::Pool $self = $_[0];
230
231    my @endpoint;  # (IP,port)
232
233    # re-load nodefile if necessary
234    if ($self->{nodefile}) {
235        my $now = time;
236        if ($now > $self->{'nodefile.lastcheck'} + NODEFILE_RELOAD_FREQ) {
237            $self->{'nodefile.lastcheck'} = $now;
238            $self->load_nodefile;
239        }
240    }
241
242    # no nodes?
243    return () unless $self->{node_count};
244
245    # pick one randomly
246    return @{$self->{nodes}[int(rand($self->{node_count}))]};
247}
248
249sub backend_should_live {
250    my Perlbal::Pool $self = $_[0];
251    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    return 1 if $self->{use_count};
257    return 0;
258}
259
260sub node_count {
261    my Perlbal::Pool $self = $_[0];
262    return $self->{node_count};
263}
264
265sub nodes {
266    my Perlbal::Pool $self = $_[0];
267    return $self->{nodes};
268}
269
270sub node_used {
271    my Perlbal::Pool $self = $_[0];
272    return $self->{node_used}->{$_[1]};
273}
274
275sub mark_node_used {
276    my Perlbal::Pool $self = $_[0];
277    $self->{node_used}->{$_[1]}++;
278}
279
280sub increment_use_count {
281    my Perlbal::Pool $self = $_[0];
282    $self->{use_count}++;
283}
284
285sub decrement_use_count {
286    my Perlbal::Pool $self = $_[0];
287    $self->{use_count}--;
288}
289
290sub name {
291    my Perlbal::Pool $self = $_[0];
292    return $self->{name};
293}
294
2951;
296
297# Local Variables:
298# mode: perl
299# c-basic-indent: 4
300# indent-tabs-mode: nil
301# End:
Note: See TracBrowser for help on using the browser.