Почему разветвление заставляет родительский процесс снова запускать весь скрипт вместо того, чтобы продолжать работу в месте fork ()? - PullRequest
0 голосов
/ 13 июля 2020

У меня есть довольно простой сценарий python, который для этого вопроса сокращен до следующего:

#!/usr/bin/env python3
import os

print(os.getpid(), "a")

def main():
    n_children = 10
    n_it = 1
    children = []
    for x in range(n_children):
        print(os.getpid(), "b", "x=", x)
        pid = os.fork()
        if pid == 0:
            for i in range(n_it):
                pass
            exit(0)
        children.append(pid)

    print("started %d children doing %d iterations each"%(n_children, n_it))

    err = 0
    for pid in children:
        r = os.wait()
        if r[1] != 0:
            print("wait", r)
            err += 1

    if err == 0:
        print("ok")
    else:
        print("err")
        exit(1)

if __name__ == '__main__':
    print(os.getpid(), __name__, "start")
    main() 
    print(os.getpid(), __name__, "end")   

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

16047 a
16047 __main__ start
16047 b x= 0
16047 b x= 1
16047 b x= 2
16047 b x= 3
16047 b x= 4
16047 b x= 5
16047 b x= 6
16047 b x= 7
16047 b x= 8
16047 b x= 9
started 10 children doing 1 iterations each
ok
16047 __main__ end

Пока все хорошо. Теперь, если я запускаю его из сценария xfstests (так AFAIK вызывал серию сценариев оболочки), я получаю очень странное поведение, когда кажется, что разветвление заставляет родительский процесс запускаться заново ... Я не могу воспроизвести его снаружи из xfstest. Это необъяснимо.

QA output created by 002
/home/aaptel/prog/xfstests-git/xfstests/tests/cifs/optest.py -u //localhost/test -o username=aaptel,password=aaptel,noperm,seal,vers=3.0,sec=ntlmsspi,mfsymlinks,actimeo=0,nosharesock -c 10 -i 1 /mnt/test /mnt/oplock
15911 a
15911 __main__ start
15911 b x= 0
15911 a
15911 __main__ start
15911 b x= 0
15911 b x= 1
15911 a
15911 __main__ start
15911 b x= 0
15911 b x= 1
15911 b x= 2
15911 a
15911 __main__ start
15911 b x= 0
15911 b x= 1
15911 b x= 2
15911 b x= 3
15911 b x= 4
15911 b x= 5
15911 b x= 6
15911 b x= 7
15911 a
15911 __main__ start
15911 b x= 0
15911 b x= 1
15911 b x= 2
15911 b x= 3
15911 b x= 4
15911 a
15911 __main__ start
15911 b x= 0
15911 b x= 1
15911 b x= 2
15911 b x= 3
15911 a
15911 __main__ start
15911 b x= 0
15911 b x= 1
15911 b x= 2
15911 b x= 3
15911 b x= 4
15911 b x= 5
15911 b x= 6
15911 b x= 7
15911 b x= 8
15911 b x= 9
15911 a
15911 __main__ start
15911 b x= 0
15911 b x= 1
15911 b x= 2
15911 b x= 3
15911 b x= 4
15911 b x= 5
15911 b x= 6
15911 a
15911 __main__ start
15911 b x= 0
15911 b x= 1
15911 b x= 2
15911 b x= 3
15911 b x= 4
15911 b x= 5
15911 a
15911 __main__ start
15911 b x= 0
15911 b x= 1
15911 b x= 2
15911 b x= 3
15911 b x= 4
15911 b x= 5
15911 b x= 6
15911 b x= 7
15911 b x= 8
15911 a
15911 __main__ start
15911 b x= 0
15911 b x= 1
15911 b x= 2
15911 b x= 3
15911 b x= 4
15911 b x= 5
15911 b x= 6
15911 b x= 7
15911 b x= 8
15911 b x= 9
started 10 children doing 1 iterations each
ok
15911 __main__ end

Обратите внимание, как один и тот же PID (основной родительский процесс) печатает «a», « main start» и входит в l oop несколько раз .. .?!

Это как если бы fork () заставлял родительский процесс запускать скрипт заново вместо того, чтобы продолжить в месте вызова fork ().

Это на Linux с Python 3.6.10.

Ответы [ 2 ]

0 голосов
/ 13 июля 2020

Я выполнил часть процесса python и обнаружил, что потомки выполняют запись:

18239 write(1</tmp/17820.out>, "18238 a\n18238 __main__ start\n182"..., 42) = 42

Обратите внимание, кто PID не соответствует. 1839 печатает «18238 ...».

Я думаю, что происходит то, что print () буферизуется, а промывка происходит в дочерних элементах. Но каждый дочерний элемент получает копию родительского буфера и сбрасывает ее, распечатывая один и тот же буфер несколько раз!

Я решил эту проблему, сбрасывая stdout перед fork () следующим образом: 1010 * Теперь я получаю тот же ожидаемый результат везде.

Я могу воспроизвести проблему, просто запустив сценарий в вопросе и перенаправив вывод в файл:

$ ./script.py > x

x будет иметь дублированный вывод.

Связанный отчет об ошибке: https://bugs.python.org/issue17230 (печально отмечен как WONTFIX)

0 голосов
/ 13 июля 2020

Это очевидная проблема с инструментом xfstests и не имеет ничего общего с Python или самими вызовами fork, поскольку вы позаботились о документировании

. Это может помочь, например, если xfstests встраивает свои Собственный интерпретатор python, вызов fork сам будет форкнуть тестовый набор, и он может решить повторить текущий тест с самого начала. (В отличие от вызова for из автономного скрипта, который формирует только состояние интерпретатора Python. Таким образом, даже если это неожиданно, это вряд ли можно назвать ошибочным поведением.

Плохая новость в том, что у вас не будет простого обходного пути, если ваше первоначальное намерение состоит в том, чтобы распараллелить выполнение тестов - или сделать тест, который выполняет параллельный код для запуска ситуаций, которые вы хотите проверить. Вызовы более высокого уровня, такие как subprocess или multiprocessing, и даже Пулы исполнителей с concurrent.futures все будут вызывать fork в слоях нижнего уровня.

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

Если они этого не делают, как я подозреваю, обходной путь для случая намеренного параллельного запуска кода для ваших тестов будет включать работника, работающего вне контроля xfstests - что-то с использованием Celery подойдет, если вы настроите его правильно:

  • Вы запускаете рабочих, используя сельдерей, параллельно с вашим набором тестов
  • ваш основной поток, выполняющийся внутри набора тестов, координирует вызовы и ожидает завершения рабочих - необязательно создание промежуточных утверждений на внешнем ресурсе (например, файловой системе), которые вы хотите измерять в тщательно определенные моменты времени.

Если вы просто хотите запускать тесты параллельно, просто проверьте сами документы xfstests о том, как для этого «извне» тестового набора, написанного на Python.

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