Mercurial > ~astiob > upreckon > hgweb
comparison win32.py @ 82:06356af50bf9
Finished testcases reorganization and CPU time limit implementation
We now have:
* Win32-specific code in the win32 module (including bug fixes),
* UNIX-specific and generic code in the unix module,
* a much cleaner testcases module,
* wait4-based resource limits working on Python 3 (this is a bug fix),
* no warning/error reported on non-Win32 when -x is not passed
but standard input does not come from a terminal,
* the maxtime configuration variable replaced with two new variables
named maxcputime and maxwalltime,
* CPU time reported if it can be determined unless an error occurs sooner
than it is determined (e. g. if the wall-clock time limit is exceeded),
* memory limits enforced even if Upreckon's forking already breaks them,
* CPU time limits and private virtual memory limits honoured on Win32,
* CPU time limits honoured on UNIX(-like) platforms supporting wait4
or getrusage,
* address space limits honoured on UNIX(-like) platforms supporting
setrlimit with RLIMIT_AS/RLIMIT_VMEM,
* resident set size limits honoured on UNIX(-like) platforms supporting
wait4.
| author | Oleg Oshmyan <chortos@inbox.lv> |
|---|---|
| date | Wed, 23 Feb 2011 23:35:27 +0000 |
| parents | 24752db487c5 |
| children | 741ae3391b61 |
comparison
equal
deleted
inserted
replaced
| 81:24752db487c5 | 82:06356af50bf9 |
|---|---|
| 1 # Copyright (c) 2011 Chortos-2 <chortos@inbox.lv> | 1 # Copyright (c) 2010-2011 Chortos-2 <chortos@inbox.lv> |
| 2 | 2 |
| 3 from __future__ import division, with_statement | 3 from __future__ import division, with_statement |
| 4 import sys | |
| 4 | 5 |
| 5 try: | 6 try: |
| 6 from compat import * | 7 from compat import * |
| 7 from testcases import (TimeLimitExceeded, MemoryLimitExceeded, | 8 import testcases # mutual import |
| 8 CanceledByUser, CannotStartTestee) | |
| 9 except ImportError: | 9 except ImportError: |
| 10 import __main__ | 10 import __main__ |
| 11 __main__.import_error(sys.exc_info()[1]) | 11 __main__.import_error(sys.exc_info()[1]) |
| 12 | 12 |
| 13 from __main__ import clock | |
| 14 from ctypes import * | 13 from ctypes import * |
| 15 from ctypes.wintypes import * | 14 from ctypes.wintypes import * |
| 15 from msvcrt import getch as pause | |
| 16 from subprocess import Popen | 16 from subprocess import Popen |
| 17 from __main__ import clock | |
| 17 | 18 |
| 18 # Defaults that may be overwritten by values from _subprocess | 19 # Defaults that may be overwritten by values from _subprocess |
| 19 INFINITE = -1 | 20 INFINITE = -1 |
| 20 STD_INPUT_HANDLE = -10 | 21 STD_INPUT_HANDLE = -10 |
| 21 WAIT_OBJECT_0 = 0 | 22 WAIT_OBJECT_0 = 0 |
| 40 __getnewargs__ = lambda self: tuple(self) | 41 __getnewargs__ = lambda self: tuple(self) |
| 41 kernel, user = (property(itemgetter(i)) for i in (0, 1)) | 42 kernel, user = (property(itemgetter(i)) for i in (0, 1)) |
| 42 else: | 43 else: |
| 43 ProcessTimes = namedtuple('ProcessTimes', 'kernel user') | 44 ProcessTimes = namedtuple('ProcessTimes', 'kernel user') |
| 44 | 45 |
| 45 __all__ = 'call', 'kill', 'terminate' | 46 __all__ = 'call', 'kill', 'terminate', 'pause' |
| 46 | 47 |
| 47 | 48 |
| 48 # Automatically convert _subprocess handle objects into low-level HANDLEs | 49 # Automatically convert _subprocess handle objects into low-level HANDLEs |
| 49 # and replicate their functionality for our own use | 50 # and replicate their functionality for our own use |
| 50 try: | 51 try: |
| 313 ('ProcessMemoryLimit', SIZE_T), | 314 ('ProcessMemoryLimit', SIZE_T), |
| 314 ('JobMemoryLimit', SIZE_T), | 315 ('JobMemoryLimit', SIZE_T), |
| 315 ('PeakProcessMemoryUsed', SIZE_T), | 316 ('PeakProcessMemoryUsed', SIZE_T), |
| 316 ('PeakJobMemoryUsed', SIZE_T)) | 317 ('PeakJobMemoryUsed', SIZE_T)) |
| 317 | 318 |
| 318 prototype = WINFUNCTYPE(BOOL, HANDLE, c_int, c_void_p, DWORD) | 319 prototype = WINFUNCTYPE(BOOL, Handle, c_int, c_void_p, DWORD) |
| 319 flags = (1, 'job'), (1, 'infoclass'), (1, 'info'), (1, 'infosize') | 320 flags = (1, 'job'), (1, 'infoclass'), (1, 'info'), (1, 'infosize') |
| 320 try: | 321 try: |
| 321 _setjobinfo = prototype(('SetInformationJobObject',windll.kernel32), flags) | 322 _setjobinfo = prototype(('SetInformationJobObject',windll.kernel32), flags) |
| 322 except AttributeError: | 323 except AttributeError: |
| 323 # Available on 2000 and up, NT line only | 324 # Available on 2000 and up, NT line only |
| 325 else: | 326 else: |
| 326 def errcheck(result, func, args): | 327 def errcheck(result, func, args): |
| 327 if not result: raise WinError() | 328 if not result: raise WinError() |
| 328 _setjobinfo.errcheck = errcheck | 329 _setjobinfo.errcheck = errcheck |
| 329 def SetInformationJobObject(job, infoclass, info): | 330 def SetInformationJobObject(job, infoclass, info): |
| 330 return _setjobinfo(job, infoclass, info, sizeof(info)) | 331 return _setjobinfo(job, infoclass, byref(info), sizeof(info)) |
| 331 | 332 |
| 332 ( | 333 ( |
| 333 JobObjectBasicAccountingInformation, | 334 JobObjectBasicAccountingInformation, |
| 334 JobObjectBasicLimitInformation, | 335 JobObjectBasicLimitInformation, |
| 335 JobObjectBasicProcessIdList, | 336 JobObjectBasicProcessIdList, |
| 414 except WindowsError: | 415 except WindowsError: |
| 415 console_input = False | 416 console_input = False |
| 416 else: | 417 else: |
| 417 console_input = True | 418 console_input = True |
| 418 FlushConsoleInputBuffer(stdin) | 419 FlushConsoleInputBuffer(stdin) |
| 419 | |
| 420 def kill(process): | |
| 421 try: | |
| 422 process.terminate() | |
| 423 except AttributeError: | |
| 424 TerminateProcess(process._handle) | |
| 425 terminate = kill | |
| 426 | 420 |
| 427 def call(*args, **kwargs): | 421 def call(*args, **kwargs): |
| 428 case = kwargs.pop('case') | 422 case = kwargs.pop('case') |
| 429 job = CreateJobObject(None) | 423 job = CreateJobObject(None) |
| 430 flags = 0 | 424 flags = 0 |
| 441 ) | 435 ) |
| 442 SetInformationJobObject(job, JobObjectExtendedLimitInformation, limits) | 436 SetInformationJobObject(job, JobObjectExtendedLimitInformation, limits) |
| 443 try: | 437 try: |
| 444 case.process = Popen(*args, **kwargs) | 438 case.process = Popen(*args, **kwargs) |
| 445 except OSError: | 439 except OSError: |
| 446 raise CannotStartTestee(sys.exc_info()[1]) | 440 raise testcases.CannotStartTestee(sys.exc_info()[1]) |
| 447 case.time_started = clock() | 441 case.time_started = clock() |
| 448 AssignProcessToJobObject(job, case.process._handle) | 442 AssignProcessToJobObject(job, case.process._handle) |
| 449 if not console_input: | 443 if not console_input: |
| 450 if case.maxwalltime: | 444 if case.maxwalltime: |
| 451 if (WaitForSingleObject(case.process._handle, case.maxwalltime) != | 445 if (WaitForSingleObject(case.process._handle, case.maxwalltime) != |
| 452 WAIT_OBJECT_0): | 446 WAIT_OBJECT_0): |
| 453 raise TimeLimitExceeded | 447 raise testcases.WallTimeLimitExceeded |
| 454 else: | 448 else: |
| 455 case.process.wait() | 449 case.process.wait() |
| 456 else: | 450 else: |
| 457 handles = stdin, case.process._handle | 451 handles = stdin, case.process._handle |
| 458 if case.maxwalltime: | 452 if case.maxwalltime: |
| 465 ir = ReadConsoleInput(stdin) | 459 ir = ReadConsoleInput(stdin) |
| 466 if (ir and | 460 if (ir and |
| 467 ir.EventType == 1 and | 461 ir.EventType == 1 and |
| 468 ir.Event.KeyEvent.bKeyDown and | 462 ir.Event.KeyEvent.bKeyDown and |
| 469 ir.Event.KeyEvent.wVirtualKeyCode == 27): | 463 ir.Event.KeyEvent.wVirtualKeyCode == 27): |
| 470 raise CanceledByUser | 464 raise testcases.CanceledByUser |
| 471 else: | 465 else: |
| 472 raise TimeLimitExceeded | 466 raise testcases.WallTimeLimitExceeded |
| 473 else: | 467 else: |
| 474 while case.process.poll() is None: | 468 while case.process.poll() is None: |
| 475 if (WaitForMultipleObjects(handles, False, INFINITE) == | 469 if (WaitForMultipleObjects(handles, False, INFINITE) == |
| 476 WAIT_OBJECT_0): | 470 WAIT_OBJECT_0): |
| 477 ir = ReadConsoleInput(stdin) | 471 ir = ReadConsoleInput(stdin) |
| 478 if (ir and | 472 if (ir and |
| 479 ir.EventType == 1 and | 473 ir.EventType == 1 and |
| 480 ir.Event.KeyEvent.bKeyDown and | 474 ir.Event.KeyEvent.bKeyDown and |
| 481 ir.Event.KeyEvent.wVirtualKeyCode == 27): | 475 ir.Event.KeyEvent.wVirtualKeyCode == 27): |
| 482 raise CanceledByUser | 476 raise testcases.CanceledByUser |
| 483 case.time_stopped = clock() | 477 case.time_stopped = clock() |
| 484 if case.maxcputime and GetProcessTimes: | 478 if GetProcessTimes: |
| 485 try: | 479 try: |
| 486 times = GetProcessTimes(case.process._handle) | 480 times = GetProcessTimes(case.process._handle) |
| 487 except WindowsError: | 481 except WindowsError: |
| 488 pass | 482 pass |
| 489 else: | 483 else: |
| 490 if times.kernel + times.user > case.maxcputime: | 484 time = times.kernel + times.user |
| 491 raise TimeLimitExceeded | 485 case.time_stopped = time |
| 486 case.time_started = 0 | |
| 487 case.time_limit_string = case.cpu_time_limit_string | |
| 488 if case.maxcputime and time > case.maxcputime: | |
| 489 raise testcases.CPUTimeLimitExceeded | |
| 490 if case.maxcputime and case.process.returncode == 1816: | |
| 491 raise testcases.CPUTimeLimitExceeded | |
| 492 if case.maxmemory and case.process.returncode == -0x3ffffffb: | |
| 493 raise testcases.MemoryLimitExceeded | |
| 492 if case.maxmemory and GetProcessMemoryInfo: | 494 if case.maxmemory and GetProcessMemoryInfo: |
| 493 try: | 495 try: |
| 494 counters = GetProcessMemoryInfo(case.process._handle) | 496 counters = GetProcessMemoryInfo(case.process._handle) |
| 495 except WindowsError: | 497 except WindowsError: |
| 496 pass | 498 pass |
| 497 else: | 499 else: |
| 498 if counters.PeakPagefileUsage > case.maxmemory * 1048576: | 500 if counters.PeakPagefileUsage > case.maxmemory * 1048576: |
| 499 raise MemoryLimitExceeded | 501 raise testcases.MemoryLimitExceeded |
| 502 | |
| 503 | |
| 504 def kill(process): | |
| 505 try: | |
| 506 process.terminate() | |
| 507 except AttributeError: | |
| 508 TerminateProcess(process._handle) | |
| 509 terminate = kill |
