# HG changeset patch # User Oleg Oshmyan # Date 1302636318 -10800 # Node ID 16fe21d6582e035938c342a38147251b468d17eb # Parent 6bb59a011bcbc2f5191070523ae32d7952ceded0 Fixed a few race conditions in unix.call triggered by very fast testees Effects included misrecording of wall-clock time usage and crashes. diff -r 6bb59a011bcb -r 16fe21d6582e unix.py --- a/unix.py Sun Apr 10 00:59:40 2011 +0300 +++ b/unix.py Tue Apr 12 22:25:18 2011 +0300 @@ -45,10 +45,11 @@ sys.stdin.read(1) try: - from signal import SIGCHLD, signal, SIG_DFL + from signal import SIGCHLD, SIG_DFL, signal, set_wakeup_fd from select import select, error as SelectError - from errno import EINTR - from fcntl import fcntl, F_SETFD, F_GETFD + from errno import EAGAIN, EINTR + from fcntl import fcntl, F_SETFD, F_GETFD, F_SETFL, F_GETFL + from os import O_NONBLOCK try: import cPickle as pickle except ImportError: @@ -106,12 +107,18 @@ setrlimit = None # Make SIGCHLD interrupt sleep() and select() + sigchld_pipe_read, sigchld_pipe_write = os.pipe() + fcntl(sigchld_pipe_read, F_SETFL, + fcntl(sigchld_pipe_read, F_GETFL) | O_NONBLOCK) + fcntl(sigchld_pipe_write, F_SETFL, + fcntl(sigchld_pipe_write, F_GETFL) | O_NONBLOCK) def bury_child(signum, frame): try: bury_child.case.time_stopped = clock() except Exception: pass signal(SIGCHLD, bury_child) + set_wakeup_fd(sigchld_pipe_write) class SignalIgnorer(object): def __enter__(self): signal(SIGCHLD, SIG_DFL) @@ -137,6 +144,14 @@ kwargs['preexec_fn'] = preexec_fn old_rusage = getrusage(RUSAGE_CHILDREN) last_rusage = None + while True: + try: + os.read(sigchld_pipe_read, 512) + except OSError: + if sys.exc_info()[1].errno == EAGAIN: + break + else: + raise try: case.process = Popen(*args, **kwargs) except OSError: @@ -147,7 +162,11 @@ try: if not catch_escape: if case.maxwalltime: - time.sleep(case.maxwalltime) + try: + select((sigchld_pipe_read,), (), (), case.maxwalltime) + except SelectError: + if sys.exc_info()[1].args[0] != EINTR: + raise if case.process.poll() is None: raise testcases.WallTimeLimitExceeded else: @@ -156,10 +175,11 @@ if not case.maxwalltime: try: while case.process.poll() is None: - if select((sys.stdin,), (), ())[0]: - if sys.stdin.read(1) == '\33': - raise testcases.CanceledByUser - except SelectError: + s = select((sys.stdin, sigchld_pipe_read), (), ()) + if (sigchld_pipe_read not in s[0] and + sys.stdin.read(1) == '\33'): + raise testcases.CanceledByUser + except (SelectError, IOError): if sys.exc_info()[1].args[0] != EINTR: raise else: @@ -170,12 +190,14 @@ while case.process.poll() is None: remaining = time_end - clock() if remaining > 0: - if select((sys.stdin,), (), (), remaining)[0]: - if sys.stdin.read(1) == '\33': - raise testcases.CanceledByUser + s = select((sys.stdin, sigchld_pipe_read), + (), (), remaining) + if (sigchld_pipe_read not in s[0] and + sys.stdin.read(1) == '\33'): + raise testcases.CanceledByUser else: raise testcases.WallTimeLimitExceeded - except SelectError: + except (SelectError, IOError): if sys.exc_info()[1].args[0] != EINTR: raise else: