我注意到了这个问题,因此我编写了以下简单的演示代码,但该问题仍然存在。
我有一个worker.py,可以模拟一些长时间的过程并通过stdout报告当前进度
from sys import stdout
import time
def main():
to_print = ("Start\n").encode("utf-8")
stdout.buffer.write(to_print)
stdout.flush()
for i in range(0, 11):
time.sleep(1)
to_print = ("progress:%d\n" % (i*10)).encode("utf-8")
stdout.buffer.write(to_print)
stdout.flush()
if __name__ == "__main__":
main()
我注册了一个芹菜任务。在任务中,我使用子流程调用worker.py
@celery.task(name="tasks.runner", bind=True)
def run_task(self):
proc = subprocess.Popen(["python", "worker.py"], bufsize=1, stdout=subprocess.PIPE)
run_task_pid = os.getpid()
update_progress(self, proc, run_task_pid)
print("I am at the last line of run_task")
def update_progress(self, proc, run_task_pid):
while True:
print('in the loop')
line = proc.stdout.readline()
# I first notice the problem from here, I found it never breaks
# proc.poll() is always None suggesting proc never be terminated
if line == "" and proc.poll() is not None:
break
linestr = line.decode("utf-8")
if "progress:" in linestr:
progress = int(linestr.strip().split(":")[-1])
self.update_state(state="PROGRESS", meta={"progress": progress,
"subprocess_pid": proc.pid,
"run_task_pid": run_task_pid,
"description": ""})
# I use this line to break because proc.poll() not work here
if progress == 100:
self.update_state(state="SUCCESS", meta={ "progress": progress,
"subprocess_pid": proc.pid,
"run_task_pid": run_task_pid,
"description": ""})
break
time.sleep(0.5)
我通过烧瓶开始celery任务,只是一个简单的简单回调,并且我有一个小的Web服务来告诉我进程ID。无论如何,芹菜任务开始像这样
def worker():
task = send_task('tasks.runner')
当celery任务已经完成时,从确认的角度来看还不错,执行run_task中的最后一行代码,并且任务本身“成功”,正如Celery告诉我的:
$ I am at the last line of run_task
$ Task tasks.runner[0a0ff60c-ab09-4279-91f8-f9dd5010a49c] succeeded in 11.078552599996328s: None
但是,如果我看一下ps,subprocess.pid仍然以(python)命令存在,这解释了为什么proc.poll()总是提示子进程仍在运行...
PID TTY TIME CMD
5160 ttys002 0:00.00 (python)
这看起来像一个僵尸进程,但不占用资源。这非常令人讨厌,因为即使这是一个伪造的僵尸,子进程proc也没有干净的退出信号(在代码中,我必须明确地说,当progress == 100时我们可以中断,否则您可以告诉芹菜工作实际上将运行永远,因为它永不中断)。而且我也不知道是否还有其他问题,因为子进程并没有真正终止。
我想知道是否有人知道这个问题? Celery使用子流程是否会因为它创建一些奇怪的子流程依赖项而出现一些已知问题?我发现我几乎没有办法手动杀死它,唯一的办法是我停止芹菜本身。我做错什么了吗?谢谢您的帮助