Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Shell hangs after killing subprocess

I know there are a bunch of similar questions on SO like this one or this one and maybe a couple more, but none of them seem to apply in my particular situation. My lack of understanding on how subprocess.Popen() works doesn't help either.

What i want to achieve is: launch a subprocess (a command line radio player) that also outputs data to the terminal and can also receive input -- wait for a while -- terminate the subprocess -- exit the shell. I am running python 2.7 on OSX 10.9

Case 1. This launches the radio player (but audio only!), terminates the process, exits.

import subprocess
import time

p = subprocess.Popen(['/bin/bash', '-c', 'mplayer http://173.239.76.147:8090'],
                     stdin=subprocess.PIPE, stdout=subprocess.PIPE, shell=False,
                     stderr=subprocess.STDOUT)
time.sleep(5)
p.kill()

Case 2. This launches the radio player, outputs information like radio name, song, bitrate, etc and also accepts input. It terminates the subprocess but it never exists the shell and the terminal becomes unusable even after using 'Ctrl-C'.

p = subprocess.Popen(['/bin/bash', '-c', 'mplayer http://173.239.76.147:8090'],
                     shell=False)
time.sleep(5)
p.kill()

Any ideas on how to do it? I was even thinking at the possibility of opening a slave-shell for the subprocess if there is no other choice (of course it is also something that I don't have a clue about). Thanks!

like image 624
skamsie Avatar asked Apr 06 '14 20:04

skamsie


2 Answers

It seems like mplayer uses the curses library and when kill()ing it or terminate()ing it, for some reason, it doesn't clean the library state correctly.

To restore the terminal state you can use the reset command.

Demo:

import subprocess, time

p = subprocess.Popen(['mplayer', 'http://173.239.76.147:8090'])
time.sleep(5)
p.terminate()
p.wait()  # important!

subprocess.Popen(['reset']).wait()

print('Hello, World!')

In principle it should be possible to use stty sane too, but it doesn't work well for me.


As Sebastian points out, there was a missing wait() call in the above code (now added). With this wait() call and using terminate() the terminal doesn't get messed up (and so there shouldn't be any need for reset).

Without the wait() I sometimes do have problems of mixed output between the python process and mplayer.

Also, a solution specific to mplayer, as pointed out by Sebastian, is to send a q to the stdin of mplayer to quit it.

I leave the code that uses reset because it works with any program that uses the curses library, whether it correctly tears down the library or not, and thus it might be useful in other situations where a clean exit isn't possible.

like image 112
Bakuriu Avatar answered Oct 02 '22 08:10

Bakuriu


What i want to achieve is: launch a subprocess (a command line radio player) that also outputs data to the terminal and can also receive input -- wait for a while -- terminate the subprocess -- exit the shell. I am running python 2.7 on OSX 10.9

On my system, mplayer accepts keyboard commands e.g., q to stop playing and quit:

#!/usr/bin/env python
import shlex
import time
from subprocess import Popen, PIPE

cmd = shlex.split("mplayer http://www.swissradio.ch/streams/6034.m3u")
p = Popen(cmd, stdin=PIPE)
time.sleep(5)
p.communicate(b'q')

It starts mplayer tuned to public domain classical; waits 5 seconds; asks mplayer to quit and waits for it to exit. The output is going to terminal (the same place where the python script's output goes).

I've also tried p.kill(), p.terminate(), p.send_signal(signal.SIGINT) (Ctrl + C). p.kill() creates the impression that the process hangs. Possible explanation: p.kill() leaves some pipes open e.g., if stdout=PIPE then your Python script might hang at p.stdout.read() i.e., it kills the parent mplayer process but there might be a child process that holds the pipes open. Nothing hangs with p.terminate(), p.send_signal(signal.SIGINT) -- mplayer exits in an orderly manner. None of the variants I've tried require reset.


how should I go about having both input from Python and keyboard? Do I need two different subprocesses and how to redirect the keyboard input to PIPE?

It would be much simpler just to drop stdin=PIPE and call p.terminate(); p.wait() instead of p.communicate(b'q').

If you want to keep stdin=PIPE then the general principle is: read from sys.stdin, write to p.stdin until timeout happens. Given that mplayer expects one letter commands, you need to be able to read one character at at time from sys.stdin. The write part is easy: p.stdin.write(c) (set bufsize=0 to avoid buffering on Python side. mplayer doesn't buffer its stdin so you don't need to worry about it).

You don't need two different subprocesses. To implement timeout, you could use threading.Timer(5, p.stdin.write, [b'q']).start() or select.select on sys.stdin with timeout.

I guess something using the good old raw_input has nothing to do with it, or?

raw_input() is not suitable for mplayer because it reads the full lines but mplayer expects one character at a time.

like image 40
jfs Avatar answered Oct 02 '22 09:10

jfs