File Coverage

blib/lib/Sub/Daemon.pm
Criterion Covered Total %
statement 98 150 65.3
branch 14 46 30.4
condition 6 22 27.2
subroutine 18 23 78.2
pod 6 7 85.7
total 142 248 57.2


line stmt bran cond sub pod time code
1             package Sub::Daemon;
2              
3             =head1 NAME
4              
5             Sub::Daemon - base class for a daemons
6              
7             =cut
8              
9 3     3   140546 use 5.014;
  3         31  
10              
11             our $VERSION = '0.04';
12              
13 3     3   18 use base 'Class::Accessor';
  3         5  
  3         1785  
14             __PACKAGE__->mk_ro_accessors( qw( pid log logdir piddir logfile pidfile debug loglevel) );
15              
16 3     3   8975 use AnyEvent;
  3         17098  
  3         126  
17 3     3   1877 use POSIX ":sys_wait_h";
  3         20207  
  3         19  
18 3     3   6115 use File::Pid;
  3         10612  
  3         25  
19 3     3   1578 use Sub::Daemon::Log;
  3         9  
  3         85  
20 3     3   1567 use FindBin;
  3         3420  
  3         129  
21 3     3   22 use Carp;
  3         8  
  3         4865  
22              
23              
24             sub new {
25 2     2 1 216 my $class = shift;
26 2         16 my %opts = (
27             'logdir' => './',
28             'piddir' => './',
29             'logfile' => undef,
30             'pidfile' => undef,
31             'debug' => 0,
32             'loglevel' => 'info',
33             @_,
34             );
35              
36 2         6 my $logfile = $opts{ 'logfile' };
37 2         4 my $pidfile = $opts{ 'pidfile' };
38              
39 2 50 33     10 unless ($logfile && $pidfile) {
40 2         4 my $filename = $0;
41 2         12 $filename =~ s/^.*\///;
42 2   33     16 $logfile ||= $filename . '.log';
43 2   33     12 $pidfile ||= $filename . '.pid';
44             }
45              
46 2         12 my $self = bless {
47             %opts,
48             'logfile' => $logfile,
49             'pidfile' => $pidfile,
50             }, $class;
51              
52 2         12 $self->pid_check();
53 2         10 $self->init_log();
54              
55 2         8 return $self;
56             }
57              
58             =head2 _fork
59              
60             Fork with dances
61              
62             Return:
63             pid of child process - for parent
64             0 - for a child process
65              
66             =cut
67              
68             sub _fork {
69 2     2   4 my $self = shift;
70              
71 2         4 my $pid;
72 2         4 my $loop = 0;
73              
74             FORK: {
75 2 50       4 if( defined( $pid = fork ) ) {
  2         2181  
76 2         201 return $pid;
77             }
78              
79             # EAGAIN - fork cannot allocate sufficient memory to copy the parent's
80             # page tables and allocate a task structure for the child.
81             # ENOMEM - fork failed to allocate the necessary kernel structures
82             # because memory is tight.
83             # Last the loop after 30 seconds
84 0 0 0     0 if ( $loop < 6 && ( $! == POSIX::EAGAIN() || $! == POSIX::ENOMEM() ) ) {
      0        
85 0         0 $loop++; sleep 5; redo FORK;
  0         0  
  0         0  
86             }
87             }
88              
89 0         0 confess "Can't fork: $!";
90             }
91              
92             =head2 _daemonize
93              
94             Daemonize of process.
95             Forks, closes STDIN/STDOUT/STDERR....
96              
97             =cut
98              
99             sub _daemonize {
100 2     2   1024 my $self = shift;
101 2         12 my %opts = (
102             'pid' => undef,
103             'debug' => undef,
104             @_,
105             );
106            
107 2   33     10 my $debug = $opts{'debug'} // $self->debug;
108              
109 2 50       14 return $self->pid_write() if $debug;
110 0 0       0 if (my $pid = $self->pid_check()) {
111 0         0 die "Already running: $pid\n" ;
112             }
113              
114             # Демонизируемся
115             # Первый fork
116 0         0 my $pid = $self->_fork();
117 0 0       0 if( $pid ) {
118 0         0 waitpid $pid, 0;
119 0         0 exit;
120             }
121              
122 0 0       0 die "Can't to $FindBin::Bin/script: $!" unless chdir $FindBin::Bin;
123 0         0 umask 0;
124 0 0       0 die "Cannot detach from controlling terminal" if POSIX::setsid() < 0;
125             # Второй fork
126 0         0 $pid = $self->_fork;
127 0 0       0 exit if $pid;
128              
129 0         0 say "Daemon started. PID=$$";
130              
131             # Закрываем все открытые файлы
132 0   0     0 my $openmax = POSIX::sysconf( &POSIX::_SC_OPEN_MAX ) // 64;
133 0 0       0 $openmax = 64 if $openmax < 0;
134 0         0 POSIX::close $_ for( 0 .. $openmax );
135             # Переоткрываем стандартные потоки
136 0         0 open \*STDIN, "
137 0         0 open \*STDOUT, ">/dev/null";
138 0         0 open \*STDERR, ">/dev/null";
139 0         0 $self->init_log(); # открываем лог еще раз, т.к. он был закрыт в "POSIX::close ... "
140 0         0 $self->pid_write();
141 0         0 return;
142             }
143              
144             =head2 pid_write
145              
146             Write pid-file
147              
148             =cut
149              
150             sub pid_write {
151 2     2 1 4 my $self = shift;
152              
153 2         8 my $pidfile = $self->piddir . $self->pidfile;
154 2         48 my $pid = File::Pid->new({
155             'file' => $pidfile,
156             'pid' => $$,
157             });
158 2 50       226 if( -f -s $pidfile ) {
159 0 0       0 if ( my $num = $pid->running ) {
160 0         0 die "Already running: $num\n";
161             }
162             }
163              
164 2         10 $self->{'pid'} = $pid;
165              
166 2 50       10 $pid->write or die "Couldnt write pid $pidfile";
167              
168 2         564 return;
169             }
170              
171             =head2 pid_check
172              
173             Checking, may by this daemon runned early
174              
175             =cut
176              
177             sub pid_check {
178 2     2 1 4 my $self = shift;
179              
180 2         14 my $pidfile = $self->piddir . $self->pidfile;
181 2         96 my $pid = File::Pid->new({
182             'file' => $pidfile,
183             'pid' => $$,
184             });
185              
186 2 50       330 return $pid->running if -f -s $pidfile;
187 2         24 return;
188             }
189              
190             =head2 pid_remove
191              
192             Remove pid-file
193              
194             =cut
195              
196             sub pid_remove {
197 0     0 1 0 my $self = shift;
198 0         0 return $self->pid->remove;
199             }
200              
201             =head2 init_log
202              
203             Init log.
204              
205             =cut
206              
207             sub init_log {
208 2     2 1 4 my $self = shift;
209             #my $filename = $ENV{LOG_DIR} . '/' . $self->logfile unless $self->debug;
210             #$self->{ 'log' } = Rept::Log->new( 'path' => $filename );
211 2 50       10 if ($self->debug) {
212 0         0 $self->{log} = Sub::Daemon::Log->new(level => $self->loglevel());
213             } else {
214 2         30 $self->{log} = Sub::Daemon::Log->new(level => $self->loglevel(),path => $self->logdir() . $self->logfile);
215             }
216              
217             $SIG{ '__WARN__' } = sub {
218 3     3   3000465 my $msg = $_[0];
219 3         23 chomp $msg;
220 3         40 $self->log->warn($msg);
221 2         16 };
222              
223 2         6 return;
224             }
225              
226             =head2 spawn(
227              
228             Start child worker proccess. Control of work
229              
230             Params:
231              
232             nproc - Number of childs process. Be default: 1
233             code - CODE REF of child process
234              
235             =cut
236              
237             sub spawn {
238 2     2 1 9588 my $self = shift;
239 2         12 my %opts = (
240             'nproc' => 1,
241             'code' => undef,
242             @_,
243             );
244            
245 2         10 $self->log->info("Daemon spawning");
246              
247 2 50       14 my $nproc = $opts{ 'nproc' } or confess 'number of child process is not specified';
248 2 50       8 my $code = $opts{ 'code' } or confess 'child code is not specified';
249 2 50       12 confess 'code must be CODE reference' unless ref $code eq 'CODE';
250              
251             # Хэш пидов рабочих процессов
252 2         4 my %childs = ();
253              
254 2         62 my $cv = AE::cv;
255              
256             # Перехват сигналов INT и TERM
257             # При их получении будет остановлен цикл выполнения,
258             # а всем рабочим процессам будет отправлен сигнал TERM
259 2     1   408 $SIG{$_} = sub { $cv->send(); kill 'TERM' => keys %childs } for qw( TERM INT );
  1         3002337  
  1         86  
260              
261             # флаг, указывающий, что это дочерний процесс. проставляется в дочернем процессе)
262             # из-за ограничений AnyEvent, невозможно делать вызов $self->run_child() сразу из $start_child,
263             # и приходится выносить его за $cv->recv.
264 2         8 my $is_child = 0;
265              
266             # Подпрограмма запуска рабочего-дочернего процесса
267             state $start_child = sub {
268 2 100   2   8 if (my $pid = $self->_fork()) {
269             # В родительском процессе:
270             # просто сохраняем pid рабочего процесса,
271             # и завершаем выполнение подпрограммы
272 1         51 $childs{ $pid } = 1;
273 1         47 return;
274             }
275             # В дочернем процессе:
276 1         128 $cv->send; # Прерываем выполнение ожидания AE
277 1         123 $is_child = 1; # Ставим флаг, что данный процесс - дочерний
278 1         47 $self->log->info("Child process $$ started");
279 1         18 return 1;
280 2         10 };
281              
282             # callback, срабатывающий в основном процессе,
283             # когда завершаются дочерние рабочие процессы
284             my $c = AE::child 0 => sub {
285 0 0   0   0 if (delete $childs{ $_[0] }) {
286 0         0 $self->log->info("Child process $_[0] stopped");
287             # удаляем pid,
288             # если это не полное завершение работы системы (цикл обработки AE еще не завершился),
289             # значит рабочий процесса упал сам, и нужно его перезапустить
290 0 0       0 $start_child->() unless $cv->ready;
291             }
292 2         50 };
293              
294             # Обработчик сигнала HUP.
295             my $hup = AE::signal HUP => sub {
296             # Посылаем HUP всем дочерним процессам
297 0     0   0 $self->log->info( 'Parent process fetch SIG HUP' );
298 0         0 $self->log->info( 'Send HUP to child processes' );
299 0         0 kill HUP => keys %childs;
300             # Переоткрываем лог??
301 0         0 $self->log->info( 'Log reopening..' );
302 0         0 $self->log->reopen();
303 2         66 };
304              
305             # Запускаем дочерние процессы
306             # "&& last" нужен того, чтобы дочерний процесс мог выйти из цикла.
307 2   100     14 $start_child->() && last for 1..$nproc;
308              
309             # запуск ожидания события AE
310 2         147 $cv->recv;
311              
312             # если этот процесс - дочерний, начинаем обработку
313 2 100       363 if ($is_child) {
314             local $SIG{ 'HUP' } = sub {
315 0     0   0 $self->log->info( 'Worker process fetch SIG HUP' );
316 0         0 $self->log->info( 'Log reopening..' );
317 0         0 $self->log->reopen();
318 1         111 };
319 1         21 $code->();
320 1         521 exit;
321             }
322              
323             # Дожидаемся завершения оставшихся рабочих процессов
324 1         26 $self->log->info("Waiting childs");
325 1         261200 waitpid($_, 0) for keys %childs;
326 1         39 $self->log->info("Daemon finished");
327              
328 1         92 return;
329             }
330              
331             sub stop {
332 0     0 0   my $self = shift;
333            
334 0           $self->log->info("Stopping daemon");
335            
336 0           my $pidfile = $self->pidfile();
337 0           open my $fi, $pidfile;
338 0           my $pid = <$fi>;
339 0           chomp $pid;
340 0           close $fi;
341            
342 0 0         if (kill 0, $pid) {
343 0           kill 'TERM', $pid;
344             } else {
345 0           die "Couldn't send kill 0 to master process";
346             }
347             }
348              
349             1;