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.