Browse Source

Refactor execute_yields logic into a new module

jonathanvdc 8 years ago
parent
commit
3afdd25ee3
2 changed files with 194 additions and 149 deletions
  1. 19 149
      kernel/modelverse_kernel/main.py
  2. 175 0
      kernel/modelverse_kernel/request_handler.py

+ 19 - 149
kernel/modelverse_kernel/main.py

@@ -1,5 +1,6 @@
 import modelverse_kernel.primitives as primitive_functions
 import modelverse_kernel.compiled as compiled_functions
+from modelverse_kernel.request_handler import RequestHandler
 import modelverse_jit.jit as jit
 import modelverse_jit.intrinsics as jit_intrinsics
 from collections import defaultdict
@@ -11,94 +12,21 @@ if sys.version > '3': # pragma: no cover
 else:
     string_types = (str, unicode)
 
-class RunRequest(Exception):
-    pass
-
-def set_finished_requests_flag(opStack):
-    """Sets the finished_requests flag in the top-of-stack tuple."""
-    current_generator, requests, finished_requests, replies, has_reply = opStack[-1]
-    opStack[-1] = (current_generator, requests, True, replies, has_reply)
-
-def push_generator(gen, opStack):
-    """Pushes a new generator onto the given stack."""
-    opStack.append((gen, None, True, [], False))
-
-def pop_requests(requests, opStack):
-    """Tries to pop a batch of Modelverse _state_ requests from the
-       given list of requests. RUN-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."""
-    if requests is None or len(requests) == 0:
-        # Couldn't find a request for the state to handle.
-        set_finished_requests_flag(opStack)
-        return requests
-
-    for i, elem in enumerate(requests):
-        if elem[0] == "RUN":
-            # The kernel should handle RUN-requests.
-            if i > 0:
-                # Handle any requests that precede the RUN-request first.
-                pre_requests = requests[:i]
-                del requests[:i]
-                return pre_requests
-
-            # The RUN-request must be the first element in the list. Pop it.
-            requests.pop(0)
-
-            # The list of requests might be empty now. If so, then flag this
-            # batch of requests as finished.
-            if len(requests) == 0:
-                set_finished_requests_flag(opStack)
-
-            _, request_args = elem
-            if len(request_args) == 1:
-                # Format: ("RUN", [gen])
-                gen, = request_args
-                push_generator(gen, opStack)
-                raise RunRequest()
-            else:
-                # Format: ("RUN", [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:
-                    push_generator(func(**kwargs), opStack)
-                    raise RunRequest()
-                except StopIteration:
-                    push_generator(None, opStack)
-                    raise
-                except primitive_functions.PrimitiveFinished as ex:
-                    push_generator(None, opStack)
-                    raise
-
-    # We couldn't find a RUN-request in the batch of requests, so we might as well
-    # handle them all at once then.
-    set_finished_requests_flag(opStack)
-    return requests
-
 class ModelverseKernel(object):
     def __init__(self, root):
         self.root = root
         self.returnvalue = None
         self.success = True
+        # request_handlers is a dictionary of usernames to dictionaries of operations
+        # to request handlers. In generics notation:
         #
-        # generators is a dictionary of usernames to dictionaries of operations
-        # to stacks of (generator, pending requests, request replies, has-reply) tuples.
-        # In generics notation:
         # Dictionary<
         #     Username,
         #     Dictionary<
         #         Operation,
-        #         Stack<(Generator, Queue<List<Request>>, List<Replies>, Boolean)>>
+        #         RequestHandler>>
         #
-        self.generators = {}
+        self.request_handlers = {}
         self.allow_compiled = True
         #self.allow_compiled = False
 
@@ -130,78 +58,16 @@ class ModelverseKernel(object):
     def execute_yields(self, username, operation, params, reply):
         self.success = True
         self.username = username
-        if username not in self.generators:
-            self.generators[username] = {}
-        if operation not in self.generators[username]:
+        if username not in self.request_handlers:
+            self.request_handlers[username] = {}
+        if operation not in self.request_handlers[username]:
             # Create the generator for the function to execute
-            self.generators[username][operation] = []
-        opStack = self.generators[username][operation]
-        if len(opStack) == 0:
-            push_generator(getattr(self, operation)(username, *params), opStack)
-
-        current_generator, requests, finished_requests, replies, has_reply = opStack[-1]
-        # Append the server's replies to the list of replies.
-        if reply is not None:
-            replies.extend(reply)
-            has_reply = True
-            opStack[-1] = (current_generator, requests, finished_requests, replies, has_reply)
-        while 1:
-            try:
-                if not finished_requests:
-                    try:
-                        # Try to pop a request for the modelverse state.
-                        result = pop_requests(requests, opStack)
-                        return result
-                    except RunRequest:
-                        # Carry on.
-                        pass
-
-                # The call to pop_requests may have appended a tuple to the stack,
-                # so we need to reload the top-of-stack tuple.
-                current_generator, requests, finished_requests, replies, has_reply = opStack[-1]
-
-                # Send the replies to the generator, and ask for new requests.
-                requests = current_generator.send(replies if has_reply else None)
-                finished_requests = False
-
-                # Update the entry in the stack.
-                opStack[-1] = (current_generator, requests, finished_requests, [], False)
-            except StopIteration:
-                # Done, so remove the generator
-                opStack.pop()
-                if len(opStack) == 0:
-                    # Looks like we're done here.
-                    # Maybe remove the stack of generators as well?
-                    # del self.generators[username][operation]
-                    return None
-
-                # Update the current generator, requests, and replies.
-                current_generator, requests, finished_requests, replies, has_reply = opStack[-1]
-                # Append 'None' to the list of replies.
-                replies.append(None)
-                if not has_reply:
-                    has_reply = True
-                    # Update the stack.
-                    opStack[-1] = current_generator, requests, finished_requests, replies, has_reply
-            except primitive_functions.PrimitiveFinished as ex:
-                # Done, so remove the generator.
-                opStack.pop()
-                if len(opStack) == 0:
-                    # Looks like we're done here.
-                    # Maybe remove the stack of generators as well?
-                    # del self.generators[username][operation]
-                    return None
-
-                # Update the current generator, requests, and replies.
-                current_generator, requests, finished_requests, replies, has_reply = opStack[-1]
-                # Append the generator's result to the list of replies.
-                replies.append(ex.result)
-                if not has_reply:
-                    has_reply = True
-                    # Update the stack.
-                    opStack[-1] = current_generator, requests, finished_requests, replies, has_reply
-            except:
-                raise
+            self.request_handlers[username][operation] = RequestHandler()
+        handler = self.request_handlers[username][operation]
+        if not handler.is_active():
+            handler.push_generator(getattr(self, operation)(username, *params))
+
+        return handler.handle_request(reply)
 
     def execute_rule(self, username):
         user_root, =    yield [("RD", [self.root, username])]
@@ -245,7 +111,11 @@ class ModelverseKernel(object):
                 raise Exception("%s: error understanding command (%s, %s)" % (self.debug_info[username], inst_v, self.phase_v))
 
         try:
-            yield [("RUN", [gen])]
+            inp = None
+            while 1:
+                inp = yield gen.send(inp)
+        except StopIteration:
+            pass
         except jit.JitCompilationFailedException as e:
             # Try again, but this time without the JIT.
             # print(e.message)

+ 175 - 0
kernel/modelverse_kernel/request_handler.py

@@ -0,0 +1,175 @@
+import modelverse_kernel.primitives as primitive_functions
+
+class KnownRequestHandled(Exception):
+    """An exception that signifies that a known request was handled."""
+    pass
+
+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 (generator, pending requests, request replies, has-reply)
+        # tuples.
+        self.generator_stack = []
+        # exception_handlers is a stack of (generator_stack index, exception handler function)
+        # tuples.
+        self.exception_handlers = []
+        self.handlers = {
+            'RUN' : self.execute_run
+        }
+
+    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.is_active():
+            raise ValueError('handle_request cannot be called with an empty generator stack.')
+
+        # Append the server's replies to the list of replies.
+        self.extend_replies(reply)
+        while 1:
+            try:
+                if self.has_pending_requests():
+                    try:
+                        # Try to pop a request for the modelverse state.
+                        result = self.pop_requests()
+                        return result
+                    except KnownRequestHandled:
+                        # Carry on.
+                        pass
+
+                # Perform a single generator step.
+                self.step()
+            except StopIteration:
+                # Done, so remove the generator
+                self.generator_stack.pop()
+                if self.is_active():
+                    # 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
+                self.generator_stack.pop()
+                if self.is_active():
+                    # 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
+            except:
+                raise
+
+    def set_finished_requests_flag(self):
+        """Sets the finished_requests flag in the top-of-stack tuple."""
+        current_generator, requests, _, replies, has_reply = self.generator_stack[-1]
+        self.generator_stack[-1] = (current_generator, requests, True, replies, has_reply)
+
+    def has_pending_requests(self):
+        """Tests if the top-of-stack generator has pending requests."""
+        _, _, finished_requests, _, _ = self.generator_stack[-1]
+        return not finished_requests
+
+    def push_generator(self, gen):
+        """Pushes a new generator onto the stack."""
+        self.generator_stack.append((gen, None, True, [], False))
+
+    def append_reply(self, new_reply):
+        """Appends a reply to the top-of-stack generator's list of pending replies."""
+        current_generator, requests, requests_done, replies, has_reply = self.generator_stack[-1]
+        replies.append(new_reply)
+        has_reply = True
+        self.generator_stack[-1] = (current_generator, requests, requests_done, replies, has_reply)
+
+    def extend_replies(self, new_replies):
+        """Appends a list of replies to the top-of-stack generator's list of pending replies."""
+        current_generator, requests, requests_done, replies, has_reply = self.generator_stack[-1]
+        if new_replies is not None:
+            replies.extend(new_replies)
+            has_reply = True
+            self.generator_stack[-1] = (
+                current_generator, requests, requests_done, replies, has_reply)
+
+    def step(self):
+        """Performs a single step: accumulated replies are fed to the generator,
+           which then produces requests."""
+        current_generator, _, _, replies, has_reply = self.generator_stack[-1]
+
+        # Send the replies to the generator, and ask for new requests.
+        requests = current_generator.send(replies if has_reply else None)
+
+        # Update the entry on the stack.
+        self.generator_stack[-1] = (current_generator, requests, False, [], False)
+
+    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]
+        if requests is None or len(requests) == 0:
+            # Couldn't find a request for the state to handle.
+            self.set_finished_requests_flag()
+            return requests
+
+        for i, elem in enumerate(requests):
+            if elem[0] in self.handlers:
+                # The kernel should handle known requests.
+                if i > 0:
+                    # Handle any requests that precede the known request first.
+                    pre_requests = requests[:i]
+                    del requests[:i]
+                    return pre_requests
+
+                # The known request must be the first element in the list. Pop it.
+                requests.pop(0)
+
+                # The list of requests might be empty now. If so, then flag this
+                # batch of requests as finished.
+                if len(requests) == 0:
+                    self.set_finished_requests_flag()
+
+                # Handle the request.
+                _, request_args = elem
+                self.handlers[elem[0]](request_args)
+
+        # We couldn't find a RUN-request in the batch of requests, so we might as well
+        # handle them all at once then.
+        self.set_finished_requests_flag()
+        return requests
+
+    def execute_run(self, request_args):
+        """Executes a RUN-request with the given argument list."""
+        # The list of requests might be empty now. If so, then flag this
+        # batch of requests as finished.
+        if len(request_args) == 1:
+            # Format: ("RUN", [gen])
+            gen, = request_args
+            self.push_generator(gen)
+            raise KnownRequestHandled()
+        else:
+            # Format: ("RUN", [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))
+                raise KnownRequestHandled()
+            except StopIteration:
+                self.push_generator(None)
+                raise
+            except primitive_functions.PrimitiveFinished:
+                self.push_generator(None)
+                raise