Преглед изворни кода

Create stack traces for jitted functions

jonathanvdc пре 8 година
родитељ
комит
b072a71f26

+ 10 - 5
kernel/modelverse_jit/runtime.py

@@ -43,14 +43,19 @@ BASELINE_JIT_ORIGIN_NAME = "baseline-jit"
 FAST_JIT_ORIGIN_NAME = "fast-jit"
 """The origin name for functions that were produced by the fast JIT."""
 
-def format_trace_message(debug_info, function_name, origin='unknown'):
-    """Creates a formatted trace message."""
-    if debug_info is None:
-        debug_info = 'unknown location '
+def format_stack_frame(function_name, debug_info, origin='unknown'):
+    """Formats a stack frame, which consists of a function name, debug
+       information and an origin."""
     if function_name is None:
         function_name = 'unknown function'
+    if debug_info is None:
+        debug_info = '[unknown location] '
 
-    return 'TRACE: %s(%s, %s)' % (debug_info, function_name, origin)
+    return '%sin %s (%s)' % (debug_info, function_name, origin)
+
+def format_trace_message(debug_info, function_name, origin='unknown'):
+    """Creates a formatted trace message."""
+    return 'TRACE: %s' % format_stack_frame(function_name, debug_info, origin)
 
 def call_function(function_id, named_arguments, **kwargs):
     """Runs the function with the given id, passing it the specified argument dictionary."""

+ 4 - 1
kernel/modelverse_jit/source_map.py

@@ -12,7 +12,10 @@ class SourceMap(object):
     def get_debug_info(self, line_number):
         """Gets the debug information for the given line number, or None if no debug info was
            found."""
-        return self.lines[line_number]
+        if line_number in self.lines:
+            return self.lines[line_number]
+        else:
+            return None
 
     def __str__(self):
         return '\n'.join(

+ 63 - 8
kernel/modelverse_kernel/request_handler.py

@@ -1,4 +1,6 @@
+import sys
 import modelverse_kernel.primitives as primitive_functions
+import modelverse_jit.runtime as jit_runtime
 
 class KnownRequestHandled(Exception):
     """An exception that signifies that a known request was handled."""
@@ -10,7 +12,7 @@ class GeneratorStackEntry(object):
         self.generator = generator
         self.function_name = None
         self.source_map = None
-        self.function_source = None
+        self.function_origin = None
         self.pending_requests = None
         self.finished_requests = True
         self.replies = []
@@ -38,6 +40,22 @@ class GeneratorStackEntry(object):
         self.replies = []
         self.has_reply = False
 
+def format_stack_trace(stack_trace):
+    """Formats a list of (function name, debug info, origin) triples."""
+    return '\n'.join([jit_runtime.format_stack_frame(*triple) for triple in stack_trace])
+
+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):
+        Exception.__init__(
+            self,
+            """The request handler encountered an unknown exception.\n
+               Inner exception: %s\n
+               Stack trace:\n%s\n""" % (inner_exception, format_stack_trace(stack_trace)))
+        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."""
@@ -109,8 +127,9 @@ class RequestHandler(object):
                     return None
             except Exception as ex:
                 # Maybe get an exception handler to do this.
-                if not self.handle_exception(ex):
-                    raise
+                stack_trace = self.handle_exception(ex)
+                if stack_trace is not None:
+                    raise UnhandledRequestHandlerException(ex, stack_trace)
 
     def set_finished_requests_flag(self):
         """Sets the finished_requests flag in the top-of-stack tuple."""
@@ -181,14 +200,50 @@ class RequestHandler(object):
                     ('TAIL_CALL_ARGS', [applicable_handler, (exception,)])]
                 stack_entry.finished_requests = False
                 self.generator_stack.append(stack_entry)
-                return True
+                return None
 
         # We couldn't find an applicable exception handler, even after exhausting the
         # entire exception handler stack. All is lost.
-        # Also, clean up after ourselves.
-        self.generator_stack = []
+        # Also, clean up after ourselves by unwinding the stack.
+        return self.unwind_stack()
+
+    def unwind_stack(self):
+        """Unwinds the entirety of the stack. All generators and exception handlers are
+           discarded. A list of (function name, debug information, source) statements is
+           returned."""
+        class UnwindStackException(Exception):
+            """A hard-to-catch exception that is used to make generators crash.
+               The exceptions they produce can then be analyzed for line numbers."""
+            pass
+
+        # First throw away all exception handlers. We won't be needing them any more.
         self.exception_handlers = []
-        return False
+
+        # Then pop every generator from the stack and make it crash.
+        stack_trace = []
+        while len(self.generator_stack) > 0:
+            top_entry = self.generator_stack.pop()
+            if top_entry.function_origin is None:
+                # Skip this function.
+                continue
+
+            try:
+                # Crash the generator.
+                top_entry.generator.throw(UnwindStackException())
+            except UnwindStackException:
+                # Find out where the exception was thrown.
+                _, _, exc_traceback = sys.exc_info()
+                line_number = exc_traceback.tb_lineno
+                source_map = top_entry.source_map
+                if source_map is not None:
+                    debug_info = source_map.get_debug_info(line_number)
+                else:
+                    debug_info = None
+
+                function_name = top_entry.function_name
+                stack_trace.append((function_name, debug_info, top_entry.function_origin))
+
+        return stack_trace[::-1]
 
     def pop_requests(self):
         """Tries to pop a batch of Modelverse _state_ requests from the
@@ -353,6 +408,6 @@ class RequestHandler(object):
         # encountered.
         # Format: ("DEBUG_INFO", [function_name, source_map])
         top_entry = self.generator_stack[-1]
-        top_entry.function_name, top_entry.source_map, top_entry.function_source = request_args
+        top_entry.function_name, top_entry.source_map, top_entry.function_origin = request_args
         top_entry.append_reply(None)