Как отключить блоки END в дочерних процессах? - PullRequest
8 голосов
/ 29 ноября 2010

Я часто использую fork в программах, которые также имеют блоки END { ... }:

...
END { &some_cleanup_code }
...
my $pid = fork();
if (defined($pid) && $pid==0) {
    &run_child_code;
    exit 0;
}

Дочерний процесс выполняет блок END {} при выходе, но обычно я не хочупроизойдет.Есть ли способ запретить дочернему процессу вызывать блок END при выходе?За исключением этого, есть ли способ для программы «знать», что это дочерний процесс, поэтому я мог бы сказать что-то вроде

END { unless (i_am_a_child_process()) { &some_cleanup_code } }

?

Ответы [ 8 ]

18 голосов
/ 29 ноября 2010

Я не думаю, что есть какой-либо способ воспрепятствовать выполнению блоков END в разветвленном процессе, но это должно позволить вам обнаружить его:

my $original_pid; BEGIN { $original_pid = $$ }

... # Program goes here

END { do_cleanup() if $$ == $original_pid }
15 голосов
/ 29 ноября 2010

perldoc -f выход

Функция exit () не всегда завершается немедленно. Сначала он вызывает любые определенные END подпрограммы, но эти подпрограммы END не могут сами прервать выход. Точно так же любые деструкторы объекта, которые должны быть вызваны, вызываются до реального выхода. Если это проблема, вы можете позвонить POSIX:_exit($status), чтобы избежать обработки END и деструктора. Подробнее см. perlmod .

7 голосов
/ 30 ноября 2010
use B;
@{; eval { B::end_av->object_2svref } || [] } = ();

Я думал, что есть модуль Devel ::, который также позволяет вам это делать, но я не могу сейчас его найти.

Конечно, вы не можете делать это безопасно, если используете произвольные модули, которые могут использовать блоки END для своих собственных целей ...


(редактирование с помощью OP) Вы можете получить контроль над блоками END с помощью B::end_av. В качестве подтверждения концепции:

END { print "This is the first end block.\n"; }
my $END_block_2_line = __LINE__ + 1;
END { print "This is the second end block.\n"; }
END { print "This is the third end block.\n" }

sub disable_specific_END_block {
  use B;
  my ($file, $line) = @_;
  eval {
    my @ENDs = B::end_av->ARRAY;
    for (my $i=$#ENDs; $i>=0; $i--) {
      my $cv = $ENDs[$i];
      if ($cv->START->file eq $file && $cv->START->line == $line) {
        splice @{B::end_av->object_2svref}, $i, 1;
      }
    }
  };
}

disable_specific_END_block(__FILE__, $END_block_2_line);


$ perl endblocks.pl
This is the third end block.
This is the first end block.

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

6 голосов
/ 30 ноября 2010

Говоря иначе, общепринятый способ избежать их у fork детей - это что-то вроде:

exec "true";

Или, если вы считаете, что это слишком ненадежно, все это работает и дажеиспользуйте use strict совместимый:

exec    $^X =>  -eexit;

exec ~~echo => ~~-echo;

exec ~~echo => !!-echo;

и ономатопоэтический

exec reverse reverse echo => ~~-echo;

Rebmemer- В Perl нет глупых вопросов ,но могут быть глупые ответы…

$ perl -Mstrict '-leprint ucfirst lc reverse-Remember'
4 голосов
/ 30 ноября 2010

Это код, используемый POE :: Wheel :: Run, чтобы делать то, что вы хотите. POE :: Kernel :: RUNNING_IN_HELL имеет значение true, когда $^O eq 'MSWin32'. Адаптируйте его под свои нужды.

sub _exit_child_any_way_we_can {
  my $class = shift;
  my $exitval = shift || 0;

  # First make sure stdio are flushed.
  close STDIN  if defined fileno(STDIN); # Voodoo?
  close STDOUT if defined fileno(STDOUT);
  close STDERR if defined fileno(STDERR);

  # On Windows, subprocesses run in separate threads.  All the "fancy"
  # methods act on entire processes, so they also exit the parent.

  unless (POE::Kernel::RUNNING_IN_HELL) {
    # Try to avoid triggering END blocks and object destructors.
    eval { POSIX::_exit( $exitval ); };

    # TODO those methods will not exit with $exitval... what to do?
    eval { CORE::kill KILL => $$; };
    eval { exec("$^X -e 0"); };
  } else {
    eval { CORE::kill( KILL => $$ ); };

    # TODO Interestingly enough, the KILL is not enough to terminate this process...
    # However, it *is* enough to stop execution of END blocks/etc
    # So we will end up falling through to the exit( $exitval ) below
  }

  # Do what we must.
  exit( $exitval ); 
}
2 голосов
/ 29 ноября 2010

Просто установите какой-нибудь глобально доступный флаг, когда вы запускаете run_child_code().

Есть несколько способов сделать это, но я, вероятно, вставлю это в пакет и использую пакетСфера применения лексическая для хранения.Вы также можете использовать простой переменный флаг.Это немного проще.Мне нравится пакетное решение, потому что оно будет легко работать с несколькими модулями.

END { &some_cleanup_code unless ImaKid->get; }
my $pid = $fork;
if( defined($pid) && $pid == 0 ) {
    ImaKid->set;
    &run_child_code;
}

BEGIN {
    package ImaKid;

    my $child_flag;

    sub set {
       $child_flag = 1;
    }

    sub get {
       return $child_flag;
    }
}

Подумайте над тем, чтобы объединить код управления вашими форками в один пакет, чтобы создать единый согласованный API для всех ваших потребностей, связанных с форками.

1 голос
/ 22 августа 2016

Это то, что я сделал. в начале программы (где я знаю, что ни одного форка еще не произошло) Сохраните PID. мой $ parent_pid = $$;

#Then in the end block
END {
    my $current_pid = $$;
    if ( $parent_pid == $current_id ) 
        ##Here goes my cleanup code.
    }
}
0 голосов
/ 30 ноября 2010

Я знаю, что это не очень полезно для вас, но я все равно скажу: не используйте блоки END. Обычно есть лучший способ.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...