themuffinman
themuffinman

Reputation: 51

How to get asynchronous input AND output using subprocess.POPEN

I've been working on this for a few hours and haven't been able to come up with a good solution. A little background, I'm running a password cracking program that's closed source from the command line but have to constantly pause it when my gpu temperature gets too hot.

I do other manipulations in python with this program so that's the language I'd prefer. Anyways, the password program gives periodic updates on how well it's doing, the gpu temperature, etc. and allows me to pause it at any time.

I'm getting the temperature fine but because of blocking issues I'm guessing I can't send the pause command. It's not doing anything at least. I've seen several examples of threading the output, but haven't seen something that that uses threading input and output without causing any issues.

I mean for all I know this could be impossible under current POPEN constraints but would appreciate some direction.

popen = Popen(command, stdout=PIPE, stdin=PIPE, shell=True)
lines_iterator = iter(popen.stdout.readline, b"")
while 1:
    for line in lines_iterator:
        cleanLine = line.replace("\n", "")
        p = re.compile('[0-9][0-9]c Temp')
        m = p.search(cleanLine)
        print cleanLine
        if m:
            temperature = m.group(0)
            if int(temperature[:2]) > 80:
                overheating = True
                print "overheating"
        if overheating:
            if "[s]tatus [p]ause [r]esume [b]ypass [q]uit" in line:

                #It's not doing anything right here, it just continues
                print popen.communicate("p")[0]

This is the gist of my code. It's still kind of through the hacky phase so I know that it might not be following best coding practices.

Upvotes: 4

Views: 2797

Answers (2)

jfs
jfs

Reputation: 414079

A simple portable solution is to use threads here. It is enough if there are no block buffering issues.

To read output and stop input if overheating is detected (not tested):

#!/usr/bin/env python
from subprocess import Popen, PIPE, CalledProcessError
from threading import Event, Thread

def detect_overheating(pipe, overheating):
    with pipe: # read output here
        for line in iter(pipe.readline, ''): 
            if detected_overheating(line.rstrip('\n')):
                overheating.set()    # overheating
            elif paused: #XXX global
                overheating.clear()  # no longer overheating

process = Popen(args, stdout=PIPE, stdin=PIPE, bufsize=1,
                universal_newlines=True) # enable text mode
overheating = Event() 
t = Thread(target=detect_overheating, args=[process.stdout, overheating])
t.daemon = True # close pipe if the process dies
t.start()
paused = False
with process.stdin: # write input here
    while process.poll() is None:
        if overheating.wait(1): # Python 2.7+
            # overheating
            if not paused:
                process.stdin.write('p\n') # pause
                process.stdin.flush()
                paused = True
        elif paused: # no longer overheating
            pass #XXX unpause here
            paused = False
if process.wait() != 0: # non-zero exit status may indicate failure
    raise CalledProcessError(process.returncode, args)

Upvotes: 1

gbs
gbs

Reputation: 1325

EDIT: Sorry, I was confused in the initial answer about the scope of overheating. I deleted the first part of my answer since it's not relevant anymore.

communicate will wait for the process to exit so it might not be what you're looking for in this case. If you want the process to keep going you can use something like popen.stdin.write("p"). You might also need to send a "\n" along if that's required by your process.

Also, if you're OK with an extra dependency you might be interested in the pexpect module that was designed to control interactive processes.

Upvotes: 1

Related Questions