123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193 |
- import sys
- import modelverse_kernel.primitives as primitive_functions
- import modelverse_kernel.jit as jit
- from collections import defaultdict
- class KnownRequestHandled(Exception):
- """An exception that signifies that a known request was handled."""
- pass
- class UnhandledRequestHandlerException(Exception):
- """The type of exception that is thrown when the request handler encounters an
- unhandled exception."""
- def __init__(self, inner_exception, stack_trace):
- import traceback
- Exception.__init__(
- self,
- """The request handler encountered an unknown exception.\n
- Inner exception: %s\n
- """ % (traceback.format_exc()))
- self.inner_exception = inner_exception
- self.stack_trace = stack_trace
- class RequestHandler(object):
- """A type of object that intercepts logic-related Modelverse requests, and
- forwards Modelverse state requests."""
- def __init__(self):
- # generator_stack is a stack of GeneratorStackEntry values.
- self.generator_stack = []
- # exception_handlers is a stack of
- # (generator_stack index, [(exception type, handler function)])
- # tuples.
- self.produce_stack_trace = True
- self.handlers = {
- 'CALL' : self.execute_call,
- 'CALL_ARGS' : self.execute_call_args,
- 'CALL_KWARGS' : self.execute_call_kwargs,
- 'SLEEP' : self.execute_sleep,
- }
- def is_active(self):
- """Tests if this request handler has a top-of-stack generator."""
- return len(self.generator_stack) > 0
- def handle_request(self, reply):
- """Replies to a request from the top-of-stack generator, and returns a new request."""
- if not self.generator_stack:
- raise ValueError('handle_request cannot be called with an empty generator stack.')
- # Append the server's replies to the list of replies.
- if reply is not None:
- gen = self.generator_stack[-1]
- if gen["replies"]:
- gen["replies"].extend(reply)
- else:
- gen["replies"] = reply
- while 1:
- # Silence pylint's warning about catching Exception.
- # pylint: disable=I0011,W0703
- try:
- gen = self.generator_stack[-1]
- if gen["finished_requests"]:
- gen["pending_requests"] = gen["generator"].send(gen["replies"])
- gen["finished_requests"] = False
- gen["replies"] = None
- return self.pop_requests()
- except KnownRequestHandled:
- pass
- except StopIteration:
- # Done, so remove the generator
- del self.generator_stack[-1]
- if self.generator_stack:
- # This generator was called from another generator.
- # Append 'None' to the caller's list of replies.
- self.append_reply(None)
- else:
- # Looks like we're done here.
- return None
- except primitive_functions.PrimitiveFinished as ex:
- # Done, so remove the generator
- del self.generator_stack[-1]
- if self.generator_stack:
- # This generator was called from another generator.
- # Append the callee's result to the caller's list of replies.
- self.append_reply(ex.result)
- else:
- # Looks like we're done here.
- return None
- def push_generator(self, gen):
- """Pushes a new generator onto the stack."""
- dd = defaultdict(lambda : None)
- dd["generator"] = gen
- dd["finished_requests"] = True
- self.generator_stack.append(dd)
- # print('Pushed generator %s. Generator count: %d' % (gen, len(self.generator_stack)))
- def append_reply(self, new_reply):
- """Appends a reply to the top-of-stack generator's list of pending replies."""
- if self.generator_stack[-1]["replies"] is None:
- self.generator_stack[-1]["replies"] = [new_reply]
- else:
- self.generator_stack[-1]["replies"].append(new_reply)
- def pop_requests(self):
- """Tries to pop a batch of Modelverse _state_ requests from the
- current list of requests. Known requests are executed immediately.
- A list of requests and a Boolean are returned. The latter is True
- if there are no more requests to process, and false otherwise."""
- requests = self.generator_stack[-1]["pending_requests"]
- if requests:
- if requests[0][0] in self.handlers:
- # First element is a known request
- elem = requests.pop(0)
- # The list of requests might be empty now. If so, then flag this
- # batch of requests as finished.
- if not requests:
- self.generator_stack[-1]["finished_requests"] = True
- # Handle the request.
- self.handlers[elem[0]](elem[1])
- raise KnownRequestHandled()
- else:
- for i, elem in enumerate(requests):
- if elem[0] in self.handlers:
- # Handle any requests that precede the known request first.
- pre_requests = requests[:i]
- del requests[:i]
- return pre_requests
- # We couldn't find a known request in the batch of requests, so we might as well
- # handle them all at once then.
- self.generator_stack[-1]["finished_requests"] = True
- return requests
- def execute_call(self, request_args):
- """Executes a CALL-request with the given argument list."""
- # Format: ("CALL", [gen])
- gen, = request_args
- self.push_generator(gen)
- def execute_call_kwargs(self, request_args):
- """Executes a CALL_KWARGS-request with the given argument list."""
- # Format: ("CALL_KWARGS", [func, kwargs])
- # This format is useful because it also works for functions that
- # throw an exception but never yield.
- func, kwargs = request_args
- # We need to be extra careful here, because func(**kwargs) might
- # not be a generator at all: it might simply be a method that
- # raises an exception. To cope with this we need to push a dummy
- # entry onto the stack if a StopIteration or PrimtiveFinished
- # exception is thrown. The logic in execute_yields will then pop
- # that dummy entry.
- try:
- self.push_generator(func(**kwargs))
- except StopIteration:
- self.push_generator(None)
- raise
- except primitive_functions.PrimitiveFinished:
- self.push_generator(None)
- raise
- except:
- print("EXCEPTION for " + str(locals()))
- raise
- def execute_call_args(self, request_args):
- """Executes a CALL_ARGS-request with the given argument list."""
- # Format: ("CALL_ARGS", [gen, args])
- func, args = request_args
- # We need to be extra careful here, because func(*args) might
- # not be a generator at all: it might simply be a method that
- # raises an exception. To cope with this we need to push a dummy
- # entry onto the stack if a StopIteration or PrimtiveFinished
- # exception is thrown. The logic in execute_yields will then pop
- # that dummy entry.
- try:
- self.push_generator(func(*args))
- except StopIteration:
- self.push_generator(None)
- raise
- except primitive_functions.PrimitiveFinished:
- self.push_generator(None)
- raise
- def execute_sleep(self, request_args):
- """Executes a SLEEP-request with the given argument list."""
- self.append_reply(None)
- raise primitive_functions.SleepKernel(request_args[0], request_args[1])
|