简体   繁体   中英

How to get stdout from subprocess.Popen elegantly with timeout check?

PC System: Ubuntu 18.04
Python version: 3.6.9
When execute command like "adb logcat" by subprocess.Popen, I want to get the stdout and decide whether to stop the subprocess based on the keyword in it. If the keyword does not appear for a long time, i will stop it too. The first attempt is as follows

import time
import subprocess

cmd = "adb logcat"
timeout = 10
adb_shell_pipe = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, universal_newlines=True)
start_time = time.time()
for info in iter(adb_shell_pipe.stdout.readline, ""):
    if "keyword" in info:
        print("Find keyword!")
        # Omit kill process
        break
    if time.time() - start_time > timeout:
        print("Fail!")
        # Omit kill process
        break

The code succeeded in doing what I need, but I found that if there is no next output from the tenth seconds after the subprocess starts, the program will not end with "Fail." until the next output.
I think it's because readline() blocks to read the output. So, i set the stdout to non-block by fcntl like the following code

import os
import time
import fcntl
import subprocess

# Command was replaced to create test scenarios
cmd = "adb shell"
timeout = 10
adb_shell_pipe = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, universal_newlines=True)

fd = adb_shell_pipe.stdout.fileno()
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)

count = 0
start_time = time.time()
while adb_shell_pipe.poll() is None:
    count += 1
    info = adb_shell_pipe.stdout.readline()
    if info and "keyword" in info:
        print("Find keyword!")
        # Omit kill process
        break
    if time.time() - start_time > timeout:
        print("Fail!")
        # Omit kill process
        break
print(count)
# Output
Fail!
4131304

As shown above, the results were as expected. However, it execute readline() 4131304 times in 10 seconds, It may waste resources, and the fcntl module cannot be used on Windows.
So, is there a more elegant, generic way to implement this requirement?

You could maybe in every iteration call an async function which will kill the process after 10 secs, and also in every iteration kill the previous async call, like

killer_coroutine = None
while loop:
   # stuff...
   if killer_coroutine:
      killer_coroutine.cancel()
   killer_coroutine = async_func()

so if the loop stops at some point, the process will get killed. I am not sure if this would work, maybe worth a shot.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM