Защо не вземам състоянието на изход от моя дъщерен процес?

Имам програма на Perl, която управлявам, която има способността да разклонява множество процеси (до определен лимит), да ги наблюдава и когато излизат, да разклонява допълнителни процеси (отново, до лимита), докато списъкът с неща за изпълнение не бъде завършен. Работи добре, с изключение на това, че по някаква причина изглежда, че не улавя правилното състояние на изход от моите дъщерни процеси.

Кодът, който не работи, използва fork(), waitpid() на Perl, а дъщерните процеси използват POSIX::_exit() за излизане. Ето някои извадки от съответния код:

Разклонителен код:

# Initialize process if running in parallel mode
my $pid;
if ($options{'parallel'} > 0) {
    log_status("Waiting to fork test #".$curr_test{'id'}."...\n");

    # Here, wait for child processes to complete so we can fork off new ones without going over the specified limit
    while ( keys(%children) >= $options{'parallel'}) {
        my $kid = waitpid(-1, 0);
        my $kid_status = $?;

        if ($kid > 0) {
            log_status("Child process (PID ".$kid.", test ".$children{$kid}.") exited with status ".$kid_status.".\n");
            $error_status |= $kid_status;
            delete $children{$kid};
        }
    }

    $pid = fork();
    tdie("Unable to fork!\n") unless defined $pid;

    if ($pid != 0) {
        # I'm the parent
        $is_child = 0;
        log_status("Forked child process (PID ".$pid.").\n");

        $children{$pid} = $curr_test{'logstr'};

        next TEST_LOOP;
    }
    else {
        # I'm the child
        $is_child = 1;
        log_status("Starting test = ".$curr_test{'logstr'}."\n");
    }
}

Изходен код на дъщерен процес:

### finish_child() ###
# Handles exiting the script, like the finish() function, but only when running as a child process in parallel mode.
# Parameters:
#   - The error code to exit with
###
sub finish_child( $ ) {
    my ($error_status) = @_;


    # If running in parallel mode, exit this fork
    if ($options{'parallel'} > 0) {
        log_status("Entering: ".Cwd::abs_path("..")."\n");
        chdir "..";
        log_status("Exiting with status: ".$error_status."\n");
        POSIX::_exit($error_status);
    }
}

Ето къде се извиква finish_child() в моето примерно изпълнение:

# If build failed, log status and gracefully clean up logfiles, then continue to next test in list.
if ($test_status > 0) {
    $email_subject = "Build failed!";
    log_status("Build of ".$testline." FAILED.\n");
    tlog(1, "Build of ".$testline." FAILED.\n");

    log_status("Entering: ".Cwd::abs_path("..")."\n");
    chdir "..";


    log_report(\%curr_test, $test_status);

    # Print out pass/fail status for each test as it completes
    $quietmode = $options{'quiet'}; # Backup quiet mode setting
    $options{'quiet'} = 0;

    if ($test_status == 0) {
        log_status("Test ".$testline." PASSED.\n");
        tlog(0, "Test ".$testline." PASSED.\n");
    }
    else {
        log_status("Test ".$testline." FAILED.\n");
        tlog(1, "Test ".$testline." FAILED.\n");
    }

    $options{'quiet'} = $quietmode;  # Restore quiet mode setting
    finish_logs();


    # Link logs to global area and rename if running multiple tests
    system("ln -sf ".$root_dir."/verify/".$curr_test{'id'}."/".$verify::logfile." ../".(($test_status > 0) ? "fail".$curr_test{'id'}.".log" : "pass".$curr_test{'id'}.".log" )) if (@tests > 1);


    if ($options{'parallel'} > 0 && $pid == 0) {
        # If we're in parallel mode and I'm a child process, I should exit, instead of continuing to loop.
        finish_child($test_status);
    }
    else {
        # If we're not in parallel mode, I should continue to loop.
        next TEST_LOOP;
    }
}

Ето поведението, което виждам според дневника от изпълнението, което направих:

<Parent> Waiting for all child processes to complete...
<Child> [PID 28657] Entering: <trimmed>
<Child> [PID 28657] Running user command: make --directory <trimmed> TARGET=build BUILD_DIR=<trimmed> RUN_DIR=<trimmed>            
<Child> [PID 28657] User command finished with return code: 512
<Child> [PID 28657] Build step finished with return code 512
<Child> [PID 28657] Entering: <trimmed>
<Child> [PID 28657] Build of rx::basic(1) FAILED.
<Child> [PID 28657] Entering: <trimmed>
<Child> [PID 28657] Test rx::basic(1) FAILED.
<Child> [PID 28657] Closing log file.
<Child> [PID 28657] Closing error log file.
<Child> [PID 28657] Entering: <trimmed>
<Parent> Child process (PID 28657, test rx::basic(1)) exited with status 0.

Имам код, който използва IPC на Perl за изпълнение на команди (вместо извикването system(), за по-голяма гъвкавост, който улавя изходния код правилно, което можете да видите в редовете „Потребителска команда“ от регистрационния файл.

Какво може да правя грешно тук? Защо в този случай не мога да получа състоянието на изход от $?? Примерите, които намерих онлайн, изглежда показват, че това трябва да работи добре.

За справка, изпълнявам Perl v5.10.1. Този инструмент Perl също е с отворен код в GitHub, ако смятате, че трябва да прегледате останалата част от кода: https://github.com/benrichards86/Verify/blob/master/verify.pl


person Ben Richards    schedule 05.09.2013    source източник
comment
Къде е редът Излизане със състояние: в дневника?   -  person ikegami    schedule 05.09.2013
comment
@ikegami Това не съм сигурен. Не забелязах това по-рано. Възможно е да се загуби в буфера. Ще отстраня грешки в това конкретно нещо допълнително и ще видя какво ще намеря.   -  person Ben Richards    schedule 05.09.2013
comment
А, версията на кода, която изпълних, нямаше този конкретен оператор за регистриране в finish_child(), поради което не се появи във фрагмента от регистрационния файл, който публикувах по-горе. Това обаче беше единствената разлика. Оттук и несъответствието. Съжалявам за това.   -  person Ben Richards    schedule 05.09.2013


Отговори (3)


Ако $test_status е 512, обаждате ли се на POSIX::_exit(512)? Това е неправилно. Дъщерен процес трябва да извика POSIX::_exit с операнд в диапазона от 0 до 255 и родителският процес на Perl, който извлича това дете, ще получи $? зададено на статус на изход << 8.

POSIX::_exit(512) е еквивалентно на POSIX::_exit(512 % 256) или POSIX::_exit(0).

person mob    schedule 05.09.2013
comment
О, това е много добра гледна точка и не е нещо, което съм разбрал. Ще тествам поправка за това и ще видя дали работи! - person Ben Richards; 05.09.2013
comment
Опитах корекцията и се получи! Маркиране на това като отговор (въпреки че всеки отговор тук беше полезен). - person Ben Richards; 05.09.2013
comment
@ikegami Използвах вашето предложение направете го като bash и се обадих: POSIX::_exit(($error_status & 0x7F) ? ($error_status | 0x80) : ($error_status >> 8));. Отбеляза това като отговор, защото той стигна тук пръв и беше прав. - person Ben Richards; 05.09.2013

Изглежда, че правите това, което се свежда до следното:

exit($?)

Искате да разпространите стойността, която детето е прехвърлило на exit, но това не е това, което $? съдържа.

Ако детето е било убито от сигнал, $? & 0x7F съдържа номера на сигнала, който е убил процеса.

Ако детето не е било убито от сигнал, $? & 0x7F е нула, а $? >> 8 съдържа стойността, която процесът е предал на exit.

Така че, когато детето прави exit(1), вие правите exit(256) и това е извън обхвата на Unix системите. Високите битове се отрязват, оставяйки ви с нула (256 & 0xFF = 0).


Предлагам ви да направите това, което прави bash:

exit( ($? & 0x7F) ? ($? | 0x80) : ($? >> 8) );

Когато детето прави exit(1), това прави exit(1).

Когато детето бъде убито от, да речем, SIGTERM (15), това прави exit(128 + 15).

person ikegami    schedule 05.09.2013
comment
(Използвах exit в моите примери, но всичко, което казах, се отнася и за _exit.) - person ikegami; 05.09.2013
comment
Благодаря! Използвах вашето предложение "do what bash прави", където извиках POSIX::_exit(), което реши проблема. Все пак маркирах отговора на @mob, тъй като той дойде тук пръв. :) - person Ben Richards; 05.09.2013

Да, това може да е обяснението, но това, което ме заинтригува е, че изходът ви от теста не показва състоянието на изход, което детето всъщност използва. В кода има лог съобщение („Излизане със състояние:...“), но няма съответен ред в изхода.

Така че не можем наистина да кажем дали нещо се обърка в тази част от вашия код.

Първо си помислих, че използването на POSIX::_exit може да обясни проблема с регистрирането (това би попречило на окончателните буфери да бъдат изчистени), но като погледна отново кода ви, виждам, че сте изключили излизането, преди да извикате finish_child.

Бих препоръчал като първа стъпка да накарате регистрирането да работи правилно, за да можете да разберете къде е проблемът. Защо не преместите логиката за затваряне на регистрационния файл и преименуване на лог файл в подпрограмата за завършване на дете като последното нещо, направено преди излизане?

Що се отнася до проблема със състоянието на излизане, виждам три възможни обяснения, всички в кода за дъщерния процес:

  • детето всъщност не излиза чрез функцията finish_child
  • ненулевото състояние, което смятате, че се предава на finish_child и след това на изход, всъщност не се предава
  • както беше предложено по-горе, вашият изходен статус е > 255

Има ли някаква конкретна причина, поради която използвате POSIX::_exit() вместо exit() и waitpid(-1) вместо wait()?

person VolDeNuit    schedule 05.09.2013
comment
Бях в процес на преработване на кода доста. Това се съсредоточава най-вече около анализирането/индексирането, но основният поток на скрипта вероятно също може да използва преработване, което в крайна сметка ще включва регистриране. Не бих се изненадал, ако там имаше грешки при регистриране! :) - person Ben Richards; 05.09.2013
comment
_exit не извиква деструктори и END блокове, предназначени да бъдат изпълнени в родителя. Например, ако имате манипулатор на база данни в родителя, извикването на exit в дъщерния ще го затвори, но _exit ще остави родителя незасегнат. - person ikegami; 05.09.2013
comment
@ikegami Да, затова го използвах. Вярвам, че първо опитах exit() и се сблъсках със същия проблем (застояли манипулатори на файлове, например). - person Ben Richards; 05.09.2013
comment
Ако искате вашите END блокове да правят различни неща в зависимост от това дали сте детето или не, използвайте глобалната променлива $is_child, която задавате като първото нещо, което се прави при въвеждане на дъщерния код. По този начин можете да използвате стандартната функция exit(), което означава, че няма да се притеснявате за странични ефекти от използването на неосновни функции. - person VolDeNuit; 06.09.2013
comment
@VolDeNuit може да погледна. Вчера разбрах, че вероятно бих могъл да използвам тази променлива на повече места от текущите. Както казах, преработвам голяма част от кода, така че това може да е нещо, което ще разгледам. - person Ben Richards; 06.09.2013