|
@@ -7,10 +7,6 @@ class KnownRequestHandled(Exception):
|
|
|
"""An exception that signifies that a known request was handled."""
|
|
"""An exception that signifies that a known request was handled."""
|
|
|
pass
|
|
pass
|
|
|
|
|
|
|
|
-def format_stack_trace(stack_trace):
|
|
|
|
|
- """Formats a list of (function name, debug info, origin) triples."""
|
|
|
|
|
- return '\n'.join([jit.format_stack_frame(*triple) for triple in stack_trace])
|
|
|
|
|
-
|
|
|
|
|
class UnhandledRequestHandlerException(Exception):
|
|
class UnhandledRequestHandlerException(Exception):
|
|
|
"""The type of exception that is thrown when the request handler encounters an
|
|
"""The type of exception that is thrown when the request handler encounters an
|
|
|
unhandled exception."""
|
|
unhandled exception."""
|
|
@@ -20,7 +16,7 @@ class UnhandledRequestHandlerException(Exception):
|
|
|
self,
|
|
self,
|
|
|
"""The request handler encountered an unknown exception.\n
|
|
"""The request handler encountered an unknown exception.\n
|
|
|
Inner exception: %s\n
|
|
Inner exception: %s\n
|
|
|
- Stack trace:\n%s\n""" % (traceback.format_exc(), format_stack_trace(stack_trace)))
|
|
|
|
|
|
|
+ """ % (traceback.format_exc()))
|
|
|
self.inner_exception = inner_exception
|
|
self.inner_exception = inner_exception
|
|
|
self.stack_trace = stack_trace
|
|
self.stack_trace = stack_trace
|
|
|
|
|
|
|
@@ -33,19 +29,11 @@ class RequestHandler(object):
|
|
|
# exception_handlers is a stack of
|
|
# exception_handlers is a stack of
|
|
|
# (generator_stack index, [(exception type, handler function)])
|
|
# (generator_stack index, [(exception type, handler function)])
|
|
|
# tuples.
|
|
# tuples.
|
|
|
- self.exception_handlers = []
|
|
|
|
|
self.produce_stack_trace = True
|
|
self.produce_stack_trace = True
|
|
|
self.handlers = {
|
|
self.handlers = {
|
|
|
'CALL' : self.execute_call,
|
|
'CALL' : self.execute_call,
|
|
|
'CALL_ARGS' : self.execute_call_args,
|
|
'CALL_ARGS' : self.execute_call_args,
|
|
|
'CALL_KWARGS' : self.execute_call_kwargs,
|
|
'CALL_KWARGS' : self.execute_call_kwargs,
|
|
|
- 'TAIL_CALL' : self.execute_tail_call,
|
|
|
|
|
- 'TAIL_CALL_ARGS' : self.execute_tail_call_args,
|
|
|
|
|
- 'TAIL_CALL_KWARGS' : self.execute_tail_call_kwargs,
|
|
|
|
|
- 'TRY' : self.execute_try,
|
|
|
|
|
- 'CATCH' : self.execute_catch,
|
|
|
|
|
- 'END_TRY' : self.execute_end_try,
|
|
|
|
|
- 'DEBUG_INFO' : self.execute_debug_info,
|
|
|
|
|
'SLEEP' : self.execute_sleep,
|
|
'SLEEP' : self.execute_sleep,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -81,7 +69,7 @@ class RequestHandler(object):
|
|
|
pass
|
|
pass
|
|
|
except StopIteration:
|
|
except StopIteration:
|
|
|
# Done, so remove the generator
|
|
# Done, so remove the generator
|
|
|
- self.pop_generator()
|
|
|
|
|
|
|
+ del self.generator_stack[-1]
|
|
|
if self.generator_stack:
|
|
if self.generator_stack:
|
|
|
# This generator was called from another generator.
|
|
# This generator was called from another generator.
|
|
|
# Append 'None' to the caller's list of replies.
|
|
# Append 'None' to the caller's list of replies.
|
|
@@ -91,7 +79,7 @@ class RequestHandler(object):
|
|
|
return None
|
|
return None
|
|
|
except primitive_functions.PrimitiveFinished as ex:
|
|
except primitive_functions.PrimitiveFinished as ex:
|
|
|
# Done, so remove the generator
|
|
# Done, so remove the generator
|
|
|
- self.pop_generator()
|
|
|
|
|
|
|
+ del self.generator_stack[-1]
|
|
|
if self.generator_stack:
|
|
if self.generator_stack:
|
|
|
# This generator was called from another generator.
|
|
# This generator was called from another generator.
|
|
|
# Append the callee's result to the caller's list of replies.
|
|
# Append the callee's result to the caller's list of replies.
|
|
@@ -99,16 +87,6 @@ class RequestHandler(object):
|
|
|
else:
|
|
else:
|
|
|
# Looks like we're done here.
|
|
# Looks like we're done here.
|
|
|
return None
|
|
return None
|
|
|
- except primitive_functions.SleepKernel:
|
|
|
|
|
- raise
|
|
|
|
|
- except Exception as ex:
|
|
|
|
|
- # Maybe get an exception handler to do this.
|
|
|
|
|
- stack_trace = self.handle_exception(ex)
|
|
|
|
|
- if stack_trace is not None:
|
|
|
|
|
- if self.produce_stack_trace:
|
|
|
|
|
- raise UnhandledRequestHandlerException(ex, stack_trace)
|
|
|
|
|
- else:
|
|
|
|
|
- raise
|
|
|
|
|
|
|
|
|
|
def set_finished_requests_flag(self):
|
|
def set_finished_requests_flag(self):
|
|
|
"""Sets the finished_requests flag in the top-of-stack tuple."""
|
|
"""Sets the finished_requests flag in the top-of-stack tuple."""
|
|
@@ -122,25 +100,6 @@ class RequestHandler(object):
|
|
|
self.generator_stack.append(dd)
|
|
self.generator_stack.append(dd)
|
|
|
# print('Pushed generator %s. Generator count: %d' % (gen, len(self.generator_stack)))
|
|
# print('Pushed generator %s. Generator count: %d' % (gen, len(self.generator_stack)))
|
|
|
|
|
|
|
|
- def pop_generator(self):
|
|
|
|
|
- """Removes the top-of-stack generator from the generator stack."""
|
|
|
|
|
- # Pop the generator itself.
|
|
|
|
|
- #self.generator_stack.pop()
|
|
|
|
|
- del self.generator_stack[-1]
|
|
|
|
|
- # print('Popped generator %s. Generator count: %d' % (gen, len(self.generator_stack)))
|
|
|
|
|
- # Pop any exception handlers defined by the generator.
|
|
|
|
|
- top_of_stack_index = len(self.generator_stack)
|
|
|
|
|
- while self.exception_handlers:
|
|
|
|
|
- if self.exception_handlers[-1][0] == top_of_stack_index:
|
|
|
|
|
- # Pop exception handlers until exception_handlers is empty or until
|
|
|
|
|
- # we find an exception handler that is not associated with the popped
|
|
|
|
|
- # generator.
|
|
|
|
|
- #self.exception_handlers.pop()
|
|
|
|
|
- del self.exception_handlers[-1]
|
|
|
|
|
- else:
|
|
|
|
|
- # We're done here.
|
|
|
|
|
- break
|
|
|
|
|
-
|
|
|
|
|
def append_reply(self, new_reply):
|
|
def append_reply(self, new_reply):
|
|
|
"""Appends a reply to the top-of-stack generator's list of pending replies."""
|
|
"""Appends a reply to the top-of-stack generator's list of pending replies."""
|
|
|
if self.generator_stack[-1]["replies"] is None:
|
|
if self.generator_stack[-1]["replies"] is None:
|
|
@@ -148,75 +107,6 @@ class RequestHandler(object):
|
|
|
else:
|
|
else:
|
|
|
self.generator_stack[-1]["replies"].append(new_reply)
|
|
self.generator_stack[-1]["replies"].append(new_reply)
|
|
|
|
|
|
|
|
- def handle_exception(self, exception):
|
|
|
|
|
- """Handles the given exception. A Boolean is returned that tells if
|
|
|
|
|
- the exception was handled."""
|
|
|
|
|
- # print('Exception thrown from %s: %s' % (str(self.generator_stack[-1]), str(exception)))
|
|
|
|
|
- while self.exception_handlers:
|
|
|
|
|
- # Pop the top-of-stack exception handler.
|
|
|
|
|
- stack_index, handlers = self.exception_handlers.pop()
|
|
|
|
|
-
|
|
|
|
|
- # Try to find an applicable handler.
|
|
|
|
|
- applicable_handler = None
|
|
|
|
|
- for handled_type, handler in handlers:
|
|
|
|
|
- if isinstance(exception, handled_type):
|
|
|
|
|
- applicable_handler = handler
|
|
|
|
|
-
|
|
|
|
|
- if applicable_handler is not None:
|
|
|
|
|
- # We handle exceptions by first clearing the current stack frame and
|
|
|
|
|
- # all of its children. Then, we place a dummy frame on the stack with
|
|
|
|
|
- # a single 'TAIL_CALL_ARGS' request. The next iteration will replace
|
|
|
|
|
- # the dummy frame by an actual frame.
|
|
|
|
|
- del self.generator_stack[stack_index:]
|
|
|
|
|
- stack_entry = defaultdict(lambda : None)
|
|
|
|
|
- stack_entry["pending_requests"] = [('TAIL_CALL_ARGS', [applicable_handler, (exception,)])]
|
|
|
|
|
- stack_entry["finished_requests"] = False
|
|
|
|
|
- self.generator_stack.append(stack_entry)
|
|
|
|
|
- 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 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 = []
|
|
|
|
|
-
|
|
|
|
|
- # Then pop every generator from the stack and make it crash.
|
|
|
|
|
- stack_trace = []
|
|
|
|
|
- while self.generator_stack:
|
|
|
|
|
- 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):
|
|
def pop_requests(self):
|
|
|
"""Tries to pop a batch of Modelverse _state_ requests from the
|
|
"""Tries to pop a batch of Modelverse _state_ requests from the
|
|
|
current list of requests. Known requests are executed immediately.
|
|
current list of requests. Known requests are executed immediately.
|
|
@@ -301,87 +191,6 @@ class RequestHandler(object):
|
|
|
self.push_generator(None)
|
|
self.push_generator(None)
|
|
|
raise
|
|
raise
|
|
|
|
|
|
|
|
- def execute_tail_call(self, request_args):
|
|
|
|
|
- """Executes a TAIL_CALL-request with the given argument list."""
|
|
|
|
|
- # Format: ("TAIL_CALL", [gen])
|
|
|
|
|
- self.pop_generator()
|
|
|
|
|
- self.execute_call(request_args)
|
|
|
|
|
-
|
|
|
|
|
- def execute_tail_call_args(self, request_args):
|
|
|
|
|
- """Executes a TAIL_CALL_ARGS-request with the given argument list."""
|
|
|
|
|
- # Format: ("TAIL_CALL_ARGS", [gen, args])
|
|
|
|
|
- self.pop_generator()
|
|
|
|
|
- self.execute_call_args(request_args)
|
|
|
|
|
-
|
|
|
|
|
- def execute_tail_call_kwargs(self, request_args):
|
|
|
|
|
- """Executes a TAIL_CALL_KWARGS-request with the given argument list."""
|
|
|
|
|
- # Format: ("TAIL_CALL_KWARGS", [gen, kwargs])
|
|
|
|
|
- self.pop_generator()
|
|
|
|
|
- self.execute_call_kwargs(request_args)
|
|
|
|
|
-
|
|
|
|
|
- def execute_try(self, request_args):
|
|
|
|
|
- """Executes a TRY-request with the given argument list."""
|
|
|
|
|
- # TRY pushes an exception handler onto the exception handler stack.
|
|
|
|
|
- # Format: ("TRY", [])
|
|
|
|
|
- if len(request_args) != 0:
|
|
|
|
|
- raise ValueError(
|
|
|
|
|
- ("TRY was given argument list '%s', " +
|
|
|
|
|
- "expected exactly zero arguments.") % repr(request_args))
|
|
|
|
|
- self.exception_handlers.append((len(self.generator_stack) - 1, []))
|
|
|
|
|
- self.append_reply(None)
|
|
|
|
|
-
|
|
|
|
|
- def execute_catch(self, request_args):
|
|
|
|
|
- """Executes a CATCH-request with the given argument list."""
|
|
|
|
|
- if len(request_args) != 2:
|
|
|
|
|
- raise ValueError(
|
|
|
|
|
- ("CATCH was given argument list '%s', "
|
|
|
|
|
- "expected exactly two arguments: an exception "
|
|
|
|
|
- "type and an exception handler.") % repr(request_args))
|
|
|
|
|
- exception_type, handler = request_args
|
|
|
|
|
- stack_index, handlers = self.exception_handlers[-1]
|
|
|
|
|
- if stack_index != len(self.generator_stack) - 1:
|
|
|
|
|
- raise ValueError(
|
|
|
|
|
- 'Cannot comply with CATCH because there is no exception handler for the '
|
|
|
|
|
- 'current generator.')
|
|
|
|
|
-
|
|
|
|
|
- handlers.append((exception_type, handler))
|
|
|
|
|
- self.append_reply(None)
|
|
|
|
|
-
|
|
|
|
|
- def execute_end_try(self, request_args):
|
|
|
|
|
- """Executes an END_TRY-request with the given argument list."""
|
|
|
|
|
- # END_TRY pops a value from the exception handler stack. The
|
|
|
|
|
- # popped value must reference the top-of-stack element in the
|
|
|
|
|
- # generator stack. END_TRY takes no arguments.
|
|
|
|
|
- # Format: ("END_TRY", [])
|
|
|
|
|
- if len(request_args) != 0:
|
|
|
|
|
- raise ValueError(
|
|
|
|
|
- "END_TRY was given argument list '%s', expected '%s'." % (
|
|
|
|
|
- repr(request_args), repr([])))
|
|
|
|
|
-
|
|
|
|
|
- if len(self.exception_handlers) == 0:
|
|
|
|
|
- raise ValueError(
|
|
|
|
|
- 'Cannot comply with END_TRY because the exception handler stack is empty.')
|
|
|
|
|
-
|
|
|
|
|
- stack_index, _ = self.exception_handlers[-1]
|
|
|
|
|
- if stack_index != len(self.generator_stack) - 1:
|
|
|
|
|
- raise ValueError(
|
|
|
|
|
- 'Cannot comply with END_TRY because there is no exception handler for the '
|
|
|
|
|
- 'current generator.')
|
|
|
|
|
-
|
|
|
|
|
- # Everything seems to be in order. Pop the exception handler.
|
|
|
|
|
- self.exception_handlers.pop()
|
|
|
|
|
- self.append_reply(None)
|
|
|
|
|
-
|
|
|
|
|
- def execute_debug_info(self, request_args):
|
|
|
|
|
- """Executes a DEBUG_INFO-request with the given argument list."""
|
|
|
|
|
- # DEBUG_INFO updates the function name and source map for the top-of-stack generator.
|
|
|
|
|
- # These two things allow us to unwind the stack neatly if an unhandled exception is
|
|
|
|
|
- # 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_origin"] = request_args
|
|
|
|
|
- self.append_reply(None)
|
|
|
|
|
-
|
|
|
|
|
def execute_sleep(self, request_args):
|
|
def execute_sleep(self, request_args):
|
|
|
"""Executes a SLEEP-request with the given argument list."""
|
|
"""Executes a SLEEP-request with the given argument list."""
|
|
|
self.append_reply(None)
|
|
self.append_reply(None)
|