comparison unix.py @ 118:16fe21d6582e

Fixed a few race conditions in unix.call triggered by very fast testees Effects included misrecording of wall-clock time usage and crashes.
author Oleg Oshmyan <chortos@inbox.lv>
date Tue, 12 Apr 2011 22:25:18 +0300
parents 218b8c28549c
children 0b265fe9c81f
comparison
equal deleted inserted replaced
117:6bb59a011bcb 118:16fe21d6582e
43 tty.setcbreak(sys.stdin.fileno()) 43 tty.setcbreak(sys.stdin.fileno())
44 def pause(): 44 def pause():
45 sys.stdin.read(1) 45 sys.stdin.read(1)
46 46
47 try: 47 try:
48 from signal import SIGCHLD, signal, SIG_DFL 48 from signal import SIGCHLD, SIG_DFL, signal, set_wakeup_fd
49 from select import select, error as SelectError 49 from select import select, error as SelectError
50 from errno import EINTR 50 from errno import EAGAIN, EINTR
51 from fcntl import fcntl, F_SETFD, F_GETFD 51 from fcntl import fcntl, F_SETFD, F_GETFD, F_SETFL, F_GETFL
52 from os import O_NONBLOCK
52 try: 53 try:
53 import cPickle as pickle 54 import cPickle as pickle
54 except ImportError: 55 except ImportError:
55 import pickle 56 import pickle
56 except ImportError: 57 except ImportError:
104 from resource import RLIMIT_VMEM 105 from resource import RLIMIT_VMEM
105 except ImportError: 106 except ImportError:
106 setrlimit = None 107 setrlimit = None
107 108
108 # Make SIGCHLD interrupt sleep() and select() 109 # Make SIGCHLD interrupt sleep() and select()
110 sigchld_pipe_read, sigchld_pipe_write = os.pipe()
111 fcntl(sigchld_pipe_read, F_SETFL,
112 fcntl(sigchld_pipe_read, F_GETFL) | O_NONBLOCK)
113 fcntl(sigchld_pipe_write, F_SETFL,
114 fcntl(sigchld_pipe_write, F_GETFL) | O_NONBLOCK)
109 def bury_child(signum, frame): 115 def bury_child(signum, frame):
110 try: 116 try:
111 bury_child.case.time_stopped = clock() 117 bury_child.case.time_stopped = clock()
112 except Exception: 118 except Exception:
113 pass 119 pass
114 signal(SIGCHLD, bury_child) 120 signal(SIGCHLD, bury_child)
121 set_wakeup_fd(sigchld_pipe_write)
115 class SignalIgnorer(object): 122 class SignalIgnorer(object):
116 def __enter__(self): 123 def __enter__(self):
117 signal(SIGCHLD, SIG_DFL) 124 signal(SIGCHLD, SIG_DFL)
118 def __exit__(self, exc_type, exc_value, traceback): 125 def __exit__(self, exc_type, exc_value, traceback):
119 signal(SIGCHLD, bury_child) 126 signal(SIGCHLD, bury_child)
135 # passing between the calls to fork and exec to be counted in 142 # passing between the calls to fork and exec to be counted in
136 os.write(write, pickle.dumps((clock(), cpuclock()), 1)) 143 os.write(write, pickle.dumps((clock(), cpuclock()), 1))
137 kwargs['preexec_fn'] = preexec_fn 144 kwargs['preexec_fn'] = preexec_fn
138 old_rusage = getrusage(RUSAGE_CHILDREN) 145 old_rusage = getrusage(RUSAGE_CHILDREN)
139 last_rusage = None 146 last_rusage = None
147 while True:
148 try:
149 os.read(sigchld_pipe_read, 512)
150 except OSError:
151 if sys.exc_info()[1].errno == EAGAIN:
152 break
153 else:
154 raise
140 try: 155 try:
141 case.process = Popen(*args, **kwargs) 156 case.process = Popen(*args, **kwargs)
142 except OSError: 157 except OSError:
143 os.close(read) 158 os.close(read)
144 raise testcases.CannotStartTestee(sys.exc_info()[1]) 159 raise testcases.CannotStartTestee(sys.exc_info()[1])
145 finally: 160 finally:
146 os.close(write) 161 os.close(write)
147 try: 162 try:
148 if not catch_escape: 163 if not catch_escape:
149 if case.maxwalltime: 164 if case.maxwalltime:
150 time.sleep(case.maxwalltime) 165 try:
166 select((sigchld_pipe_read,), (), (), case.maxwalltime)
167 except SelectError:
168 if sys.exc_info()[1].args[0] != EINTR:
169 raise
151 if case.process.poll() is None: 170 if case.process.poll() is None:
152 raise testcases.WallTimeLimitExceeded 171 raise testcases.WallTimeLimitExceeded
153 else: 172 else:
154 case.process.wait() 173 case.process.wait()
155 else: 174 else:
156 if not case.maxwalltime: 175 if not case.maxwalltime:
157 try: 176 try:
158 while case.process.poll() is None: 177 while case.process.poll() is None:
159 if select((sys.stdin,), (), ())[0]: 178 s = select((sys.stdin, sigchld_pipe_read), (), ())
160 if sys.stdin.read(1) == '\33': 179 if (sigchld_pipe_read not in s[0] and
161 raise testcases.CanceledByUser 180 sys.stdin.read(1) == '\33'):
162 except SelectError: 181 raise testcases.CanceledByUser
182 except (SelectError, IOError):
163 if sys.exc_info()[1].args[0] != EINTR: 183 if sys.exc_info()[1].args[0] != EINTR:
164 raise 184 raise
165 else: 185 else:
166 case.process.poll() 186 case.process.poll()
167 else: 187 else:
168 time_end = clock() + case.maxwalltime 188 time_end = clock() + case.maxwalltime
169 try: 189 try:
170 while case.process.poll() is None: 190 while case.process.poll() is None:
171 remaining = time_end - clock() 191 remaining = time_end - clock()
172 if remaining > 0: 192 if remaining > 0:
173 if select((sys.stdin,), (), (), remaining)[0]: 193 s = select((sys.stdin, sigchld_pipe_read),
174 if sys.stdin.read(1) == '\33': 194 (), (), remaining)
175 raise testcases.CanceledByUser 195 if (sigchld_pipe_read not in s[0] and
196 sys.stdin.read(1) == '\33'):
197 raise testcases.CanceledByUser
176 else: 198 else:
177 raise testcases.WallTimeLimitExceeded 199 raise testcases.WallTimeLimitExceeded
178 except SelectError: 200 except (SelectError, IOError):
179 if sys.exc_info()[1].args[0] != EINTR: 201 if sys.exc_info()[1].args[0] != EINTR:
180 raise 202 raise
181 else: 203 else:
182 case.process.poll() 204 case.process.poll()
183 finally: 205 finally: