Timeout on subprocess readline in Python Timeout on subprocess readline in Python python python

Timeout on subprocess readline in Python


Thanks for all the answers!

I found a way to solve my problem by simply using select.poll to peek into standard output.

import select...scan_process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)poll_obj = select.poll()poll_obj.register(scan_process.stdout, select.POLLIN)while(some_criterium and not time_limit):    poll_result = poll_obj.poll(0)    if poll_result:        line = scan_process.stdout.readline()        some_criterium = do_something(line)    update(time_limit)


Here's a portable solution that enforces the timeout for reading a single line using asyncio:

#!/usr/bin/env python3import asyncioimport sysfrom asyncio.subprocess import PIPE, STDOUTasync def run_command(*args, timeout=None):    # Start child process    # NOTE: universal_newlines parameter is not supported    process = await asyncio.create_subprocess_exec(*args,            stdout=PIPE, stderr=STDOUT)    # Read line (sequence of bytes ending with b'\n') asynchronously    while True:        try:            line = await asyncio.wait_for(process.stdout.readline(), timeout)        except asyncio.TimeoutError:            pass        else:            if not line: # EOF                break            elif do_something(line):                continue # While some criterium is satisfied        process.kill() # Timeout or some criterion is not satisfied        break    return await process.wait() # Wait for the child process to exitif sys.platform == "win32":    loop = asyncio.ProactorEventLoop() # For subprocess' pipes on Windows    asyncio.set_event_loop(loop)else:    loop = asyncio.get_event_loop()returncode = loop.run_until_complete(run_command("cmd", "arg 1", "arg 2",                                                 timeout=10))loop.close()


I used something a bit more general in Python (if I remember correctly, also pieced together from Stack Overflow questions, but I cannot recall which ones).

import threadfrom threading import Timerdef run_with_timeout(timeout, default, f, *args, **kwargs):    if not timeout:        return f(*args, **kwargs)    try:        timeout_timer = Timer(timeout, thread.interrupt_main)        timeout_timer.start()        result = f(*args, **kwargs)        return result    except KeyboardInterrupt:        return default    finally:        timeout_timer.cancel()

Be warned, though. This uses an interrupt to stop whatever function you give it. This might not be a good idea for all functions and it also prevents you from closing the program with Ctrl + C during the timeout (i.e. Ctrl + C will be handled as a timeout).

You could use this and call it like:

scan_process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)while(some_criterium):    line = run_with_timeout(timeout, None, scan_process.stdout.readline)    if line is None:        break    else:        some_criterium = do_something(line)

It might be a bit overkill, though. I suspect there is a simpler option for your case that I don't know.