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 |