由于子进程,Python应用程序变得无响应

时间:2014-06-01 20:09:01

标签: python linux raspberry-pi

我已经使用Flask编写了一个Python应用程序,它提供了一个简单的网站,我可以用它来开始在我的Raspberry Pi(微型计算机)上播放流式视频。基本上,该应用程序允许使用我的手机或平板电脑作为遥控器。

我在Mac OS上测试了该应用程序,它运行正常。将它部署到Raspberry Pi后(安装了Debian的Raspbian变体),它可以很好地为网站服务,并且开始播放也可以按预期工作。但是,停止播放失败。

相关代码在此处托管:https://github.com/lcvisser/mlbviewer-remote/blob/master/remote/mlbviewer-remote.py

子进程的启动方式如下:

cmd = 'python2.7 mlbplay.py v=%s j=%s/%s/%s i=t1' % (team, mm, dd, yy)
player = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=sys.argv[1])

这很好用。

在此之后,子进程应该停止:

player.send_signal(signal.SIGINT)
player.communicate()

这个 可以在Mac OS上运行,但是在Raspberry Pi上运行:应用程序挂起,直到子进程(以cmd开头)为止完成了。似乎子进程未发送或未接收到SIGINT

有什么想法吗?


(我也在这里发布了这个问题:https://unix.stackexchange.com/questions/133946/application-becomes-non-responsive-to-requests-on-raspberry-pi因为我不知道这是操作系统问题还是与Python / Flask相关的问题。)

更新 尝试使用下面Jan Vlcinsky建议的player.communicate()(并在最终看到警告here之后)没有帮助。

我正在考虑使用Jan Vlcinsky提出的解决方案,但如果Flask甚至没有收到请求,我也不会认为会收到这个问题。

更新2: 昨天晚上,我很幸运能够找到一个能够准确找出问题的情况。使用相关代码更新了问题。

我觉得Jan Vlcinsky的解决方案只是将问题转移到另一个应用程序,这将使Flask应用程序保持响应,但会让新应用程序挂起。

更新3: 我编辑了问题的原始部分,以删除我现在知道不相关的内容。

更新4:在@shavenwarthog的评论之后,以下信息可能非常相关:

在Mac上,mlbplay.py会启动以下内容:

rmtpdump <some_options_and_url> | mplayer -

SIGINT发送到mlbplay.py时,会终止此管道命令创建的进程组(如果我understood正确)。

在Raspberry Pi上,我使用的是omxplayer,但为了避免更改mlbplay.py的代码(这不是我的代码),我创建了一个名为mplayer的脚本,其中包含以下内容:

#!/bin/bash

MLBTV_PIPE=mlbpipe

if [ ! -p $MLBTV_PIPE ]
then
    mkfifo $MLBTV_PIPE
fi

cat <&0 > $MLBTV_PIPE | omxplayer -o hdmi $MLBTV_PIPE

我现在猜测这最后一行启动了一个新的进程组,该进程组SIGINT信号终止,从而使我的应用程序挂起。如果是这样,我应该以某种方式获得该组的进程组ID以便能够正确终止它。有人可以证实这一点吗?

更新5: omxplayer会处理SIGINT

https://github.com/popcornmix/omxplayer/blob/master/omxplayer.cpp#L131

更新6:事实证明,我的SIGINT以某种方式在命令链的某处转换为SIGTERM。 omxplayer无法正确处理SIGTERM,这似乎是事情一直悬而未决的问题。我通过实现一个shell脚本来解决这个问题,该脚本管理信号并将它们转换为正确的omxplayer命令(排序是Jan建议的蹩脚版本)。

解决方案:问题发生在player.send_signal()。沿命令链未正确处理信号,导致父应用程序挂起。解决方案是为不能很好地处理信号的命令实现包装器。

此外:使用Popen(cmd.split())而非使用shell=True。这在发送信号时效果更好!

2 个答案:

答案 0 :(得分:1)

问题标在以下代码段中:

@app.route('/watch/<year>/<month>/<day>/<home>/<away>/')
def watch(year, month, day, home, away):
    global session
    global watching
    global player

    # Select video stream
    fav = config.get('favorite')
    if fav:
        fav = fav[0] # TODO: handle multiple favorites
        if fav in (home, away):
            # Favorite team is playing
            team = fav
        else:
            # Use stream of home team
            team = home
    else:
        # Use stream of home team
        team = home

    # End session
    session = None

    # Start mlbplay
    mm = '%02i' % int(month)
    dd = '%02i' % int(day)
    yy = str(year)[-2:]
    cmd = 'python2.7 mlbplay.py v=%s j=%s/%s/%s' % (team, mm, dd, yy)
    # problem is here ----->
    player = subprocess.Popen(cmd, shell=True, cwd=sys.argv[1])
    # < ------problem is here

    # Render template
    game = {}
    game['away_code'] = away
    game['away_name'] = TEAMCODES[away][1]
    game['home_code'] = home
    game['home_name'] = TEAMCODES[home][1]
    watching = game
    return flask.render_template('watching.html', game=game)

您正在启动执行shell命令的新进程,但不要等到它完成。您似乎依赖于一个事实,即命令行进程本身是单一的,但您的前端不会处理它并且可以轻松地启动另一个。

另一个问题可能是,您没有致电player.communicate(),如果stdoutstderr被某些输出填充,您的流程可能会阻止。

建议的解决方案 - 从Web应用程序拆分流程控制器

您正在尝试创建用于控制播放器的UI。为此,将解决方案分为前端和后端是切实可行的。后端将作为玩家控制器,并提供像

这样的方法
  • 启动
  • 停止
  • nowPlaying

要集成前端和后端,可以使用多个选项,其中一个选项为zerorpc,如下所示:https://stackoverflow.com/a/23944303/346478

优点是,您可以非常轻松地创建其他前端(如命令行一,甚至是远程前端)。

答案 1 :(得分:0)

另一个难题:proc.terminate() vs send_signal

以下代码分叉'播放器'(在这种情况下只是一个带sleep的shell),然后打印其进程信息。等待片刻,terminate播放器,然后验证该过程不再存在,它已经不再存在。

感谢@Jan Vlcinsky将proc.communicate()添加到代码中。

(我正在运行Linux Mint LMDE,另一个Debian版本。)

# pylint: disable=E1101

import subprocess, time

def show_procs(pid):
    print 'Process Details:'
    subprocess.call(
        'ps -fl {}'.format(pid),
        shell=True,
    )

cmd = '/bin/sleep 123'
player = subprocess.Popen(cmd, shell=True)

print '* player started, PID',player.pid
show_procs(player.pid)

time.sleep(3)

print '\n*killing player'
player.terminate()
player.communicate()

show_procs(player.pid)

输出

* player started, PID 20393
Process Details:
F S UID        PID  PPID  C PRI  NI ADDR SZ WCHAN  STIME TTY        TIME CMD
0 S johnm    20393 20391  0  80   0 -  1110 wait   17:30 pts/4      0:00 /bin/sh -c /bin/sleep 123

*killing player
Process Details:
F S UID        PID  PPID  C PRI  NI ADDR SZ WCHAN  STIME TTY        TIME CMD