File Coverage

blib/lib/Sphinx/Manager.pm
Criterion Covered Total %
statement 102 188 54.2
branch 28 106 26.4
condition 6 24 25.0
subroutine 19 25 76.0
pod 7 7 100.0
total 162 350 46.2


line stmt bran cond sub pod time code
1             package Sphinx::Manager;
2              
3 4     4   391268 use warnings;
  4         8  
  4         120  
4 4     4   21 use strict;
  4         10  
  4         147  
5 4     4   21 use base qw/Class::Accessor::Fast/;
  4         12  
  4         4115  
6              
7 4     4   20463 use Carp qw/croak/;
  4         11  
  4         282  
8 4     4   3729 use Proc::ProcessTable;
  4         50114  
  4         288  
9 4     4   880 use Path::Class;
  4         84858  
  4         246  
10 4     4   29 use File::Spec;
  4         10  
  4         85  
11 4     4   972 use Sphinx::Config;
  4         13335  
  4         151  
12 4     4   33 use Errno qw/ECHILD/;
  4         5  
  4         9763  
13              
14             our $VERSION = '0.06';
15              
16             __PACKAGE__->mk_accessors(qw/config_file
17             pid_file
18             bindir
19             searchd_args
20             searchd_sudo
21             indexer_args
22             indexer_sudo
23             process_timeout
24             debug/);
25              
26             my $default_config_file = 'sphinx.conf';
27             my $default_process_timeout = 10;
28              
29             sub new {
30 3     3 1 4569 my $class = shift;
31 3         42 my $self = $class->SUPER::new(@_);
32              
33 3 50       54 $self->debug(0) unless $self->debug;
34 3 50       81 $self->config_file($default_config_file) unless $self->config_file;
35 3 50       48 $self->process_timeout($default_process_timeout) unless $self->process_timeout;
36            
37 3         48 return $self;
38             }
39              
40             # Determines pid_file from explicitly given file or by reading the config
41             sub _find_pidfile {
42 6     6   15 my $self = shift;
43              
44 6   33     30 my $config_file = $self->config_file || $default_config_file;
45              
46 6 50       90 if (my $file = $self->pid_file) {
47 0         0 return $self->{_pid_file} = Path::Class::file($file);
48             }
49 6 100 66     57 if ($self->{_config_file} && $config_file eq $self->{_config_file}) {
50             # Config file unchanged
51 3 50       276 return $self->{_pid_file} if $self->{_pid_file};
52             }
53 3         15 $self->_load_config_file;
54 3         375 return $self->{_pid_file};
55             }
56              
57             # Loads given config file and extracts the pid_file
58             sub _load_config_file {
59 3     3   6 my $self = shift;
60              
61 3   33     12 my $config_file = $self->config_file || $default_config_file;
62              
63 3         45 my $config = Sphinx::Config->new;
64 3         33 $config->parse($config_file);
65 3 50       984 if (my $pid_file = $config->get('searchd', undef, 'pid_file')) {
66 3         60 $self->{_pid_file} = Path::Class::file($pid_file);
67             }
68 3         291 $self->{_config_file} = Path::Class::file($config_file); # records which file we have loaded
69             }
70              
71             # Find executable file
72             sub _find_exe {
73 3     3   6 my $self = shift;
74 3         6 my $name = shift;
75              
76 3 50       15 return Path::Class::file($self->bindir, $name) if $self->bindir;
77              
78 0         0 my @candidates = map { Path::Class::file($_, $name) } File::Spec->path();
  0         0  
79 0         0 for my $bin (@candidates) {
80 0 0       0 return $bin if -x "$bin";
81             }
82 0         0 die "Failed to find $name binary in bindir or system path; please specify bindir correctly";
83             }
84              
85             # Find a process for given pid; return the PID if the process matches the given pattern
86             # If pid is not given, returns all process IDs matching the pattern
87             sub _findproc {
88 13     13   244 my ($self, $pid, $pat) = @_;
89              
90 13         793 my $t = Proc::ProcessTable->new;
91              
92 13 50       29733 if ($pid) {
93 0         0 my $process;
94 0         0 for (@{$t->table}) {
  0         0  
95 0 0       0 $process = $_, last if $_->pid == $pid;
96             }
97 0 0       0 return [ $pid ] if $process;
98             }
99             else {
100 13         124 my @procs;
101 13         26 for (@{$t->table}) {
  13         53251  
102 132   33     430 my $cmndline = $_->{cmndline} || $_->{fname};
103 132 50       530 warn "Checking $cmndline against $pat" if $self->debug > 2;
104 132 50       1876 push(@procs, $_->pid) if $cmndline =~ /$pat/;
105             }
106 13         560 return \@procs;
107             }
108              
109 0         0 return [];
110             }
111              
112             # Waits for a PID to disappear from the process table; returns 1 if found, 0 if timeout.
113             sub _wait_for_death {
114 0     0   0 my $self = shift;
115 0         0 my $pid = shift;
116 0   0     0 my $timeout = $self->process_timeout || $default_process_timeout;
117              
118 0         0 my $ret = 0;
119 0         0 my $t = time() + $timeout;
120 0         0 while (time() < $t) {
121 0 0       0 $ret++, last unless @{$self->_findproc($pid)};
  0         0  
122 0         0 sleep(1);
123             }
124 0         0 return $ret;
125             }
126              
127             # Waits for a process matching a given pattern to appear in the process table; returns 1 if found, 0 if timeout.
128             sub _wait_for_proc {
129 1     1   10 my $self = shift;
130 1         13 my $pat = shift;
131 1   33     27 my $timeout = $self->process_timeout || $default_process_timeout;
132              
133 1         8 my $ret = 0;
134 1         13 my $t = time() + $timeout;
135 1         17 while (time() < $t) {
136 10 50       99 $ret++, last if @{$self->_findproc(undef, $pat)};
  10         128  
137 10         10006222 sleep(1);
138             }
139 1         596 return $ret;
140             }
141              
142             sub _system_with_status
143             {
144 0     0   0 my (@args) = @_;
145              
146 0         0 local $SIG{CHLD} = 'IGNORE';
147 0         0 my $status = system(@args);
148 0 0       0 unless ($status == 0) {
149 0 0       0 if ($? == -1) {
150 0 0       0 return '' if $! == ECHILD;
151 0         0 return "@args failed to execute: $!";
152             }
153 0 0       0 if ($? & 127) {
154 0 0       0 return sprintf("@args died with signal %d, %s coredump\n",
155             ($? & 127), ($? & 128) ? 'with' : 'without');
156             }
157 0         0 return sprintf("@args exited with value %d\n", $? >> 8);
158             }
159 0         0 return '';
160             }
161              
162             # Get regexp for matching command line
163             sub _get_searchd_matchre {
164 4     4   19 my $self = shift;
165 4         56 my $c = $self->{_config_file}->stringify;
166 4         456 return qr/searchd.*(?:\s|=)$c(?:$|\s)/;
167             }
168              
169             sub get_searchd_pid {
170 3     3 1 6 my $self = shift;
171              
172 3         6 my $pids = [];
173 3         12 my $pidfile = $self->_find_pidfile;
174 3 50       12 if ( -f "$pidfile" ) {
175 0 0       0 if (my $pid = $pidfile->slurp(chomp => 1)) {
176 0 0       0 push(@$pids, $pid) if @{$self->_findproc($pid, 'searchd')};
  0         0  
177             }
178             }
179 3 50       165 if (! @$pids) {
180             # backup plan if PID file is empty or invalid
181 3         66 $pids = $self->_findproc(undef, $self->_get_searchd_matchre);
182             }
183 3 50       21 warn("Found searchd pid " . join(", ", @$pids)) if $self->debug;
184 3         21 return $pids;
185             }
186              
187             sub start_searchd {
188 3     3 1 18 my $self = shift;
189 3         6 my $ok_if_running = shift;
190              
191 3         15 my $pidfile = $self->_find_pidfile;
192 3 50       39 warn "start_searchd: Checking pidfile $pidfile" if $self->debug;
193              
194 3 50       24 if ( -f "$pidfile" ) {
195 0         0 my $pid = Path::Class::file($pidfile)->slurp(chomp => 1);
196 0 0       0 warn "start_searchd: Found PID $pid" if $self->debug;
197 0 0 0     0 if ($pid && @{$self->_findproc($pid, qr/searchd/)}) {
  0         0  
198 0 0       0 return if $ok_if_running;
199 0         0 die "searchd is already running, PID $pid";
200             }
201             }
202              
203 3         156 my @args;
204 3 50       15 if (my $sudo = $self->searchd_sudo) {
205 0 0       0 if (ref($sudo) ne 'ARRAY') {
206 0         0 $sudo = [ split(/\s+/, $sudo) ];
207             }
208 0         0 push(@args, @$sudo);
209             }
210 3         27 push(@args, $self->_find_exe('searchd'));
211 3         360 push(@args, "--config", $self->{_config_file}->stringify);
212 3 50       99 push(@args, @{$self->searchd_args}) if $self->searchd_args;
  0         0  
213 3 50       24 warn("Executing " . join(" ", @args)) if $self->debug;
214              
215 3         117 local $SIG{CHLD} = 'IGNORE';
216 3         3774 my $pid = fork();
217 3 50       199 die "Fork failed: $!" unless defined $pid;
218 3 100       59 if ($pid == 0) {
219 2 100       6540 fork() and exit; # double fork to ensure detach
220 1 0       0 exec(@args)
221             or die("Failed to exec @args}: $!");
222             }
223              
224 1 50       91 die "Searchd not running after timeout"
225             unless $self->_wait_for_proc($self->_get_searchd_matchre);
226              
227             }
228              
229             sub _kill {
230 0     0   0 my ($self, $sig, @pids) = @_;
231              
232 0 0       0 if (my $sudo = $self->searchd_sudo) {
233 0         0 my @args;
234 0 0       0 if (ref($sudo) ne 'ARRAY') {
235 0         0 $sudo = [ split(/\s+/, $sudo) ];
236             }
237 0         0 push(@args, @$sudo);
238 0         0 push(@args, 'kill');
239 0         0 push(@args, "-$sig", @pids);
240 0         0 _system_with_status(@args);
241             }
242             else {
243 0         0 kill $sig, @pids;
244             }
245             }
246              
247             sub stop_searchd {
248 3     3 1 1314 my $self = shift;
249              
250 3         18 my $pids = $self->get_searchd_pid;
251 3 50       18 if (@$pids) {
252 0           $self->_kill(15, @$pids);
253 0 0         unless ($self->_wait_for_death(@$pids)) {
254 0           $self->_kill(9, @$pids);
255 0 0         unless ($self->_wait_for_death(@$pids)) {
256 0           die "Failed to stop searchd PID " . join(", ", @$pids) . ", even with sure kill";
257             }
258             }
259             }
260             # nothing found to kill so assume success
261             }
262              
263             sub restart_searchd {
264 0     0 1   my $self = shift;
265 0           $self->stop_searchd;
266 0           $self->start_searchd(1);
267             }
268              
269             sub reload_searchd {
270 0     0 1   my $self = shift;
271            
272 0           my $pids = $self->get_searchd_pid;
273 0 0         if (@$pids) {
274             # send HUP
275 0           $self->_kill(1, @$pids);
276             }
277             else {
278 0           $self->start_searchd(1);
279             }
280             }
281              
282             sub run_indexer {
283 0     0 1   my $self = shift;
284 0           my @extra_args = @_;
285              
286 0           my @args;
287 0 0         if (my $sudo = $self->indexer_sudo) {
288 0 0         if (ref($sudo) ne 'ARRAY') {
289 0           $sudo = [ split(/\s+/, $sudo) ];
290             }
291 0           push(@args, @$sudo);
292             }
293 0           push(@args, my $indexer = $self->_find_exe('indexer'));
294              
295 0 0         warn("Using indexer @args") if $self->debug;
296 0 0         die "Cannot execute Sphinx indexer binary $indexer" unless -x "$indexer";
297              
298 0   0       my $config = $self->config_file || $default_config_file;
299 0           push(@args, "--config", $config);
300 0 0         push(@args, @{$self->indexer_args}) if $self->indexer_args;
  0            
301 0 0         push(@args, @extra_args) if @extra_args;
302              
303 0 0         if (my $status = _system_with_status(@args)) {
304 0           die $status;
305             }
306             }
307              
308             1;
309              
310             __END__