all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Wolfgang Bumiller <w.bumiller@proxmox.com>
To: Kefu Chai <k.chai@proxmox.com>
Cc: pve-devel@lists.proxmox.com
Subject: Re: [PATCH common 1/2] cmd: run: do not cancel the caller's alarm
Date: Thu, 5 Nov 2026 14:20:55 +0100	[thread overview]
Message-ID: <cgsng52kirt4qcaehzqlb75kqttxcgjirdgum6ndwp7ym6ag2d@p254xv6gnjv4> (raw)
In-Reply-To: <20261021110126.4024539-2-k.chai@proxmox.com>

On Wed, Oct 21, 2026 at 07:01:25PM +0800, Kefu Chai wrote:
> run() calls alarm(0) when done, so a caller's pending alarm is lost,
> or pushed back if run had a timeout of its own. The cfs lock relies on
> such an alarm: on shared LVM, 'vgs' and 'lvcreate' run under the
> storage lock, and after 'vgs' nothing limits 'lvcreate' anymore.
> 
> Only touch the alarm if run has its own timeout, and afterwards give
> the caller's alarm back what is left of it.
> 
> Signed-off-by: Kefu Chai <k.chai@proxmox.com>
> ---
>  src/PVE/Cmd.pm   | 16 ++++++++++++----
>  test/cmd-test.pl | 28 ++++++++++++++++++++++++++++
>  2 files changed, 40 insertions(+), 4 deletions(-)
> 
> diff --git a/src/PVE/Cmd.pm b/src/PVE/Cmd.pm
> index 27c9193..a6bb17d 100644
> --- a/src/PVE/Cmd.pm
> +++ b/src/PVE/Cmd.pm
> @@ -100,6 +100,7 @@ sub run($cmd, %param) {
>      my $laststderr;
>      my $timeout;
>      my $oldtimeout;
> +    my $armed_at;
>      my $pid;
>      my $exitcode = -1;
>  
> @@ -195,7 +196,10 @@ sub run($cmd, %param) {
>  
>          local $SIG{ALRM} = sub { die "got timeout\n"; }
>              if $timeout;

There is technically a race here where at *this* point the outer alarm
can trigger.

I suggest placing

    $oldtimeout = alarm(0) if $timeout;

*above* the `local $SIG{ALRM}` line above first, so that if a timeout is
created, we first disable the alarm, then establish the signal handler,
*then* the new alarm (without re-assigning to $oldtimeout again).


> -        $oldtimeout = alarm($timeout) if $timeout;
> +        if ($timeout) {
> +            $oldtimeout = alarm($timeout);
> +            $armed_at = Time::HiRes::time();
> +        }
>  
>          &$afterfork() if $afterfork;
>  
> @@ -295,12 +299,12 @@ sub run($cmd, %param) {
>              }
>          }
>  
> -        alarm(0);
> +        alarm(0) if $timeout; # while our handler is still in place
>      };
>  
>      my $err = $@;
>  
> -    alarm(0);
> +    alarm(0) if $armed_at;

This now means an external timer can interrupt `$errfunc` which it could
not previously.

I think it makes more sense to `eval` the `$errfunc` call and do
something like:

    if ($err eq "got timeout\n") {
        warn $errfunc_error;
    } else {
        $err = "$errfunc_error\n$err";
    }

then…

>  
>      if ($errmsg && $laststderr) {
>          &$errfunc(undef); # flush laststderr
> @@ -308,7 +312,11 @@ sub run($cmd, %param) {
>  
>      umask($old_umask) if defined($old_umask);
>  
> -    alarm($oldtimeout) if $oldtimeout;
> +    # give the caller's alarm back what is left of it, a lock may depend on it
> +    if ($oldtimeout) {
> +        my $left = $oldtimeout - (Time::HiRes::time() - $armed_at);
> +        alarm($left > 0 ? $left : 0.001);
> +    }

…then move this hunk to after the kill/wait code below, replacing its
`die`s with appending the error similar to the above code, so the
external timer also cannot prevent the kill/wait.

This might then also make the 2nd patch unnecessary?

>  
>      if ($err) {
>          if ($pid && ($err eq "got timeout\n")) {
> diff --git a/test/cmd-test.pl b/test/cmd-test.pl
> index c2f7ae7..191f9f5 100755
> --- a/test/cmd-test.pl
> +++ b/test/cmd-test.pl
> @@ -11,6 +11,7 @@ use lib '../src';
>  use PVE::Cmd;
>  
>  use Test::More;
> +use Time::HiRes ();
>  
>  # shell_quote: quote a single argument for the shell
>  #
> @@ -67,6 +68,33 @@ ok($@, 'run dies on a failing command without noerr');
>  
>  is(PVE::Cmd::run_command(['true']), 0, 'run_command works as a wrapper for run');
>  
> +# run must leave a pending alarm of its caller alone, like the time limit of a lock
> +#
> +# name  - how run is called
> +# cmd   - command to run, sleeps for a second by default
> +# param - parameters for run
> +my $caller_alarm_tests = [
> +    { name => 'without a timeout', param => {} },
> +    { name => 'with a timeout', param => { timeout => 5 } },
> +    {
> +        name => 'with a timeout on a command that cannot start',
> +        cmd => ['/nonexistent/command'],
> +        param => { timeout => 5 },
> +    },
> +];
> +for my $test ($caller_alarm_tests->@*) {
> +    my $fired;
> +    local $SIG{ALRM} = sub { $fired = Time::HiRes::time() };
> +    my $start = Time::HiRes::time();
> +    Time::HiRes::alarm(2);
> +    eval { PVE::Cmd::run($test->{cmd} // ['sleep', '1'], $test->{param}->%*) };
> +    Time::HiRes::sleep(0.1) until $fired || Time::HiRes::time() - $start > 5;
> +    Time::HiRes::alarm(0);
> +    ok($fired, "the caller's alarm still fires after run $test->{name}");
> +    my $after = defined($fired) ? $fired - $start : 99;
> +    cmp_ok($after, '<', 2.5, "run $test->{name} does not delay the caller's alarm");
> +}
> +
>  # TODO:
>  # - cover pipe_socket (needs a socket peer)
>  # - cover timeouts and the errfunc/logfunc callbacks
> -- 
> 2.47.3
> 
> 
> 
> 
> 

-- 




  reply	other threads:[~2026-11-05 13:21 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-21 11:01 [PATCH common 0/2] cmd: run: do not leave the command behind on a caller's timeout Kefu Chai
2026-10-21 11:01 ` [PATCH common 1/2] cmd: run: do not cancel the caller's alarm Kefu Chai
2026-11-05 13:20   ` Wolfgang Bumiller [this message]
2026-10-21 11:01 ` [PATCH common 2/2] cmd: run: kill the command when run dies Kefu Chai

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=cgsng52kirt4qcaehzqlb75kqttxcgjirdgum6ndwp7ym6ag2d@p254xv6gnjv4 \
    --to=w.bumiller@proxmox.com \
    --cc=k.chai@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal