def get_execution_wall_clock_time(self): """Return the total time from the start of the sandbox to the conclusion of the task. return (float): total time the sandbox was alive. """ if self.exec_time: return self.exec_time if self.popen_time: self.exec_time = monotonic_time() - self.popen_time return self.exec_time return None
def add_timeout(self, func, plus, seconds, immediately=False): """Registers a function to be called every x seconds. func (function): the function to call. plus (object): additional data to pass to the function. seconds (float): the function will be called every seconds seconds. immediately (bool): if True, func will be called also at the beginning. """ next_timeout = monotonic_time() if not immediately: next_timeout += seconds heapq.heappush(self._timeouts, (next_timeout, seconds, func, plus)) # Wake up the run() cycle self.event.set()
def _trigger(self, maximum=2.0): """Call the timeouts that have expired and find interval to next timeout (capped to maximum second). maximum (float): seconds to cap to the value. return (float): seconds to next timeout. """ current = monotonic_time() # Try to connect to disconnected services. self._reconnect() # Check if some scheduled function needs to be called. while self._timeouts != []: timeout_data = self._timeouts[0] next_timeout, _, _, _ = timeout_data if current > next_timeout: heapq.heappop(self._timeouts) # The helper function checks the return value and, if # needed, enqueues the next timeout call def helper(timeout_data): next_timeout, seconds, func, plus = timeout_data if plus is None: ret = func() else: ret = func(plus) if ret: heapq.heappush(self._timeouts, (next_timeout + seconds, seconds, func, plus)) gevent.spawn(helper, timeout_data) else: break # Compute time to next timeout call next_timeout = maximum if self._timeouts != []: next_timeout = min(next_timeout, self._timeouts[0][0] - current) return max(0.0, next_timeout)
def _trigger(self, maximum=2.0): """Call the timeouts that have expired and find interval to next timeout (capped to maximum second). maximum (float): seconds to cap to the value. return (float): seconds to next timeout. """ current = monotonic_time() # Try to connect to disconnected services. self._reconnect() # Check if some scheduled function needs to be called. while self._timeouts != []: timeout_data = self._timeouts[0] next_timeout, _, _, _ = timeout_data if current > next_timeout: heapq.heappop(self._timeouts) # The helper function checks the return value and, if # needed, enqueues the next timeout call def helper(timeout_data): next_timeout, seconds, func, plus = timeout_data if plus is None: ret = func() else: ret = func(plus) if ret: heapq.heappush( self._timeouts, (next_timeout + seconds, seconds, func, plus)) gevent.spawn(helper, timeout_data) else: break # Compute time to next timeout call next_timeout = maximum if self._timeouts != []: next_timeout = min(next_timeout, self._timeouts[0][0] - current) return max(0.0, next_timeout)
def execute_without_std(self, command, wait=False): """Execute the given command in the sandbox using subprocess.Popen and discarding standard input, output and error. More specifically, the standard input gets closed just after the execution has started; standard output and error are read until the end, in a way that prevents the execution from being blocked because of insufficient buffering. command (list): executable filename and arguments of the command. return (bool): True if the sandbox didn't report errors (caused by the sandbox itself), False otherwise """ def preexec_fn(self): """Set limits for the child process. """ if self.chdir: os.chdir(self.chdir) # TODO - We're not checking that setrlimit() returns # successfully (they may try to set to higher limits than # allowed to); anyway, this is just for testing # environment, not for real contests, so who cares. if self.timeout: rlimit_cpu = self.timeout if self.extra_timeout: rlimit_cpu += self.extra_timeout rlimit_cpu = int(rlimit_cpu) + 1 resource.setrlimit(resource.RLIMIT_CPU, (rlimit_cpu, rlimit_cpu)) if self.address_space: rlimit_data = int(self.address_space * 1024) resource.setrlimit(resource.RLIMIT_DATA, (rlimit_data, rlimit_data)) if self.stack_space: rlimit_stack = int(self.stack_space * 1024) resource.setrlimit(resource.RLIMIT_STACK, (rlimit_stack, rlimit_stack)) # TODO - Doesn't work as expected #resource.setrlimit(resource.RLIMIT_NPROC, (1, 1)) # Setup std*** redirection if self.stdin_file: stdin_fd = os.open(os.path.join(self.path, self.stdin_file), os.O_RDONLY) else: stdin_fd = subprocess.PIPE if self.stdout_file: stdout_fd = os.open(os.path.join(self.path, self.stdout_file), os.O_WRONLY | os.O_TRUNC | os.O_CREAT, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH | stat.S_IWUSR) else: stdout_fd = subprocess.PIPE if self.stderr_file: stderr_fd = os.open(os.path.join(self.path, self.stderr_file), os.O_WRONLY | os.O_TRUNC | os.O_CREAT, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH | stat.S_IWUSR) else: stderr_fd = subprocess.PIPE # Note down execution time self.popen_time = monotonic_time() # Actually call the Popen self.popen = self._popen(command, stdin=stdin_fd, stdout=stdout_fd, stderr=stderr_fd, preexec_fn=partial(preexec_fn, self), close_fds=True) # Close file descriptors passed to the child if self.stdin_file: os.close(stdin_fd) if self.stdout_file: os.close(stdout_fd) if self.stderr_file: os.close(stderr_fd) # Kill the process after the wall clock time passed def timed_killer(timeout, popen): gevent.sleep(timeout) # TODO - Here we risk to kill some other process that gets # the same PID in the meantime; I don't know how to # properly solve this problem try: popen.kill() except OSError: # The process had died by itself pass # Setup the killer full_wallclock_timeout = self.wallclock_timeout if self.extra_timeout: full_wallclock_timeout += self.extra_timeout gevent.spawn(timed_killer, full_wallclock_timeout, self.popen) # If the caller wants us to wait for completion, we also avoid # std*** to interfere with command. Otherwise we let the # caller handle these issues. if wait: return self.translate_box_exitcode( wait_without_std([self.popen])[0]) else: return self.popen
def execute_without_std(self, command, wait=False): """Execute the given command in the sandbox using subprocess.Popen and discarding standard input, output and error. More specifically, the standard input gets closed just after the execution has started; standard output and error are read until the end, in a way that prevents the execution from being blocked because of insufficient buffering. command ([string]): executable filename and arguments of the command. return (bool): True if the sandbox didn't report errors (caused by the sandbox itself), False otherwise """ def preexec_fn(self): """Set limits for the child process. """ if self.chdir: os.chdir(self.chdir) # TODO - We're not checking that setrlimit() returns # successfully (they may try to set to higher limits than # allowed to); anyway, this is just for testing # environment, not for real contests, so who cares. if self.timeout: rlimit_cpu = self.timeout if self.extra_timeout: rlimit_cpu += self.extra_timeout rlimit_cpu = int(rlimit_cpu) + 1 resource.setrlimit(resource.RLIMIT_CPU, (rlimit_cpu, rlimit_cpu)) if self.address_space: rlimit_data = int(self.address_space * 1024) resource.setrlimit(resource.RLIMIT_DATA, (rlimit_data, rlimit_data)) if self.stack_space: rlimit_stack = int(self.stack_space * 1024) resource.setrlimit(resource.RLIMIT_STACK, (rlimit_stack, rlimit_stack)) # TODO - Doesn't work as expected #resource.setrlimit(resource.RLIMIT_NPROC, (1, 1)) # Setup std*** redirection if self.stdin_file: stdin_fd = os.open(os.path.join(self.path, self.stdin_file), os.O_RDONLY) else: stdin_fd = subprocess.PIPE if self.stdout_file: stdout_fd = os.open( os.path.join(self.path, self.stdout_file), os.O_WRONLY | os.O_TRUNC | os.O_CREAT, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH | stat.S_IWUSR) else: stdout_fd = subprocess.PIPE if self.stderr_file: stderr_fd = os.open( os.path.join(self.path, self.stderr_file), os.O_WRONLY | os.O_TRUNC | os.O_CREAT, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH | stat.S_IWUSR) else: stderr_fd = subprocess.PIPE # Note down execution time self.popen_time = monotonic_time() # Actually call the Popen self.popen = self._popen(command, stdin=stdin_fd, stdout=stdout_fd, stderr=stderr_fd, preexec_fn=partial(preexec_fn, self), close_fds=True) # Close file descriptors passed to the child if self.stdin_file: os.close(stdin_fd) if self.stdout_file: os.close(stdout_fd) if self.stderr_file: os.close(stderr_fd) if self.wallclock_timeout: # Kill the process after the wall clock time passed def timed_killer(timeout, popen): gevent.sleep(timeout) # TODO - Here we risk to kill some other process that gets # the same PID in the meantime; I don't know how to # properly solve this problem try: popen.kill() except OSError: # The process had died by itself pass # Setup the killer full_wallclock_timeout = self.wallclock_timeout if self.extra_timeout: full_wallclock_timeout += self.extra_timeout gevent.spawn(timed_killer, full_wallclock_timeout, self.popen) # If the caller wants us to wait for completion, we also avoid # std*** to interfere with command. Otherwise we let the # caller handle these issues. if wait: return self.translate_box_exitcode( wait_without_std([self.popen])[0]) else: return self.popen