changeset 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 6bb59a011bcb
children 0b265fe9c81f
files unix.py
diffstat 1 files changed, 34 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- 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: