File Coverage

blib/lib/Proc/Govern.pm
Criterion Covered Total %
statement 114 278 41.0
branch 31 132 23.4
condition 14 65 21.5
subroutine 13 24 54.1
pod 1 2 50.0
total 173 501 34.5


line stmt bran cond sub pod time code
1             package Proc::Govern;
2              
3             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
4             our $DATE = '2021-08-06'; # DATE
5             our $DIST = 'Proc-Govern'; # DIST
6             our $VERSION = '0.212'; # VERSION
7              
8 1     1   70107 use 5.010001;
  1         12  
9 1     1   8 use strict;
  1         1  
  1         33  
10 1     1   6 use warnings;
  1         2  
  1         24  
11 1     1   1899 use Log::ger;
  1         54  
  1         4  
12              
13 1     1   243 use Exporter qw(import);
  1         3  
  1         63  
14             our @EXPORT_OK = qw(govern_process);
15              
16             our %SPEC;
17              
18 1     1   528 use IPC::Run::Patch::Setuid ();
  1         17921  
  1         21  
19 1     1   946 use IPC::Run (); # just so prereq can be detected
  1         42176  
  1         28  
20 1     1   556 use Time::HiRes qw(sleep);
  1         1378  
  1         4  
21              
22             sub new {
23 1     1 0 4 my ($class) = @_;
24 1         3 bless {}, $class;
25             }
26              
27             sub _suspend {
28 0     0   0 my $self = shift;
29 0         0 my $h = $self->{h};
30 0         0 log_debug "[govproc] Suspending child ...";
31 0 0       0 if (@{ $h->{KIDS} }) {
  0         0  
32 0         0 my @args = (STOP => (map { $_->{PID} } @{ $h->{KIDS} }));
  0         0  
  0         0  
33 0 0       0 if ($self->{args}{killfam}) {
34             #say "D:killfam ".join(" ", @args) if $self->{debug};
35 0         0 Proc::Killfam::killfam(@args);
36             } else {
37             #say "D:kill ".join(" ", @args) if $self->{debug};
38 0         0 kill @args;
39             }
40             }
41 0         0 $self->{suspended} = 1;
42             }
43              
44             sub _resume {
45 0     0   0 my $self = shift;
46 0         0 my $h = $self->{h};
47 0         0 log_debug "[govproc] Resuming child ...";
48 0 0       0 if (@{ $h->{KIDS} }) {
  0         0  
49 0         0 my @args = (CONT => (map { $_->{PID} } @{ $h->{KIDS} }));
  0         0  
  0         0  
50 0 0       0 if ($self->{args}{killfam}) {
51             #say "D:killfam ".join(" ", @args) if $self->{debug};
52 0         0 Proc::Killfam::killfam(@args);
53             } else {
54             #say "D:kill ".join(" ", @args) if $self->{debug};
55 0         0 kill @args;
56             }
57             }
58 0         0 $self->{suspended} = 0;
59             }
60              
61             sub _kill {
62 0     0   0 my $self = shift;
63 0         0 my $h = $self->{h};
64 0 0       0 $self->_resume if $self->{suspended};
65 0         0 log_debug "[govproc] Killing child ...";
66              
67             # we turn this off because restarting is done by the child signal handle.
68             # restart_if_failed will be set (again) by do_start().
69 0         0 $self->{restart_if_failed} = 0;
70              
71 0         0 $h->kill_kill;
72             }
73              
74             $SPEC{govern_process} = {
75             v => 1.1,
76             summary => 'Run child process and govern its various aspects',
77             description => <<'_',
78              
79             It basically uses and a loop to check various conditions during
80             the lifetime of the child process.
81              
82             TODO: restart_delay, check_alive.
83              
84             _
85             args => {
86             name => {
87             schema => 'str*',
88             description => <<'_',
89              
90             Should match regex `\A\w+\z`. Used in several places, e.g. passed as `prefix` in
91             's constructor as well as used as name of PID file.
92              
93             If not given, will be taken from command.
94              
95             _
96             },
97             command => {
98             schema => ['array*' => of => 'str*'],
99             req => 1,
100             pos => 0,
101             slurpy => 1,
102             summary => 'Command to run',
103             description => <<'_',
104              
105             Passed to 's `start()`.
106              
107             _
108             },
109             nice => {
110             summary => 'Set nice/priority level',
111             schema => ['int*'],
112             },
113             single_instance => {
114             schema => [bool => default => 0],
115             description => <<'_',
116              
117             If set to true, will prevent running multiple instances simultaneously.
118             Implemented using . You will also normally have to set
119             `pid_dir`, unless your script runs as root, in which case you can use the
120             default `/var/run`.
121              
122             _
123             tags => ['category:instance-control'],
124             },
125             pid_dir => {
126             summary => 'Directory to put PID file in',
127             schema => 'dirname*',
128             },
129             on_multiple_instance => {
130             schema => ['str*' => in => ['exit']],
131             description => <<'_',
132              
133             Can be set to `exit` to silently exit when there is already a running instance.
134             Otherwise, will print an error message `Program already running`.
135              
136             _
137             tags => ['category:instance-control'],
138             },
139             load_watch => {
140             schema => [bool => default => 0],
141             description => <<'_',
142              
143             If set to 1, enable load watching. Program will be suspended when system load is
144             too high and resumed if system load returns to a lower limit.
145              
146             _
147             tags => ['category:load-control'],
148             },
149             load_check_every => {
150             schema => [duration => {default => 10, 'x.perl.coerce_rules'=>['From_str::human']}],
151             summary => 'Frequency of load checking (in seconds)',
152             tags => ['category:load-control'],
153             },
154             load_high_limit => {
155             schema => ['any*' => of => [[int => default => 1.25], 'code*']],
156             description => <<'_',
157              
158             Limit above which program should be suspended, if load watching is enabled. If
159             integer, will be compared against `->load`'s `$load1` value.
160             Alternatively, you can provide a custom routine here, code should return true if
161             load is considered too high.
162              
163             Note: `load_watch` needs to be set to true first for this to be effective.
164              
165             _
166             tags => ['category:load-control'],
167             },
168             load_low_limit => {
169             schema => ['any*' => of => [[int => default => 0.25], 'code*']],
170             description => <<'_',
171              
172             Limit below which program should resume, if load watching is enabled. If
173             integer, will be compared against `->load`'s `$load1` value.
174             Alternatively, you can provide a custom routine here, code should return true if
175             load is considered low.
176              
177             Note: `load_watch` needs to be set to true first for this to be effective.
178              
179             _
180             tags => ['category:load-control'],
181             },
182             killfam => {
183             summary => 'Instead of kill, use killfam (kill family of process)',
184             schema => 'bool',
185             description => <<'_',
186              
187             This can be useful e.g. to control load more successfully, if the
188             load-generating processes are the subchildren of the one we're governing.
189              
190             This requires CPAN module, which is installed separately.
191              
192             _
193             },
194             log_stdout => {
195             summary => 'Will be passed as arguments to `File::Write::Rotate`',
196             description => <<'_',
197              
198             Specify logging for STDOUT. Logging will be done using .
199             Known hash keys: `dir` (STR, defaults to `/var/log`, directory, preferably
200             absolute, where the log file(s) will reside, should already exist and be
201             writable, will be passed to 's constructor), `size`
202             (int, also passed to 's constructor), `histories` (int,
203             also passed to 's constructor), `period` (str, also
204             passed to 's constructor).
205              
206             Instead of this option, you can also use `log_combined` to log both stdout and
207             stderr to the same directory.
208              
209             _
210             schema => ['hash*' => keys => {
211             dir => 'str*',
212             size => 'str*',
213             histories => 'int*',
214             }],
215             tags => ['category:logging'],
216             },
217             show_stdout => {
218             schema => [bool => default => 1],
219             summary => 'Just like `show_stderr`, but for STDOUT',
220             tags => ['category:output-control'],
221             },
222             log_stderr => {
223             summary => 'Will be passed as arguments to `File::Write::Rotate`',
224             description => <<'_',
225              
226             Specify logging for STDERR. Logging will be done using .
227             Known hash keys: `dir` (STR, defaults to `/var/log`, directory, preferably
228             absolute, where the log file(s) will reside, should already exist and be
229             writable, will be passed to 's constructor), `size`
230             (int, also passed to 's constructor), `histories` (int,
231             also passed to 's constructor), `period` (str, also
232             passed to 's constructor).
233              
234             Instead of this option, you can also use `log_combined` to log both stdout and
235             stderr to the same directory.
236              
237             _
238             schema => ['hash*' => keys => {
239             dir => 'str*',
240             size => 'str*',
241             histories => 'int*',
242             }],
243             tags => ['category:logging'],
244             },
245             show_stderr => {
246             schema => ['bool'],
247             default => 1,
248             description => <<'_',
249              
250             Can be used to turn off STDERR output. If you turn this off and set
251             `log_stderr`, STDERR output will still be logged but not displayed to screen.
252              
253             _
254             tags => ['category:output-control'],
255             },
256             log_combined => {
257             summary => 'Will be passed as arguments to `File::Write::Rotate`',
258             description => <<'_',
259              
260             Specify logging for STDOUT and STDERR. Logging will be done using
261             . Known hash keys: `dir` (STR, defaults to `/var/log`,
262             directory, preferably absolute, where the log file(s) will reside, should
263             already exist and be writable, will be passed to 's
264             constructor), `size` (int, also passed to 's
265             constructor), `histories` (int, also passed to 's
266             constructor), `period` (str, also passed to 's
267             constructor).
268              
269             Instead of this option, you can also use `log_stdout` and `log_stderr`
270             separately to log stdout and stderr to different directory.
271              
272             _
273             schema => ['hash*' => keys => {
274             dir => 'str*',
275             size => 'str*',
276             histories => 'int*',
277             }],
278             tags => ['category:logging'],
279             },
280             timeout => {
281             schema => ['duration*', 'x.perl.coerce_rules'=>['From_str::human']],
282             summary => 'Apply execution time limit, in seconds',
283             description => <<'_',
284              
285             After this time is reached, process (and all its descendants) are first sent the
286             TERM signal. If after 30 seconds pass some processes still survive, they are
287             sent the KILL signal.
288              
289             The killing is implemented using 's `kill_kill()`.
290              
291             Upon timeout, exit code is set to 124.
292              
293             _
294             tags => ['category:timeout'],
295             },
296             restart_if_failed => {
297             schema => ['bool'],
298             summary => 'If set to true, do restart',
299             tags => ['category:restart'],
300             },
301             restart_if_no_output_after => {
302             schema => ['uint*'],
303             summary => 'If set to positive number, restart when there is no '.
304             'output after this many seconds',
305             tags => ['category:restart'],
306             },
307             # not yet defined
308             #restart_delay => {
309             # schema => ['duration*', default=>0, 'x.perl.coerce_rules'=>['From_str::human']],
310             # tags => ['category:restart'],
311             #},
312             #check_alive => {
313             # # not yet defined, can supply a custom coderef, or specify some
314             # # standard checks like TCP/UDP connection to some port, etc.
315             # schema => 'any*',
316             #},
317             no_screensaver => {
318             summary => 'Prevent screensaver from being activated',
319             schema => ['true*'],
320             tags => ['category:screensaver'],
321             },
322             no_sleep => {
323             summary => 'Prevent system from sleeping',
324             schema => ['true*'],
325             tags => ['category:power-management'],
326             },
327             euid => {
328             summary => 'Set EUID of command process',
329             schema => 'unix::local_uid*',
330             description => <<'_',
331              
332             Need to be root to be able to setuid.
333              
334             _
335             tags => ['category:setuid'],
336             },
337             egid => {
338             summary => 'Set EGID(s) of command process',
339             schema => 'str*',
340             description => <<'_',
341              
342             Need to be root to be able to setuid.
343              
344             _
345             tags => ['category:setuid'],
346             },
347             },
348             args_rels => {
349             'dep_all&' => [
350             [pid_dir => ['single_instance']],
351             [load_low_limit => ['load_watch']], # XXX should only be allowed when load_watch is true
352             [load_high_limit => ['load_watch']], # XXX should only be allowed when load_watch is true
353             [load_check_every => ['load_watch']], # XXX should only be allowed when load_watch is true
354             ],
355             'choose_once&' => {
356             ['log_stdout', 'log_combined'],
357             ['log_stderr', 'log_combined'],
358             },
359              
360             },
361             result_naked => 1,
362             result => {
363             summary => "Child's exit code",
364             schema => 'int',
365             },
366             };
367             sub govern_process {
368 1     1 1 1234 my $self;
369 1 50       4 if (ref $_[0]) {
370 0         0 $self = shift;
371             } else {
372 1         10 $self = __PACKAGE__->new;
373             }
374              
375             # assign and check arguments
376 1         18 my %args = @_;
377 1         8 $self->{args} = \%args;
378 1 50       5 if (defined $args{euid}) {
379             # coerce from username
380 0 0       0 unless ($args{euid} =~ /\A[0-9]+\z/) {
381 0         0 my @pw = getpwnam $args{euid};
382 0 0       0 $args{euid} = $pw[2] if @pw;
383             }
384 0 0       0 $args{euid} =~ /\A[0-9]+\z/
385             or die "govproc: euid ('$args{euid}') has to be integer";
386             }
387 1 50       4 if (defined $args{egid}) {
388             # coerce from groupname
389 0 0       0 unless ($args{egid} =~ /\A[0-9]+( [0-9]+)*\z/) {
390 0         0 my @gr = getgrnam $args{egid};
391 0 0       0 $args{egid} = $gr[2] if @gr;
392             }
393 0 0       0 $args{egid} =~ /\A[0-9]+( [0-9]+)*\z/
394             or die "govproc: egid ('$args{egid}') has to be integer or ".
395             "integers separated by space";
396             }
397              
398 1 50       3 require Proc::Killfam if $args{killfam};
399 1 50       3 require Screensaver::Any if $args{no_screensaver};
400 1 50       3 require PowerManagement::Any if $args{no_sleep};
401              
402 1         2 my $exitcode;
403              
404 1         2 my $cmd = $args{command};
405 1 50       3 defined($cmd) or die "govproc: Please specify command";
406 1 50       5 ref($cmd) eq 'ARRAY' or die "govproc: Command must be arrayref of strings";
407              
408 1         2 my $name = $args{name};
409 1 50       3 if (!defined($name)) {
410 1         3 $name = $cmd->[0];
411 1         7 $name =~ s!.*/!!; $name =~ s/\W+/_/g;
  1         3  
412 1 50       5 length($name) or $name = "prog";
413             }
414 1 50       4 defined($name) or die "govproc: Please specify name";
415 1 50       5 $name =~ /\A\w+\z/ or die "govproc: Invalid name, please use letters/numbers only";
416 1         2 $self->{name} = $name;
417              
418 1 50       4 if ($args{single_instance}) {
419 0   0     0 my $pid_dir = $args{pid_dir} // "/var/run";
420 0         0 require Proc::PID::File;
421 0 0       0 if (Proc::PID::File->running(dir=>$pid_dir, name=>$name, verify=>1)) {
422 0 0 0     0 if ($args{on_multiple_instance} &&
423             $args{on_multiple_instance} eq 'exit') {
424 0         0 $exitcode = 202; goto EXIT;
  0         0  
425             } else {
426 0         0 warn "govproc: Program $name already running";
427 0         0 $exitcode = 202; goto EXIT;
  0         0  
428             }
429             }
430             }
431              
432 1   50     6 my $showout = $args{show_stdout} // 1;
433 1   50     5 my $showerr = $args{show_stderr} // 1;
434              
435 1   50     3 my $lw = $args{load_watch} // 0;
436 1   50     5 my $lwfreq = $args{load_check_every} // 10;
437 1   50     4 my $lwhigh = $args{load_high_limit} // 1.25;
438 1   50     5 my $lwlow = $args{load_low_limit} // 0.25;
439              
440 1         3 my $noss = $args{no_screensaver};
441 1         3 my $nosleep = $args{no_sleep};
442              
443             ###
444              
445 1         2 my $out;
446 1         2 my $last_out_time = time(); # for restarting after no output for some time
447             LOG_STDOUT: {
448 1 50       2 last unless $args{log_stdout};
  1         4  
449              
450 0         0 require File::Write::Rotate;
451 0         0 my %fwrargs = %{$args{log_stdout}};
  0         0  
452 0   0     0 $fwrargs{dir} //= "/var/log";
453 0         0 $fwrargs{prefix} = $name;
454 0         0 my $fwr = File::Write::Rotate->new(%fwrargs);
455             $out = sub {
456 0     0   0 $last_out_time = time();
457 0 0 0     0 print STDOUT $_[0]//'' if $showout;
458             # XXX prefix with timestamp, how long script starts,
459 0         0 $_[0] =~ s/^/STDOUT: /mg;
460 0         0 $fwr->write($_[0]);
461 0         0 };
462             }
463              
464 1         1 my $err;
465             LOG_STDERR: {
466 1 50       3 last unless $args{log_stderr};
  1         2  
467              
468 0         0 require File::Write::Rotate;
469 0         0 my %fwrargs = %{$args{log_stderr}};
  0         0  
470 0   0     0 $fwrargs{dir} //= "/var/log";
471 0         0 $fwrargs{prefix} = $name;
472 0         0 my $fwr = File::Write::Rotate->new(%fwrargs);
473             $err = sub {
474 0 0 0 0   0 print STDERR $_[0]//'' if $showerr;
475             # XXX prefix with timestamp, how long script starts,
476 0         0 $_[0] =~ s/^/STDERR: /mg;
477 0         0 $fwr->write($_[0]);
478 0         0 };
479             }
480              
481             LOG_COMBINED: {
482 1 50       2 last unless $args{log_combined};
  1         3  
483              
484 0         0 require File::Write::Rotate;
485 0         0 my %fwrargs = %{$args{log_combined}};
  0         0  
486 0   0     0 $fwrargs{dir} //= "/var/log";
487 0         0 $fwrargs{prefix} = $name;
488 0         0 my $fwr = File::Write::Rotate->new(%fwrargs);
489             $out = sub {
490 0 0 0 0   0 print $_[0]//'' if $showout;
491             # XXX prefix with timestamp, how long script starts,
492 0         0 $_[0] =~ s/^/STDOUT: /mg;
493 0         0 $fwr->write($_[0]);
494 0         0 };
495             $err = sub {
496 0 0 0 0   0 print STDERR $_[0]//'' if $showerr;
497             # XXX prefix with timestamp, how long script starts,
498 0         0 $_[0] =~ s/^/STDERR: /mg;
499 0         0 $fwr->write($_[0]);
500 0         0 };
501             }
502              
503             $out //= sub {
504 1 50 50 1   1328 print STDERR $_[0]//'' if $showerr;
505 1   50     11 };
506             $err //= sub {
507 0 0 0 0   0 print STDERR $_[0]//'' if $showerr;
508 1   50     7 };
509              
510 1         2 my $prevented_sleep;
511             PREVENT_SLEEP: {
512 1 50       1 last unless $nosleep;
  1         4  
513 0         0 my $res = PowerManagement::Any::sleep_is_prevented();
514 0 0       0 unless ($res->[0] == 200) {
515 0         0 log_warn "[govproc] Cannot check if sleep is being prevented (%s), ".
516             "will not be preventing sleep", $res;
517 0         0 last;
518             }
519 0 0       0 if ($res->[2]) {
520 0         0 log_info "[govproc] Sleep is already being prevented";
521 0         0 last;
522             }
523 0         0 $res = PowerManagement::Any::prevent_sleep();
524 0 0 0     0 unless ($res->[0] == 200 || $res->[0] == 304) {
525 0         0 log_warn "[govproc] Cannot prevent sleep (%s), will be running anyway", $res;
526 0         0 last;
527             }
528 0         0 log_info "[govproc] Prevented sleep (%s)", $res;
529 0         0 $prevented_sleep++;
530             }
531              
532             my $do_unprevent_sleep = sub {
533 1 50   1   5 return unless $prevented_sleep;
534 0         0 my $res = PowerManagement::Any::unprevent_sleep();
535 0 0 0     0 unless ($res->[0] == 200 || $res->[0] == 304) {
536 0         0 log_warn "[govproc] Cannot unprevent sleep (%s)", $res;
537             }
538 0         0 $prevented_sleep = 0;
539 1         3 };
540              
541 1         2 my $start_time; # for timeout
542 1         2 my ($to, $h);
543              
544             my $do_start = sub {
545 1     1   2 $start_time = time();
546             IPC::Run::Patch::Setuid->import(
547             -warn_target_loaded => 0,
548             -euid => $args{euid},
549             -egid => $args{egid},
550 1 50 33     5 ) if defined $args{euid} || defined $args{egid};
551              
552 1         9 log_debug "[govproc] (Re)starting program $name ...";
553 1         8 $to = IPC::Run::timeout(1);
554             #$self->{to} = $to;
555 1 50       232 $h = IPC::Run::start($cmd, \*STDIN, $out, $err, $to)
556             or die "govproc: Can't start program: $?";
557 1         6840 $self->{h} = $h;
558              
559 1 50       19 if (defined $args{nice}) {
560             log_debug "[govproc] Setting nice level of PID %d to %d ...",
561 0         0 $h->{KIDS}[0]{PID}, $args{nice};
562 0         0 setpriority(0, $h->{KIDS}[0]{PID}, $args{nice});
563             }
564              
565             IPC::Run::Patch::Setuid->unimport()
566 1 50 33     37 if defined $args{euid} || defined $args{egid};
567 1         4 };
568              
569 1         2 $do_start->();
570              
571             local $SIG{INT} = sub {
572 0     0   0 log_debug "[govproc] Received INT signal";
573 0         0 $self->_kill;
574 0         0 $do_unprevent_sleep->();
575 0         0 exit 1;
576 1         65 };
577              
578             local $SIG{TERM} = sub {
579 0     0   0 log_debug "[govproc] Received TERM signal";
580 0         0 $self->_kill;
581 0         0 $do_unprevent_sleep->();
582 0         0 exit 1;
583 1         42 };
584              
585 1         7 my $chld_handler;
586 1         9 $self->{restart_if_failed} = $args{restart_if_failed};
587             $chld_handler = sub {
588 0     0   0 $SIG{CHLD} = $chld_handler;
589 0 0       0 if ($self->{restart_if_failed}) {
590 0         0 log_debug "[govproc] Child died";
591 0         0 $do_start->();
592             }
593 1         16 };
594 1 50       16 local $SIG{CHLD} = $chld_handler if $args{restart_if_failed};
595              
596 1         6 my $lastlw_time;
597 1         8 my ($noss_screensaver, $noss_timeout, $noss_lastprevent_time);
598              
599             MAIN_LOOP:
600 1         3 while (1) {
601             #log_debug "[govproc] main loop";
602 3 50       9 if (!$self->{suspended}) {
603             # re-set timer, it might be reset by suspend/resume?
604 3         39 $to->start(1);
605              
606 3 100       1175 unless ($h->pumpable) {
607 1         35 $h->finish;
608 1         469 $exitcode = $h->result;
609 1         29 last MAIN_LOOP;
610             }
611              
612 2         23 eval { $h->pump };
  2         12  
613 2         1289 my $everr = $@;
614 2 50 33     10 die $everr if $everr && $everr !~ /^IPC::Run: timeout/;
615             } else {
616 0         0 sleep 1;
617             }
618 2         3 my $now = time();
619              
620             TIMEOUT:
621 2 50       7 if (defined $args{timeout}) {
622 0 0       0 if ($now - $start_time >= $args{timeout}) {
623 0         0 $err->("Timeout ($args{timeout}s), killing child ...\n");
624 0         0 $self->_kill;
625             # mark with a special exit code that it's a timeout
626 0         0 $exitcode = 124;
627 0         0 last MAIN_LOOP;
628             }
629             }
630              
631             RESTART_IF_NO_OUTPUT_AFTER: {
632 2 50       10 last unless $args{restart_if_no_output_after};
  2         7  
633 0 0       0 last unless $now - $last_out_time >= $args{restart_if_no_output_after};
634 0         0 $err->("No output after $args{restart_if_no_output_after}s, restarting ...\n");
635 0         0 $self->_kill;
636 0         0 $do_start->();
637             }
638              
639             LOAD_CONTROL:
640 2 0 0     10 if ($lw && (!$lastlw_time || $lastlw_time <= ($now-$lwfreq))) {
      33        
641 0         0 log_debug "[govproc] Checking load";
642 0 0       0 if (!$self->{suspended}) {
643 0         0 my $is_high;
644 0 0       0 if (ref($lwhigh) eq 'CODE') {
645 0         0 $is_high = $lwhigh->($h);
646             } else {
647 0         0 require Unix::Uptime;
648 0         0 my @load = Unix::Uptime->load();
649 0         0 $is_high = $load[0] >= $lwhigh;
650             }
651 0 0       0 if ($is_high) {
652 0         0 log_debug "[govproc] Load is too high";
653 0         0 $self->_suspend;
654             }
655             } else {
656 0         0 my $is_low;
657 0 0       0 if (ref($lwlow) eq 'CODE') {
658 0         0 $is_low = $lwlow->($h);
659             } else {
660 0         0 require Unix::Uptime;
661 0         0 my @load = Unix::Uptime->load();
662 0         0 $is_low = $load[0] <= $lwlow;
663             }
664 0 0       0 if ($is_low) {
665 0         0 log_debug "[govproc] Load is low";
666 0         0 $self->_resume;
667             }
668             }
669 0         0 $lastlw_time = $now;
670             }
671              
672             NOSS:
673             {
674 2 50       4 last unless $noss;
  2         5  
675 0 0 0     0 last unless !$noss_lastprevent_time ||
676             $noss_lastprevent_time <= ($now-$noss_timeout+10);
677 0         0 log_debug "[govproc] Preventing screensaver from activating ...";
678 0 0       0 if (!$noss_lastprevent_time) {
679 0         0 $noss_screensaver = Screensaver::Any::detect_screensaver();
680 0 0       0 if (!$noss_screensaver) {
681 0         0 warn "govproc: Can't detect any known screensaver, ".
682             "will skip preventing screensaver from activating";
683 0         0 $noss = 0;
684 0         0 last NOSS;
685             }
686 0         0 my $res = Screensaver::Any::get_screensaver_timeout(
687             screensaver => $noss_screensaver,
688             );
689 0 0       0 if ($res->[0] != 200) {
690 0         0 warn "govproc: Can't get screensaver timeout ($res->[0]: $res->[1])".
691             ", will skip preventing screensaver from activating";
692 0         0 $noss = 0;
693 0         0 last NOSS;
694             }
695 0         0 $noss_timeout = $res->[2];
696             }
697 0         0 my $res = Screensaver::Any::prevent_screensaver_activated(
698             screensaver => $noss_screensaver,
699             );
700 0 0       0 if ($res->[0] != 200) {
701 0         0 warn "govproc: Can't prevent screensaver from activating ".
702             "($res->[0]: $res->[1])";
703             }
704 0         0 $noss_lastprevent_time = $now;
705             }
706              
707             } # MAINLOOP
708              
709 1         9 $do_unprevent_sleep->();
710              
711 1   50     70 EXIT:
712             return $exitcode || 0;
713             }
714              
715             1;
716             # ABSTRACT: Run child process and govern its various aspects
717              
718             __END__