[pve-devel] [RFC common 2/2] fork_worker: use separate pipe for status messages

Thomas Lamprecht t.lamprecht at proxmox.com
Fri Dec 15 14:07:52 CET 2017


On 12/15/2017 12:07 PM, Wolfgang Bumiller wrote:
> Generally the idea is good. Some issues in the code, though.
> Plus I wonder if the long stdout/err replacements could just be changed
> to use dup2()... (There's more that could be improved, eg. csync+psync
> could be one socketpair())

For now I'll send a v2 with you comments (hopefully) addressed.
But I agree that this could be improved a bit, I'll add it to my
nice-to-look-at list for next year :)
> 
> On Fri, Dec 15, 2017 at 09:13:33AM +0100, Thomas Lamprecht wrote:
>> We forced line wise flushing of the workers STDOUT and STDERR to
>> capture the final status (TASK OK/TASK ERROR).
>> Thus, if the code executed in the worker wanted to flush explicitly,
>> e.g., when the last output wasn't new line terminated but needed to
>> reach the users eyes, the parent just ignored that.
>> This leads to confusing results in CLI handlers using fork_workers.
>>
>> So remove the buffering logic completely and introduce a separate
>> pipe for sending the final status.
>>
>> Signed-off-by: Thomas Lamprecht <t.lamprecht at proxmox.com>
>> ---
>>  src/PVE/RESTEnvironment.pm | 56 +++++++++++++++++++++++++---------------------
>>  1 file changed, 30 insertions(+), 26 deletions(-)
>>
>> diff --git a/src/PVE/RESTEnvironment.pm b/src/PVE/RESTEnvironment.pm
>> index 0ad6dba..3a2bf8e 100644
>> --- a/src/PVE/RESTEnvironment.pm
>> +++ b/src/PVE/RESTEnvironment.pm
>> @@ -396,6 +396,7 @@ sub fork_worker {
>>  
>>      my @psync = POSIX::pipe();
>>      my @csync = POSIX::pipe();
>> +    my @ctrlfd = POSIX::pipe() if $sync;
>>  
>>      my $node = $self->{nodename};
>>  
>> @@ -427,9 +428,11 @@ sub fork_worker {
>>  	POSIX::setsid();
>>  
>>  	POSIX::close ($psync[0]);
>> +	POSIX::close ($ctrlfd[0]) if $sync;
>>  	POSIX::close ($csync[1]);
>>  
>>  	$outfh = $sync ? $psync[1] : undef;
>> +	my $resfh = $sync ? $ctrlfd[1] : undef;
>>  
>>  	eval {
>>  	    PVE::INotify::inotify_close();
>> @@ -450,6 +453,7 @@ sub fork_worker {
>>  		    if !open(STDIN, "</dev/null");
>>  
>>  		$outfh = PVE::Tools::upid_open($upid);
>> +		$resfh = $outfh;
>>  	    }
>>  
>>  
>> @@ -499,10 +503,14 @@ sub fork_worker {
>>  	    chomp $err;
>>  	    $err =~ s/\n/ /mg;
>>  	    syslog('err', $err);
>> -	    print STDERR "TASK ERROR: $err\n";
>> +	    my $msg = "TASK ERROR: $err\n";
>> +	    POSIX::write($resfh, $msg, length($msg));
>> +	    POSIX::close($resfh) if $sync;
>>  	    POSIX::_exit(-1);
>>  	} else {
>> -	    print STDERR "TASK OK\n";
>> +	    my $msg = "TASK OK\n";
>> +	    POSIX::write($resfh, $msg, length($msg));
>> +	    POSIX::close($resfh) if $sync;
>>  	    POSIX::_exit(0);
>>  	}
>>  	kill(-9, $$);
>> @@ -511,6 +519,7 @@ sub fork_worker {
>>      # parent
>>  
>>      POSIX::close ($psync[1]);
>> +    POSIX::close ($ctrlfd[1]);
> 
> Needs to check $sync
>     pvedaemon[1617]: Use of uninitialized value in subroutine entry at /usr/share/perl5/PVE/RESTEnvironment.pm line 522.
> 

Yes, that one slipped through... thanks for catching!

>>      POSIX::close ($csync[0]);
>>  
>>      my $readbuf = '';
>> @@ -562,7 +571,6 @@ sub fork_worker {
>>  
>>      if ($sync) {
>>  	my $count;
>> -	my $outbuf = '';
>>  	my $int_count = 0;
>>  	eval {
>>  	    local $SIG{INT} = local $SIG{QUIT} = local $SIG{TERM} = sub {
>> @@ -591,38 +599,34 @@ sub fork_worker {
>>  		    }
>>  		    last if $count == 0; # eof
>>  
>> -		    $outbuf .= $readbuf;
>> -		    while ($outbuf =~ s/^(([^\010\r\n]*)(\r|\n|(\010)+|\r\n))//s) {
>> -			my $line = $1;
>> -			my $data = $2;
>> -			if ($data =~ m/^TASK OK$/) {
>> -			    # skip
>> -			} elsif ($data =~ m/^TASK ERROR: (.+)$/) {
>> -			    print STDERR "$1\n";
>> -			} else {
>> -			    print $line;
>> -			}
>> -			if ($outfh) {
>> -			    print $outfh $line;
>> -			    $outfh->flush();
>> -			}
>> -		    }
>> +		    print $readbuf;
>> +		    select->flush();
>> +
>> +		    # write always to task log (output and control messages)
> 
> But control messages aren't always read here as $ctrlfd isn't in
> $select (and doesn't have to be since it's only filled when the client
> exits, and read below.)

uh, rebasing leftover... I had another - worse - approach initially,
where the comment held true

> 
>> +		    print $outfh $readbuf;
>> +		    $outfh->flush();
>>  		} else {
>>  		    # some commands daemonize without closing stdout
>>  		    last if !PVE::ProcFSTools::check_process_running($cpid);
>>  		}
>>  	    }
>> +
>> +	    # get status (error or OK)
>> +	    POSIX::read($ctrlfd[0], $readbuf, 4096);
> 
> Again: $ctrlfd validity depends on $sync.
> 

that one is OK, though, we're in a if ($sync) branch here

> (We could always have a $ctrlfd thoguh?)
> 

Hmm, not totally sure, for the !$sync case it'd have no use, wouldn't it?

>> +	    if ($readbuf =~ m/^TASK OK\n?$/) {
>> +		print $outfh $readbuf;
>> +	    } elsif ($readbuf =~ m/^TASK ERROR: (.*)\n?$/) {
>> +		print STDERR "$1\n";
>> +		print $outfh "\n$readbuf";
>> +	    } else {
>> +		die "got unexpected control message: $readbuf\n";
>> +	    }
>> +	    $outfh->flush();
>>  	};
>>  	my $err = $@;
>>  
>>  	POSIX::close($psync[0]);
>> -
>> -	if ($outbuf) { # just to be sure
>> -	    print $outbuf;
>> -	    if ($outfh) {
>> -		print $outfh $outbuf;
>> -	    }
>> -	}
>> +	POSIX::close($ctrlfd[0]);
>>  
>>  	if ($err) {
>>  	    $err =~ s/\n/ /mg;
>> -- 
>> 2.11.0





More information about the pve-devel mailing list