File Coverage

lib/Geo/Coder/Many/Scheduler/Selective.pm
Criterion Covered Total %
statement 69 70 98.5
branch 9 10 90.0
condition 2 3 66.6
subroutine 13 13 100.0
pod 5 5 100.0
total 98 101 97.0


line stmt bran cond sub pod time code
1             package Geo::Coder::Many::Scheduler::Selective;
2              
3 2     2   6 use strict;
  2         2  
  2         41  
4 2     2   5 use warnings;
  2         2  
  2         38  
5 2     2   6 use Time::HiRes qw( gettimeofday );
  2         2  
  2         11  
6 2     2   256 use List::Util qw( min max );
  2         2  
  2         91  
7 2     2   6 use Carp;
  2         2  
  2         103  
8              
9 2     2   6 use base 'Geo::Coder::Many::Scheduler';
  2         2  
  2         641  
10              
11             our $VERSION = '0.01';
12              
13             =head1 NAME
14            
15             Geo::Coder::Many::Scheduler::Selective - Scheduler that times out bad geocoders
16              
17             =head1 DESCRIPTION
18              
19             This scheduler wraps another scheduler, and provides facilities for disabling
20             various geocoders based on external feedback (e.g. limit exceeded messages)
21              
22             In particular, if a geocoder returns an error, it is disabled for a timeout
23             period. This period increases exponentially upon each successive consecutive
24             failure.
25              
26             =head1 METHODS
27              
28             =head2 new
29              
30             Constructs and returns an instance of the class.
31             Takes a reference to an array of {name, weight} hashrefs, and the name of a
32             scheduler class to wrap (e.g. Geo::Coder::Many::Scheduler::OrderedList)
33              
34             =cut
35              
36             sub new {
37 210     210 1 215 my $class = shift;
38 210         146 my $ra_geocoders = shift;
39 210         144 my $scheduler = shift;
40              
41 210 50       303 unless (defined $scheduler) {
42 0         0 croak "Selective scheduler needs to wrap an ordinary scheduler.\n";
43             }
44            
45 210         175 my $self = { };
46 210         468 $self->{ scheduler } = $scheduler->new( $ra_geocoders );
47 210         249 $self->{ geocoder_meta } = { };
48              
49             # Length of first timeout in seconds
50 210         192 $self->{ base_timeout } = 1;
51              
52             # Timeout multiplies by this upon each successive failure
53 210         197 $self->{ timeout_multiplier } = 1.5;
54              
55 210         194 bless $self, $class;
56              
57 210         222 for my $rh_geocoder (@$ra_geocoders) {
58 315         380 $self->_clear_timeout($rh_geocoder->{name});
59             }
60              
61              
62 210         426 return $self;
63             }
64              
65             =head2 reset_available
66              
67             Wrapper method - passes the reset on to the wrapped scheduler.
68              
69             =cut
70              
71             sub reset_available {
72 1050     1050 1 748 my $self = shift;
73 1050         1749 $self->{scheduler}->reset_available();
74 1050         1118 return;
75             }
76              
77             =head2 get_next_unique
78              
79             Retrieves the next geocoder from the internal scheduler, but skipping over
80             it if it isn't acceptable (e.g. being timed out)
81              
82             =cut
83              
84             sub get_next_unique {
85 1299     1299 1 809 my $self = shift;
86              
87 1299         793 my $acceptable = 0;
88 1299         1467 my $t = gettimeofday();
89 1299         850 my $geocoder;
90 1299         1570 while (!$acceptable) {
91 2986         4257 $geocoder = $self->{scheduler}->get_next_unique();
92 2986 100 66     7228 if (!defined $geocoder || $geocoder eq '') {return;}
  902         1100  
93 2084         1652 my $rh_meta = $self->{geocoder_meta}->{$geocoder};
94 2084 100       3822 if ($t >= $rh_meta->{timeout_end}) {
95 397         680 $acceptable = 1;
96             }
97             }
98 397         600 return $geocoder;
99             }
100              
101             =head2 next_available
102              
103             Returns undef if there are no more geocoders that will become available.
104             Otherwise it returns the time remaining until the earliest timeout-end arrives.
105              
106             =cut
107              
108             sub next_available {
109 1519     1519 1 1101 my $self = shift;
110 1519 100       2369 return if (!defined $self->{scheduler}->next_available());
111             my $first_time =
112             min(
113             map {
114 2768         4785 $_->{timeout_end};
115 1384         1022 } values %{$self->{geocoder_meta}}
  1384         1899  
116             ) - gettimeofday();
117 1384         2484 return max 0, $first_time;
118             }
119              
120             =head2 process_feedback
121              
122             Recieves feedback about geocoders, and sets/clears timeouts appropriately.
123              
124             =cut
125              
126             sub process_feedback {
127 397     397 1 412 my ($self, $geocoder, $rh_feedback) = @_;
128              
129 397 100       533 if ( $rh_feedback->{response_code} != 200 ) {
130 209         289 $self->_increase_timeout($geocoder);
131             }
132             else {
133 188         239 $self->_clear_timeout($geocoder);
134             }
135 397         615 return;
136             }
137              
138             =head1 INTERNAL METHODS
139              
140             =head2 _increase_timeout
141              
142             Increases the timeout for the specified geocoder, according to the base_timeout
143             and timeout_multiplier instance variables.
144              
145             =cut
146              
147             sub _increase_timeout {
148 209     209   172 my ($self, $geocoder) = @_;
149 209         200 my $rh_meta = $self->{geocoder_meta}->{$geocoder};
150 209         174 $rh_meta->{timeout_count} += 1;
151 209         163 my $timeout_count = $rh_meta->{timeout_count};
152              
153 209         164 my $base_timeout = $self->{base_timeout};
154 209         166 my $timeout_multiplier = $self->{timeout_multiplier};
155              
156 209         383 my $timeout_length =
157             $base_timeout * ($timeout_multiplier ** $timeout_count);
158              
159 209         346 $rh_meta->{timeout_end} = gettimeofday() + $timeout_length;
160 209         236 return $timeout_length;
161             };
162              
163             =head2 _clear_timeout
164              
165             Clears the timeout for the specified geocoder.
166              
167             =cut
168              
169             sub _clear_timeout {
170 503     503   453 my ($self, $geocoder) = @_;
171 503         885 $self->{geocoder_meta}->{$geocoder} = {
172             timeout_count => 0,
173             timeout_end => 0
174             };
175 503         623 return;
176             };
177              
178             1;
179              
180             __END__