Lab
Lab

Reputation: 226

Using asyncio to run command in a time interval and terminate it afterwards

I'm excuting a shell command by os.system(). I planned to run it for 1 second, then terminate it if time exceeded. Here's what I tried instead as a test.

import os, time, asyncio

async def cmd():
    os.system("my.py > my.txt") # this processes longer than 1 second

@asyncio.coroutine
def run():
    try:
        yield from asyncio.wait_for(cmd(), 1) # this should excute for more than 1 sec, and hence raise TimeoutError
        print("A")
    except asyncio.TimeoutError:
        os.system("killall python")
        print("B")
    
asyncio.run(run())

But the result was always "A" and the txt was written by my.py.

I also tried:

import os, time, asyncio

async def cmd():
    os.system("my.py > my.txt") # longer than 1 sec

async def run():
    try:
        await asyncio.wait_for(cmd(), 1) # should raise TimeoutError
        print("A")
    except asyncio.TimeoutError:
        os.system("killall python")
        print("B")
    
asyncio.run(run())

results in the same output. What's wrong with the code? I'm pretty new to asyncio and never used it before. Thanks in advance. It's most likely not the problem that wait_for does not stop the cast automatically, since I have a killall python in the second part and in fact, the wait_for function never raises the timeout error, that's the problem.

Upvotes: 2

Views: 2043

Answers (1)

Amadan
Amadan

Reputation: 198304

os.system is a blocking operation, so it doesn't play well with asyncio. Whenever you use asyncio, all "longer" operations need to be asynchronous, or you lose all benefits of going with asyncio in the first place.

import asyncio

async def cmd():
    try:
        proc = await asyncio.create_subprocess_shell(
            "sleep 2 && date > my.txt",
            shell=True,
            stdout=asyncio.subprocess.DEVNULL,
            stderr=asyncio.subprocess.DEVNULL)
        await proc.communicate()

    except asyncio.CancelledError:
        proc.terminate()
        print("X")

async def run():
    try:
        await asyncio.wait_for(cmd(), 1)
        print("A")

    except asyncio.TimeoutError:
        print("B")

asyncio.run(run())

As @dim mentions in comments, asyncio.create_subprocess_exec will launch an external command asynchronously. wait_for will actually raise two exceptions: TimeoutError in the awaiting code, and CancelledError in the awaited code. We can use the latter to realise the operation was cancelled, and to terminate or kill our subprocess (as this is not done automatically).

Upvotes: 2

Related Questions