class SandboxProcessServer: def __init__(self, *, sandbox_dir, executable): self.executable = executable self.boundary = PipeBoundary(sandbox_dir) self.boundary.create_channel(SANDBOX_PROCESS_CHANNEL) self.boundary.create_queue(SANDBOX_REQUEST_QUEUE) self.done = False self.process = None self.process_exit_stack = ExitStack() def run(self): logger.debug("starting process...") with self.boundary.open_channel(SANDBOX_PROCESS_CHANNEL, PipeBoundarySide.SERVER) as pipes: connection = SandboxProcessConnection(**pipes) self.process = self.process_exit_stack.enter_context( self.executable.run(connection)) logger.debug("process started") while not self.done: logger.debug("handling requests...") self.boundary.handle_request( SANDBOX_REQUEST_QUEUE, self.handle_request, ) def handle_request(self, *, wait): assert not self.done assert wait in ("0", "1") time_usage = self.executable.get_time_usage(self.process) memory_usage = self.executable.get_memory_usage(self.process) message = stacktrace = "" if wait == "1": self.done = True self.process = None try: self.process_exit_stack.close() except AlgorithmRuntimeError as e: message, stacktrace = e.args return { "error": message, "stacktrace": stacktrace, "time_usage": str(time_usage), "memory_usage": str(memory_usage), }
class MetaServer: """ Implements a server that starts child servers. This class listens on a synchronous queue: it reads the parameters for the server to start, starts the child server in a new thread, listening in a temporary directory, writes the location of the temporary directory, and deletes the temporary directory when the child server stops. """ def __init__(self, directory): self.boundary = PipeBoundary(directory) self.boundary.create_queue(self.get_queue_descriptor()) @abstractmethod def get_queue_descriptor(self): pass @abstractmethod def create_child_server(self, child_server_dir, **request_payloads): pass @abstractmethod def run_child_server(self, child_server): pass @abstractmethod def create_response(self, child_server_dir): pass def run(self): while True: try: self.boundary.handle_request( self.get_queue_descriptor(), self.handle_request, ) except StopMetaServer: break def _run_child_server(self, child_server, child_server_dir): try: self.run_child_server(child_server) finally: child_server_dir.cleanup() def handle_request(self, **request_payloads): self.handle_stop_request(request_payloads) child_server_dir = TemporaryDirectory(dir="/tmp") child_server = self.create_child_server(child_server_dir.name, **request_payloads) Thread(target=lambda: self._run_child_server( child_server, child_server_dir)).start() return self.create_response(child_server_dir.name) def handle_stop_request(self, request_payloads): if any(request_payloads.values()): return raise StopMetaServer def stop(self): self.boundary.send_empty_request(self.get_queue_descriptor())
class DriverProcessServer: def __init__(self, *, interface_text, sandbox_process_dir, driver_process_dir): self.sandbox_dir = sandbox_process_dir self.boundary = PipeBoundary(driver_process_dir) self.interface = InterfaceDefinition.compile(interface_text) self.boundary.create_queue(DRIVER_PROCESS_QUEUE) self.run_driver_iterator = None def run(self): with ExitStack() as stack: logger.debug("connecting to process...") sandbox_connection = stack.enter_context( SandboxProcessClient(self.sandbox_dir).connect()) self.run_driver_iterator = drive_interface( sandbox_connection=sandbox_connection, interface=self.interface) self.process_requests() def handle_request(self, request): current_request = self.deserialize(request) try: response = self.run_driver_iterator.send(current_request) except CommunicationBroken: logger.warning(f"communication with process broken") return { "sandbox_error": "communication broken", } assert all(isinstance(x, int) for x in response) return {"response": "\n".join(str(x) for x in response)} def deserialize(self, request): deserializer = deserialize_request() assert next(deserializer) is None lines = request.splitlines() lines_it = iter(lines) try: for line in lines_it: deserializer.send(line) except StopIteration as e: extra_lines = list(lines_it) if extra_lines: raise ValueError( f"extra lines '{extra_lines!s:.50}'") from None result = e.value else: raise ValueError(f"too few lines") return result def process_requests(self): assert next(self.run_driver_iterator) is None while True: self.boundary.handle_request(DRIVER_PROCESS_QUEUE, self.handle_request) try: assert next(self.run_driver_iterator) is None except StopIteration: break