I believe you can pare your program down to this, if you don't need to provide a whole new pty to the subprocess:
from argparse import ArgumentParser
import os
import signal
import subprocess
import itertools
# your argumentparser stuff goes here
def become_tty_fg():
os.setpgrp()
hdlr = signal.signal(signal.SIGTTOU, signal.SIG_IGN)
tty = os.open('/dev/tty', os.O_RDWR)
os.tcsetpgrp(tty, os.getpgrp())
signal.signal(signal.SIGTTOU, hdlr)
if __name__ == "__main__":
args = parser.parse_args()
if args.verbose: print "command is %s" % (args.command)
if args.invert and args.limit==None:
sys.exit("You must define a limit if you have inverted the return code test")
for run_count in itertools.count():
return_code = subprocess.call(args.command, close_fds=True,
preexec_fn=become_tty_fg)
if args.test == True: break
if run_count >= args.limit: break
if args.invert and return_code != 0: break
elif not args.invert and return_code == 0: break
print "Ran command %d times" % (run_count)
The setpgrp()
call creates a new process group in the same session, so that the new process will receive any ctrl-c/ctrl-z/etc from the user, and your retry script won't. Then the tcsetpgrp()
makes the new process group be the foreground one on the controlling tty. The new process gets a SIGTTOU
when that happens (because since the setpgrp()
, it has been in a background process group), which normally would make the process stop, so that's the reason for ignoring SIGTTOU
. We set the SIGTTOU
handler back to whatever it was before, to minimize the chance of the subprocess being confused by an unexpected signal table.
Since the subprocess is now in the foreground group for the tty, its tcgetpgrp() and getpgrp() will be the same, and isatty(1) will be true (assuming the stdout it inherits from retry.py actually is a tty). You don't need to proxy traffic between the subprocess and the tty, which lets you ditch all the select
event handling and fcntl-nonblocking-setting.