Почему `killpg` возвращает значение« не разрешено »при правильном владении?

У меня есть код, который fork()s вызывает setsid() в дочернем элементе и запускает некоторую обработку. Если любой из дочерних процессов завершает работу (waitpid(-1, 0)), я убиваю все группы дочерних процессов:

child_pids = []
for child_func in child_functions:
    pid = fork()
    if pid == 0:
        setsid()
        child_func()
        exit()
    else:
        child_pids.append(pid)

waitpid(-1, 0)
for child_pid in child_pids:
    try:
        killpg(child_pid, SIGTERM)
    except OSError as e:
        if e.errno != 3: # 3 == no such process
            print "Error killing %s: %s" %(child_pid, e)

Однако иногда вызов killpg завершается ошибкой с сообщением «операция не разрешена»:

Error killing 22841: [Errno 1] Operation not permitted

Почему это могло происходить?

Полный рабочий пример:

from signal import SIGTERM
from sys import exit
from time import sleep
from os import *

def slow():
    fork()
    sleep(10)

def fast():
    sleep(1)

child_pids = []
for child_func in [fast, slow, slow, fast]:
    pid = fork()
    if pid == 0:
        setsid()
        child_func()
        exit(0)
    else:
        child_pids.append(pid)

waitpid(-1, 0)
for child_pid in child_pids:
    try:
        killpg(child_pid, SIGTERM)
    except OSError as e:
        print "Error killing %s: %s" %(child_pid, e)

Который дает:

$ python killpg.py
Error killing 23293: [Errno 3] No such process
Error killing 23296: [Errno 1] Operation not permitted

person David Wolever    schedule 20.09.2012    source источник
comment
Во-первых, вы используете Linux, * BSD / Mac или что-то еще? Поскольку в IIRC linux есть более сложные правила разрешений для killpg. Кроме того, можете ли вы kill дети, даже если вы не можете killpg их? (Я понимаю, что это не решение, это просто помогает диагностировать.)   -  person abarnert    schedule 21.09.2012
comment
@abarnert подытожил. Не бывает в Linux, происходит на моем Mac.   -  person Steve Kehlet    schedule 21.09.2012
comment
Извините, я должен был упомянуть: это на OS X. Глядя на ответы, кажется, что Darwin / BSD делает что-то уникальное.   -  person David Wolever    schedule 21.09.2012
comment
Похоже, что это происходит во FreeBSD 7, а также в OS X 10.5 и 10.8, так что это могут быть все системы, производные от BSD. Ничто на страницах руководства BSD не указывает на то, что это должно произойти, но я не смог найти никаких соответствующих отчетов об ошибках. Чтобы ответить на мой собственный вопрос, kill тоже не работает, но с ESRCH, а не с EPERM. В любом случае, я думаю, что nneonneo раскопал корень проблемы; что вы хотите с этим делать - другой вопрос. (Мое обходное решение должно сработать, но оно некрасиво, и между двумя вызовами может быть состояние гонки…)   -  person abarnert    schedule 21.09.2012
comment
В вашем коде есть состояние гонки (хотя waitpid делает это крайне маловероятным): нет ничего, что заставляло бы setsid выполняться до того, как вы вызовете killpg. Обычный способ избежать этого состояния гонки, установив группу процессов как в дочернем, так и в родительском, предотвратит setsid, поэтому вам, вероятно, придется использовать явную синхронизацию.   -  person jilles    schedule 21.09.2012


Ответы (3)


Я также добавил некоторую отладку (слегка измененный исходный код). Это происходит, когда вы пытаетесь убить группу процессов, которая уже завершена и находится в статусе зомби. Да, и это легко повторить только с [fast, fast].

$ python so.py 
spawned pgrp 6035
spawned pgrp 6036
Reaped pid: 6036, status: 0
 6035  6034  6035 Z    (Python)
 6034   521  6034 S+   python so.py
 6037  6034  6034 S+   sh -c ps -e -o pid,ppid,pgid,state,command | grep -i python
 6039  6037  6034 R+   grep -i python

killing pg 6035
Error killing 6035: [Errno 1] Operation not permitted
 6035  6034  6035 Z    (Python)
 6034   521  6034 S+   python so.py
 6040  6034  6034 S+   sh -c ps -e -o pid,ppid,pgid,state,command | grep -i python
 6042  6040  6034 S+   grep -i python

killing pg 6036
Error killing 6036: [Errno 3] No such process

Не знаю, как с этим бороться. Возможно, вы можете поместить waitpid в цикл while, чтобы получить все завершенные дочерние процессы, а затем продолжить с помощью pgkill () остальные.

Но ответ на ваш вопрос: вы получаете EPERM, потому что вам не разрешено убивать лидера группы процессов зомби (по крайней мере, в Mac OS).

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

$ kill -TERM -6115
-bash: kill: (-6115) - Operation not permitted

Подтверждено, что этого также не происходит в Linux.

person Steve Kehlet    schedule 20.09.2012

Очевидно, вы не можете убить группу процессов, состоящую из зомби. Когда процесс завершается, он становится зомби, пока кто-нибудь не позвонит ему waitpid. Обычно init берет на себя ответственность за детей, чьи родители умерли, чтобы избежать детей-сирот-зомби.

Таким образом, процесс в некотором смысле все еще «работает», но он не получает процессорного времени и игнорирует любые kill команды, отправленные ему напрямую. Однако, если группа процессов состоит полностью из зомби, поведение кажется таково, что завершение группы процессов вызывает EPERM вместо тихого отказа. Обратите внимание, что уничтожение группы процессов, не содержащей зомби, по-прежнему удается.

Пример программы, демонстрирующий это:

import os
import time

res = os.fork()

if res:
    time.sleep(0.2)
    pgid = os.getpgid(res)
    print pgid

    while 1:
        try:
            print os.kill(-pgid, 9)
        except Exception, e:
            print e
            break

    print 'wait', os.waitpid(res, 0)

    try:
        print os.kill(-pgid, 9)
    except Exception, e:
        print e

else:
    os.setpgid(0, 0)
    while 1:
        pass

Результат выглядит как

56621
None
[Errno 1] Operation not permitted
wait (56621, 9)
[Errno 3] No such process

Родитель убивает ребенка с помощью SIGKILL, затем пытается снова. Во второй раз он получает EPERM, поэтому он ждет дочернего элемента (пожинает его и уничтожает его группу процессов). Итак, третий kill дает ESRCH, как и ожидалось.

person nneonneo    schedule 21.09.2012
comment
Хорошее расследование. Похоже, это объясняет поведение, которое я только смог обнаружить. - person abarnert; 21.09.2012
comment
Я считаю это ошибкой операционной системы. Согласно POSIX, отправка сигнала зомби должна завершиться успешно (поскольку время жизни процесса продлевается до тех пор, пока зомби не будет пожат). - person jilles; 21.09.2012
comment
@jilles: Мне нужна ссылка на это. Также обратите внимание, что kill(pid, sig) молча завершается успешно, если pid является зомби-процессом, но что kill(-pgid, sig) терпит неудачу, если pgid является одноэлементной группой зомби-процессов. - person nneonneo; 21.09.2012
comment
Ах, интересно. Спасибо за расследование. - person David Wolever; 21.09.2012
comment
Поскольку процессы-зомби в конечном итоге становятся ответственностью init, возможно, что если все дочерние элементы группы процессов являются зомби, вся группа перейдет в собственность init, и у бывших дедушек и бабушек больше нет разрешения на ее уничтожение. - person mrgrieves; 22.01.2014

При добавлении дополнительных журналов иногда кажется, что killpg возвращает EPERM вместо ESRCH:

#!/usr/bin/python

from signal import SIGTERM
from sys import exit
from time import sleep
from os import *

def slow():
    fork()
    sleep(10)

def fast():
    sleep(1)

child_pids = []
for child_func in [fast, slow, slow, fast]:
    pid = fork()
    if pid == 0:
        setsid()
        print child_func, getpid(), getuid(), geteuid()
        child_func()
        exit(0)
    else:
        child_pids.append(pid)

print waitpid(-1, 0)
for child_pid in child_pids:
    try:
        print child_pid, getpgid(child_pid)
    except OSError as e:
        print "Error getpgid %s: %s" %(child_pid, e)      
    try:
        killpg(child_pid, SIGTERM)
    except OSError as e:
        print "Error killing %s: %s" %(child_pid, e)

Каждый раз, когда killpg завершается с ошибкой с EPERM, getpgid ранее терпит неудачу с ESRCH. Например:

<function fast at 0x109950d70> 26561 503 503
<function slow at 0x109950a28> 26562 503 503
<function slow at 0x109950a28> 26563 503 503
<function fast at 0x109950d70> 26564 503 503
(26564, 0)
26561 Error getpgid 26561: [Errno 3] No such process
Error killing 26561: [Errno 1] Operation not permitted
26562 26562
26563 26563
26564 Error getpgid 26564: [Errno 3] No such process
Error killing 26564: [Errno 3] No such process

Я понятия не имею, почему это происходит - будь то законное поведение или ошибка в Дарвине (унаследованная от FreeBSD или иначе) и т. Д.

Похоже, вы можете обойти это подобным образом, дважды проверив EPERM, вызвав kill(child_pid, 0); если это возвращает ESRCH, то реальной проблемы с разрешением нет. Конечно, в коде это выглядит довольно некрасиво:

for child_pid in child_pids:
    try:
        killpg(child_pid, SIGTERM)
    except OSError as e:
        if e.errno != 3: # 3 == no such process
            if e.errno == 1:
                try:
                    kill(child_pid, 0)
                except OSError as e2:
                    if e2.errno != 3:
                        print "Error killing %s: %s" %(child_pid, e)
            else:
                print "Error killing %s: %s" %(child_pid, e)
person abarnert    schedule 20.09.2012