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.