我已经使用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
。这在发送信号时效果更好!
答案 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()
,如果stdout
或stderr
被某些输出填充,您的流程可能会阻止。
您正在尝试创建用于控制播放器的UI。为此,将解决方案分为前端和后端是切实可行的。后端将作为玩家控制器,并提供像
这样的方法要集成前端和后端,可以使用多个选项,其中一个选项为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