changeset 77:69eadc60f4e2

Memory limit is now applied to the RSS when os.wait4 is available
author Oleg Oshmyan <chortos@inbox.lv>
date Thu, 13 Jan 2011 19:47:35 +0200
parents 0e5ae28e0b2b
children d46bd7ee3e69
files problem.py testcases.py
diffstat 2 files changed, 37 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/problem.py	Sat Jan 08 16:03:35 2011 +0200
+++ b/problem.py	Thu Jan 13 19:47:35 2011 +0200
@@ -1,5 +1,5 @@
 #! /usr/bin/env python
-# Copyright (c) 2010 Chortos-2 <chortos@inbox.lv>
+# Copyright (c) 2010-2011 Chortos-2 <chortos@inbox.lv>
 
 from __future__ import division, with_statement
 
@@ -143,6 +143,8 @@
 					verdict = 'canceled by the user'
 				except testcases.TimeLimitExceeded:
 					verdict = 'time limit exceeded'
+				except testcases.MemoryLimitExceeded:
+					verdict = 'memory limit exceeded'
 				except testcases.WrongAnswer:
 					e = sys.exc_info()[1]
 					if e.comment:
--- a/testcases.py	Sat Jan 08 16:03:35 2011 +0200
+++ b/testcases.py	Thu Jan 13 19:47:35 2011 +0200
@@ -1,5 +1,5 @@
 #! /usr/bin/env python
-# Copyright (c) 2010 Chortos-2 <chortos@inbox.lv>
+# Copyright (c) 2010-2011 Chortos-2 <chortos@inbox.lv>
 
 # TODO: copy the ansfile if not options.erase even if no validator is used
 
@@ -272,6 +272,32 @@
 		else:
 			return call_real(*args, **kwargs)
 
+# Emulate memory limits on platforms compatible with 4.3BSD but not XSI
+# I say 'emulate' because the OS will allow excessive memory usage
+# anyway; Upreckon will just treat the test case as not passed.
+# To do this, we not only require os.wait4 to be present but also
+# assume things about the implementation of subprocess.Popen.
+try:
+	def waitpid_emu(pid, options, _wait4=os.wait4):
+		global last_rusage
+		pid, status, last_rusage = _wait4(pid, options)
+		return pid, status
+	_waitpid = os.waitpid
+	os.waitpid = waitpid_emu
+	try:
+		defaults = Popen._internal_poll.__func__.__defaults__
+	except AttributeError:
+		# Python 2.5
+		defaults = Popen._internal_poll.im_func.func_defaults
+	i = defaults.index(_waitpid)
+	defaults = defaults[:i] + (waitpid_emu,) + defaults[i+1:]
+	try:
+		Popen._internal_poll.__func__.__defaults__ = defaults
+	except AttributeError:
+		pass
+	Popen._internal_poll.im_func.func_defaults = defaults
+except (AttributeError, ValueError):
+	pass
 
 __all__ = ('TestCase', 'load_problem', 'TestCaseNotPassed',
            'TimeLimitExceeded', 'CanceledByUser', 'WrongAnswer',
@@ -285,6 +311,7 @@
 
 class TestCaseNotPassed(Exception): __slots__ = ()
 class TimeLimitExceeded(TestCaseNotPassed): __slots__ = ()
+class MemoryLimitExceeded(TestCaseNotPassed): __slots__ = ()
 class CanceledByUser(TestCaseNotPassed): __slots__ = ()
 
 class WrongAnswer(TestCaseNotPassed):
@@ -539,6 +566,8 @@
 					pass
 		case.open_infile()
 		case.time_started = None
+		global last_rusage
+		last_rusage = None
 		if case.problem.config.stdio:
 			if options.erase and not case.validator or not case.problem.config.inname:
 				# TODO: re-use the same file name if possible
@@ -601,6 +630,8 @@
 										time.sleep(.001)
 						if config.globalconf.force_zero_exitcode and case.process.returncode or case.process.returncode < 0:
 							raise NonZeroExitCode(case.process.returncode)
+						if case.maxmemory and last_rusage and last_rusage.ru_maxrss > case.maxmemory * (1024 if sys.platform != 'darwin' else 1048576):
+							raise MemoryLimitExceeded
 						callback()
 						case.has_called_back = True
 						outfile.seek(0)
@@ -653,6 +684,8 @@
 							time.sleep(.001)
 			if config.globalconf.force_zero_exitcode and case.process.returncode or case.process.returncode < 0:
 				raise NonZeroExitCode(case.process.returncode)
+			if case.maxmemory and last_rusage and last_rusage.ru_maxrss > case.maxmemory * (1024 if sys.platform != 'darwin' else 1048576):
+				raise MemoryLimitExceeded
 			callback()
 			case.has_called_back = True
 			with open(case.problem.config.outname, 'rU') as output: