How to send kill signal after x seconds when running commands expecting interaction?

78 views
Skip to first unread message

Robert Smith

unread,
Sep 28, 2015, 3:24:58 AM9/28/15
to sarge
Is there some way to prevent waiting indefinitely after running something like sarge.run('vim', stdout=sarge.Capture(), stderr=sarge.Capture())? 

I tried adding the parameter timeout to the Capture instance (sarge.run('vim', stdout=sarge.Capture(timeout=2), stderr=sarge.Capture(timeout=2))), but it is not working.

Thanks

Vinay Sajip

unread,
Sep 28, 2015, 3:47:17 AM9/28/15
to sarge
The timeout passed to Capture relates to I/O only, and not to the termination of the spawned process. For the latter, you can call run with async=True, which should return from the call straight away with a Pipeline instance. This has a commands attribute which is a list of the commands run in the pipeline, and each command has methods poll, terminate and kill that you can invoke whenever you like if you want to check the status of a spawned child process or want to terminate it. For example:

>>> import time, sarge
>>> p = sarge.run('python -c "import time; time.sleep(1000)"', async=True)
>>> cmd = p.commands[0]
>>> time.sleep(1)
>>> cmd.poll()
>>> cmd.terminate()
>>> cmd.poll()
-15
>>> 

The first cmd.poll() doesn't print anything (because the result is None - the command is still running) but after the cmd.terminate() call, you see from the second cmd.poll() that the process exited with an error (because it was terminated).

Regards,

Vinay Sajip

Robert Smith

unread,
Sep 28, 2015, 3:25:45 PM9/28/15
to sarge
Thank you. Yes, that prevents blocking but there are a couple of scenarios when this doesn't work as expected:

1. Run p = sarge.run('vim', async=True) in the python REPL. After a few seconds, this replaces the prompt and shows the welcome message from vim. After that, the terminal becomes unstable. Would it be possible to tell sarge not to try to interact with vim (or any other program that requires complex interactions)? 

2. Run p = sarge.run('vim', async=True) in a python script. This is a bit brittle because sometimes p doesn't contain the Pipeline instance, but I'm trying to fix that using time.sleep  in the following way (wait_timeout is taken from here:

import sarge
import time

def wait_timeout(proc, seconds):
    """Wait for a process to finish, or raise exception after timeout"""
    start = time.time()
    end = start + seconds
    interval = min(seconds / 1000.0, .25)

    while True:
        result = proc.poll()
        if result is not None:
            return result
        if time.time() >= end:
            raise RuntimeError("Process timed out")
        time.sleep(interval)

p = sarge.run('vim', async=True)
time.sleep(0.5)
cmd = p.commands[0]

try:
    wait_timeout(cmd, 2)
except RuntimeError:
    cmd.terminate()

This again shows vim for a moment and then kills it. In this case, I would also like to prevent vim from showing in the first place, although it is not as pressing as in the first case.

Regards

Vinay Sajip

unread,
Sep 28, 2015, 4:50:14 PM9/28/15
to sarge
Handling terminal interactions in vim (or similar) is not in scope for sarge. It is not a great idea to run an interactive program like vim from a program which might also want to interact with the terminal (such as the Python REPL). There is really no way around this, and it is unrelated to sarge (you would get the same effect if you used subprocess).

In your second example - are you sure p does not contain the Pipeline instance? In that case, what does it contain?

Regards,

Vinay Sajip

Robert Smith

unread,
Sep 28, 2015, 5:40:52 PM9/28/15
to sarge
I understand that. That's why I'm trying to prevent interactive programs from being executed (in both cases).

Regarding your question, p contains the Pipeline when I include the sleep statement. Without it, p is an empty list. I haven't tested the second example extensively to know if that time.sleep is enough to ensure that p always contains the pipeline. I suppose I'm asking for something like sarge.run('vim', tty=False) to tell sarge "ignore interactions and return control to the user". However, I believe the current behavior won't cause major issues.

Vinay Sajip

unread,
Sep 28, 2015, 6:08:44 PM9/28/15
to sarge
The code for run is 

    async = kwargs.pop('async', False)
    if async:
        p = Pipeline(cmd, **kwargs)
        p.run(input=input, async=True)
    else:
        with Pipeline(cmd, **kwargs) as p:
            p.run(input=input, async=async)
    return p

I find it hard to see how that code could return anything other than a Pipeline instance.

Regards,

Vinay Sajip

Robert Smith

unread,
Sep 28, 2015, 9:44:19 PM9/28/15
to sarge
I don't know why it outputs an empty list, but I think the distinction is that p.commands outputs an empty list, not the pipeline instance:


Is that more likely to happen? It doesn't happen every time, though.

Vinay Sajip

unread,
Sep 29, 2015, 2:42:53 AM9/29/15
to sarge
Ok, that's because when running async, it takes some finite time for the commands in the pipeline to get set up, which will populate the commands attribute of the Pipeline instance. That is not unexpected. You are still getting the Pipeline instance back from the run call.

Regards,

Vinay Sajip

Robert Smith

unread,
Sep 29, 2015, 7:34:49 PM9/29/15
to sarge
That's okay. I don't this that causes any issues. Thanks. 
Reply all
Reply to author
Forward
0 new messages