root/trunk/lib/Perlbal/ReproxyManager.pm

Revision 687, 8.7 kB (checked in by jacques, 2 years ago)

Bump copyright and fixed typo in Interactive

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1# HTTP connection to non-pool backend nodes (probably fast event-based webservers)
2#
3# Copyright 2004, Danga Interactive, Inc.
4# Copyright 2005-2007, Six Apart, Ltd.
5#
6
7package Perlbal::ReproxyManager;
8use strict;
9use warnings;
10no  warnings qw(deprecated);
11
12# class storage to store 'host:ip' => $service objects, for making
13# reproxies use a service that you can then track
14our $ReproxySelf;
15our %ReproxyConnecting; # ( host:ip => $backend ); keeps track of outstanding connections to backend that
16                        # are in the connecting state
17our %ReproxyBored;      # ( host:ip => [ $backend, ... ] ); list of our bored backends
18our %ReproxyQueues;     # ( host:ip => [ $clientproxy, ... ] ); queued up requests for this backend
19our %ReproxyBackends;   # ( host:ip => [ $backend, ... ] ); array of backends we have connected
20our %ReproxyMax;        # ( host:ip => int ); maximum number of connections to have open at any one time
21our $ReproxyGlobalMax;  # int; the global cap used if no per-host cap is specified
22our $NoSpawn = 0;       # bool; when set, spawn_backend immediately returns without running
23our $LastCleanup = 0;   # int; time we last ran our cleanup logic (FIXME: temp hack)
24
25Perlbal::track_var("rep_connecting", \%ReproxyConnecting);
26Perlbal::track_var("rep_bored",      \%ReproxyBored);
27Perlbal::track_var("rep_queues",     \%ReproxyQueues);
28Perlbal::track_var("rep_backends",   \%ReproxyBackends);
29
30# singleton new function; returns us if we exist, else creates us
31sub get {
32    return $ReproxySelf if $ReproxySelf;
33
34    # doesn't exist, so create it and return it
35    my $class = shift;
36    my $self = {};
37    bless $self, $class;
38    return $ReproxySelf = $self;
39}
40
41# given (clientproxy, primary_res_hdrs), initiate proceedings to process a
42# request for a reproxy resource
43sub do_reproxy {
44    my Perlbal::ReproxyManager $self = Perlbal::ReproxyManager->get; # singleton
45    my Perlbal::ClientProxy $cp = $_[0];
46    return undef unless $self && $cp;
47
48    # get data we use
49    my $datref = $cp->{reproxy_uris}->[0];
50    my $ipport = "$datref->[0]:$datref->[1]";
51    push @{$ReproxyQueues{$ipport} ||= []}, $cp;
52
53    # see if we should do cleanup (FIXME: temp hack)
54    my $now = time();
55    if ($LastCleanup < $now - 5) {
56        # remove closed backends from our array. this is O(n) but n is small
57        # and we're paranoid that just keeping a count would get corrupt over
58        # time.  also removes the backends that have clients that are closed.
59        @{$ReproxyBackends{$ipport}} = grep {
60            ! $_->{closed} && (! $_->{client} || ! $_->{client}->{closed})
61        } @{$ReproxyBackends{$ipport}};
62
63        $LastCleanup = $now;
64    }
65
66    # now start a new backend
67    $self->spawn_backend($ipport);
68    return 1;
69}
70
71# part of the reportto interface; this is called when a backend is unable to establish
72# a connection with a backend.  we simply try the next uri.
73sub note_bad_backend_connect {
74    my Perlbal::ReproxyManager $self = $_[0];
75    my Perlbal::BackendHTTP $be = $_[1];
76
77    # decrement counts and undef connecting backend
78    $ReproxyConnecting{$be->{ipport}} = undef;
79
80    # if nobody waiting, doesn't matter if we couldn't get to this backend
81    return unless @{$ReproxyQueues{$be->{ipport}} || []};
82
83    # if we still have some connected backends then ignore this bad connection attempt
84    return if scalar @{$ReproxyBackends{$be->{ipport}} || []};
85
86    # at this point, we have no connected backends, and our connecting one failed
87    # so we want to tell all of the waiting clients to try their next uri, because
88    # this host is down.
89    while (my Perlbal::ClientProxy $cp = shift @{$ReproxyQueues{$be->{ipport}}}) {
90        $cp->try_next_uri;
91    }
92    return 1;
93}
94
95# called by a backend when it's ready for a request
96sub register_boredom {
97    my Perlbal::ReproxyManager $self = $_[0];
98    my Perlbal::BackendHTTP $be = $_[1];
99
100    # if this backend was connecting
101    my $ipport = $be->{ipport};
102    if ($ReproxyConnecting{$ipport} && $ReproxyConnecting{$ipport} == $be) {
103        $ReproxyConnecting{$ipport} = undef;
104        $ReproxyBackends{$ipport} ||= [];
105        push @{$ReproxyBackends{$ipport}}, $be;
106    }
107
108    # sometimes a backend is closed but it tries to register with us anyway... ignore it
109    # but since this might have been our only one, spawn another
110    if ($be->{closed}) {
111        $self->spawn_backend($ipport);
112        return;
113    }
114
115    # find some clients to use
116    while (my Perlbal::ClientProxy $cp = shift @{$ReproxyQueues{$ipport} || []}) {
117        # safety checks
118        next if $cp->{closed};
119
120        # give backend to client
121        $cp->use_reproxy_backend($be);
122        return;
123    }
124
125    # no clients if we get here, so push onto bored backend list
126    push @{$ReproxyBored{$ipport} ||= []}, $be;
127
128    # clean up the front of our list if we can (see docs above)
129    if (my Perlbal::BackendHTTP $bbe = $ReproxyBored{$ipport}->[0]) {
130        if ($bbe->{alive_time} < time() - 5) {
131            $NoSpawn = 1;
132            $bbe->close('have_newer_bored');
133            shift @{$ReproxyBored{$ipport}};
134            $NoSpawn = 0;
135        }
136    }
137    return 0;
138}
139
140# backend closed, decrease counts, etc
141sub note_backend_close {
142    my Perlbal::ReproxyManager $self = $_[0];
143    my Perlbal::BackendHTTP $be = $_[1];
144
145    # remove closed backends from our array. this is O(n) but n is small
146    # and we're paranoid that just keeping a count would get corrupt over
147    # time.
148    @{$ReproxyBackends{$be->{ipport}}} = grep {
149        ! $_->{closed}
150    } @{$ReproxyBackends{$be->{ipport}}};
151
152    # spawn more if needed
153    $self->spawn_backend($be->{ipport});
154}
155
156sub spawn_backend {
157    return if $NoSpawn;
158
159    my Perlbal::ReproxyManager $self = $_[0];
160    my $ipport = $_[1];
161
162    # if we're already connecting, we don't want to spawn another one
163    if (my Perlbal::BackendHTTP $be = $ReproxyConnecting{$ipport}) {
164        # see if this one is too old?
165        if ($be->{create_time} < (time() - 5)) { # older than 5 seconds?
166            $self->note_bad_backend_connect($be);
167            $be->close("connection_timeout");
168
169            # we return here instead of spawning because closing the backend calls
170            # note_backend_close which will call spawn_backend again, and at that
171            # point we won't have a pending connection and can spawn
172            return;
173        } else {
174            # don't spawn more if we're already connecting
175            return;
176        }
177    }
178
179    # if nobody waiting, don't spawn extra connections
180    return unless @{$ReproxyQueues{$ipport} || []};
181
182    # don't spawn if we have a bored one already
183    while (my Perlbal::BackendHTTP $bbe = pop @{$ReproxyBored{$ipport} || []}) {
184
185        # don't use keep-alive connections if we know the server's
186        # just about to kill the connection for being idle
187        my $now = time();
188        if ($bbe->{disconnect_at} && $now + 2 > $bbe->{disconnect_at} ||
189            $bbe->{alive_time} < $now - 5)
190        {
191            $NoSpawn = 1;
192            $bbe->close("too_close_disconnect");
193            $NoSpawn = 0;
194            next;
195        }
196
197        # it's good, give it to someone
198        $self->register_boredom($bbe);
199        return;
200    }
201
202    # see if we have too many already?
203    my $max = $ReproxyMax{$ipport} || $ReproxyGlobalMax || 0;
204    my $count = scalar @{$ReproxyBackends{$ipport} || []};
205    return if $max && ($count >= $max);
206
207    # start one connecting and enqueue
208    my $be = Perlbal::BackendHTTP->new(undef, split(/:/, $ipport), { reportto => $self })
209        or return 0;
210    $ReproxyConnecting{$ipport} = $be;
211}
212
213sub backend_response_received {
214    my Perlbal::ReproxyManager $self = $_[0];
215    my Perlbal::BackendHTTP $be = $_[1];
216    my Perlbal::ClientProxy $cp = $be->{client};
217
218    # if no client, close backend and return 1
219    unless ($cp) {
220        $be->close("lost_client");
221        return 1;
222    }
223
224    # pass on to client
225    return $cp->backend_response_received($be);
226}
227
228sub dump_state {
229    my $out = shift;
230    return unless $out;
231
232    # spits out what we have connecting
233    while (my ($hostip, $dat) = each %ReproxyConnecting) {
234        $out->("connecting $hostip 1") if defined $dat;
235    }
236    while (my ($hostip, $dat) = each %ReproxyBored) {
237        $out->("bored $hostip " . scalar(@$dat));
238    }
239    while (my ($hostip, $dat) = each %ReproxyQueues) {
240        $out->("clients_queued $hostip " . scalar(@$dat));
241    }
242    while (my ($hostip, $dat) = each %ReproxyBackends) {
243        $out->("backends $hostip " . scalar(@$dat));
244        foreach my $be (@$dat) {
245            $out->("... " . $be->as_string);
246        }
247    }
248    while (my ($hostip, $dat) = each %ReproxyMax) {
249        $out->("SERVER max_reproxy_connections($hostip) = $dat");
250    }
251    $out->("SERVER max_reproxy_connections = " . ($ReproxyGlobalMax || 0));
252    $out->('.');
253}
254
2551;
Note: See TracBrowser for help on using the browser.