Python失去控制的子进程?

13
我正在使用一个商业应用程序,它使用Python作为其脚本API的一部分。其中提供的一个函数被称为App.run()。当调用此函数时,它会启动一个新的Java进程来执行其余的操作。(不幸的是,我不知道它在幕后做了什么,因为提供的Python模块是 .pyc 文件,并且许多Python函数是SWIG生成的)。
我遇到的问题是,我正在将App.run()调用构建到一个较大的Python应用程序中,需要执行一些保证的清理代码(关闭数据库等)。不幸的是,如果子进程被Ctrl+C中断,则会中止并返回到命令行,而不将控制返回给主Python程序。因此,我的清理代码永远不会执行。
到目前为止,我已经尝试过:
  1. 注册一个函数atexit……不起作用
  2. 将清理放在类__del__析构函数中……不起作用。(App.run()在类内部)
  3. 在主Python应用程序中创建一个Ctrl+C信号处理程序……不起作用
  4. App.run()放入一个线程中……在Ctrl+C后导致内存错误。
  5. App.run()放入一个进程中(来自 multiprocessing)……不起作用
有什么想法是发生了什么?

1
欢迎来到 Stack Overflow。我已经编辑了您的问题,以使代码更加清晰可见。 - user647772
2
这在运行Windows还是Linux? - tMC
3个回答

4
这只是一个简要概述 - 但是类似于这样的东西吗?
import os

cpid = os.fork()
if not cpid:
    # change stdio handles etc
    os.setsid() # Probably not needed
    App.run()
    os._exit(0)

os.waitpid(cpid)
# clean up here

(os.fork仅适用于*nix)

同样的想法可以用操作系统无关的方式使用subprocess实现。这个想法是在子进程中运行App.run(),然后等待子进程退出;无论子进程如何死亡。在posix上,你还可以陷阱SIGCHLD(子进程死亡)。我不是一个Windows专家,所以如果适用且subprocess不起作用,其他人将不得不在这里发表意见。

在调用App.run()之后,我很好奇进程树的情况。它可能正在运行一个exec并占用python进程空间。如果发生这种情况,创建一个子进程是我能想到的唯一方法来捕获它。


这似乎接近可能有效的东西。当我尝试时,我可以按Ctrl-C并进入清理代码,但App.Run()进程不断重新启动!然而,我对*nix命令不够熟悉,不知道发生了什么。 - jasonm76
如果您的Python代码中没有放置任何循环,则循环必须在App.run()代码中。当应用程序正在运行时,调用它的Python进程是否仍然存在?ps fax将打印进程树。如果不存在,则应用程序代码调用了exec并消耗了您的Python进程空间。如果发生这种情况,除发送信号外,您几乎没有任何救济或影响应用程序所做的事情。 - tMC
ps fax 命令非常有用。据我所知,如果我不使用 setsid,当我按下 Ctrl-C 时,App 对象会生成消息,表明它在中止之前接收到了两个停止信号。当我使用 setsid 时,Ctrl-C 会杀死 Python 线程,分叉线程独立运行,但它不能再接收 Ctrl-C - jasonm76

2
如果 try: App.run() finally: cleanup() 无法正常工作,您可以尝试在子进程中运行它:
import sys
from subprocess import call

rc = call([sys.executable, 'path/to/run_app.py'])
cleanup()

如果您的代码以字符串形式存在,则可以使用-c选项,例如:

rc = call([sys.executable, '-c', '''import sys
print(sys.argv)
'''])

您可以使用subprocess来实现@tMC的建议,只需添加preexec_fn=os.setsid参数(注意:不要有()),但我不确定创建进程组如何有所帮助。或者您可以尝试shell=True参数来在单独的shell中运行它。
您也可以尝试使用multiprocessing。
import multiprocessing as mp

if __name__=="__main__":
   p = mp.Process(target=App.run)
   p.start()
   p.join()
   cleanup()

os.setsid 真的只是出于习惯而已,值得考虑在没有它的情况下进行测试。 - tMC
@tMC 请看我上面的评论...似乎setsid确实有所不同。 - jasonm76
我尝试使用动态生成Python脚本并调用subprocess.call方法。但出现了奇怪的问题,即使它的返回代码为0,它也无法运行。但是,如果我从命令行(或Python交互)中运行动态生成的文件,则可以正常工作。这种行为真是奇怪... - jasonm76
@jasonm76:我已经更新了答案,以澄清如何将Python脚本作为子进程运行。 - jfs
太好了!我刚刚在调用命令中加入了一个 try..except KeyboardInterrupt 语句,这样 Ctrl-C 就会优雅地退出子进程,然后执行我的清理代码。谢谢!! - jasonm76

1
你能否将App.Run()放在Try/Catch中呢?
就像这样:
try:
    App.Run()
except (KeyboardInterrupt, SystemExit):
    print "User requested an exit..."
cleanup()

谢谢,我刚试了一下你的代码...但不幸的是和我之前测试的结果一样:立即退出到命令行,没有任何输出。 - jasonm76
糟糕,我以为这个方法会对你有所帮助。你的App.Run()可能在做一些不好的事情,导致Python进程也崩溃了。 - Chris Zeh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接