Каковы способы, которыми сигналы могут вмешиваться в коммуникацию по трубам?


Я ничего не знаю о сигналах, и только немного о трубах.

Из комментариев на ответ здима здесь похоже, что сигналы могут мешать каналу связи между родительскими и дочерними процессами.

Мне сказали, что, если вы используете IO::Select и sysread, затем выход из дочернего процесса может как-то испортить поведение IO::Select::can_read, особенно если имеется несколько дочерних процессов.

Пожалуйста, опишите, как учитывать сигналы при использовании труб? Приведенный ниже код является примером, в котором сигналы не учитываются.

use warnings;
use strict;
use feature 'say';

use Time::HiRes qw(sleep);
use IO::Select; 

my $sel = IO::Select->new;

pipe my $rd, my $wr;
$sel->add($rd); 

my $pid = fork // die "Can't fork: $!";  #/

if ( $pid == 0 ) {     # Child code

    close $rd; 
    $wr->autoflush;

    for ( 1..4 ) {

        sleep 1;
        say "tsending data";
        say $wr 'a' x ( 120 * 1024 );
    }

    say "tClosing writer and exiting";
    close $wr;

    exit; 
}

# Parent code
close $wr;    
say "Forked and will read from $pid";

my @recd;

READ:
while ( 1 ) {

    if ( my @ready = $sel->can_read(0) ) {  # beware of signals

        foreach my $handle (@ready) {

            my $buff;
            my $rv = sysread $handle, $buff, ( 64 * 1024 );
            warn "Error reading: $!" if not defined $rv;

            if ( defined $buff and $rv != 0 ) {
                say "Got ", length $buff, " characters";
                push @recd, length $buff; 
            }

            last READ if $rv == 0;
        }
    }
    else {
        say "Doing else ... ";
        sleep 0.5; 
    }
}   
close $rd;

my $gone = waitpid $pid, 0;

say "Reaped pid $gone";
say "Have data: @recd"
2 2

2 ответа:

Две вещи.

  1. Запись в трубу после закрытия считывателя (например, возможно, потому, что процесс на другом конце вышел) приводит к СИГПАЙПУ. Вы можете проигнорировать этот сигнал ($SIG{PIPE} = 'IGNORE';), чтобы вместо него получить запись для возврата ошибки EPIPE.

    В вашем случае, если вы хотите обработать эту ошибку вместо того, чтобы ваша программа была убита, просто добавьте

    $SIG{PIPE} = 'IGNORE';
    
  2. Если у вас есть определенный обработчик сигнала (например, используя $SIG{...} = sub { ... };, но не $SIG{...} = 'IGNORE'; или $SIG{...} = 'DEFAULT';), длительные системные вызовы (например, чтение из дескриптора файла) могут быть прерваны сигналом. Если это произойдет, они вернутся с ошибкой EINTR, чтобы дать обработчику сигнала шанс запустить. В Perl вам не нужно ничего делать, кроме перезапуска системного вызова, который не удался.

    В вашем случае у вас нет определенных обработчиков сигналов, поэтому это не влияет на вас.

Кстати, вы проверяете $rv == 0 даже тогда, когда $rv, как известно, не определено, и вы размещаете длину данные в @recd вместо самих данных. На самом деле, нет особого смысла использовать массив там вообще. Заменить

my @recd;

...

my $rv = sysread $handle, $buff, ( 64 * 1024 );
warn "Error reading: $!" if not defined $rv;

if ( defined $buff and $rv != 0 ) {
    say "Got ", length $buff, " characters";
    push @recd, length $buff; 
}

last READ if $rv == 0;

...

say "Have data: @recd"

С

my $buf = '';

...

my $received = sysread($handle, $buf, 64 * 1024, length($buf));
warn "Error reading: $!" if !defined($received);
last if !$received;

say "Got $received characters";

...

say "Have data: $buf"

Сигналы

Могут также прерывать функции ввода-вывода, вызывая затем сбой с $!, установленным в EINTR. Поэтому вы должны проверить наличие этой ошибки и повторить попытку, когда это произойдет.

Не делать этого-распространенный источник труднодоступных ошибок.