Skip to content
Advertisement

coreutils timeout in a bash script not transparent for the application

I have an issue with executing application via /usr/bin/timeout in a bash script. In this specific case this is a simple python fabric script (fabric version 1.14) In order to install this version of fabric library run: pip install “fabric<2” There is no reproduction with new fabric 2.x.

Shell script causing issue:

[root@testhost:~ ] $ cat testNOK.sh
#!/bin/bash
timeout 10 ./test.py
echo "RETCODE=$?"
[root@testhost:~ ] $ ./testNOK.sh
[localhost] run: echo Hello!
RETCODE=124
[root@testhost:~ ] $

Similar script (without timeout) working fine

[root@testhost:~ ] $ cat testOK.sh
#!/bin/bash
./test.py
echo "RETCODE=$?"
[root@testhost:~ ] $ ./testOK.sh
[localhost] run: echo Hello!
[localhost] out: Hello!
[localhost] out:

RETCODE=0
[root@testhost:~ ] $

Manual execution from bash commandline with timeout working fine:

[root@testhost:~ ] $ timeout 10 ./test.py && echo "RETCODE=$?"
[localhost] run: echo Hello!
[localhost] out: Hello!
[localhost] out:

RETCODE=0
[root@testhost:~ ] $

Python2.7 test.py script

[root@testhost:~ ] $ cat test.py
#!/usr/bin/python
from fabric.api import run, settings

with settings(host_string='localhost', user='root', password='XXXXX'):
    run('echo Hello!')
[root@testhost:~ ] $

I have observed the same behavior on different Linux distributions.

Now the question is why application executed via timeout within bash script behaves in a different way and what would be the best solution to this issue?

Advertisement

Answer

You need to invoke timeout with the --foreground option:

timeout --foreground ./test.py

This is only required if the timeout command is not executed from an interactive shell (that is, if it’s executed from a script file).

Quoting from the timeout info page:

‘--foreground’
     Don’t create a separate background program group, so that the
     managed COMMAND can use the foreground TTY normally.  This is
     needed to support timing out commands not started directly from an
     interactive shell, in two situations.
       1. COMMAND is interactive and needs to read from the terminal for
          example
       2. the user wants to support sending signals directly to COMMAND
          from the terminal (like Ctrl-C for example)

What’s actually going on in this case is that fabric (or something invokes) is calling tcsetattr to turn terminal echo off. I don’t know why, but I suppose it has something to do with the process used to (not) collect the user password. (I just saw it in an strace; I made no attempt to find the call.) Attempting to change tty configuration from a background process will cause the process to block until it regains control of the tty, and that’s what’s happening.

It doesn’t happen when timeout is not used because bash doesn’t create a background program group. I suppose that fabric 2 avoids the call to tcsetattr.

You could probably also avoid the issue by avoiding password-based SSH authentication but I didn’t try that.

You can also avoid the problem by redirecting stdin to /dev/null (either in the timeout command or in the invocation of the shell script.) If you don’t need to forward stdin to the remote command (and you probably don’t), that might also be useful.

User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement