request_handler.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. import sys
  2. import modelverse_kernel.primitives as primitive_functions
  3. import modelverse_kernel.jit as jit
  4. from collections import defaultdict
  5. class RequestHandler(object):
  6. """A type of object that intercepts logic-related Modelverse requests, and
  7. forwards Modelverse state requests."""
  8. def __init__(self):
  9. # generator_stack is a stack of GeneratorStackEntry values.
  10. self.generator_stack = []
  11. # exception_handlers is a stack of
  12. # (generator_stack index, [(exception type, handler function)])
  13. # tuples.
  14. self.produce_stack_trace = True
  15. self.handlers = {
  16. 'CALL' : self.execute_call,
  17. 'CALL_ARGS' : self.execute_call_args,
  18. 'CALL_KWARGS' : self.execute_call_kwargs,
  19. 'SLEEP' : self.execute_sleep,
  20. }
  21. def handle_request(self, reply):
  22. """Replies to a request from the top-of-stack generator, and returns a new request."""
  23. if not self.generator_stack:
  24. raise ValueError('handle_request cannot be called with an empty generator stack.')
  25. # Append the server's replies to the list of replies.
  26. if reply is not None:
  27. gen = self.generator_stack[-1]
  28. if gen["replies"]:
  29. gen["replies"].extend(reply)
  30. else:
  31. gen["replies"] = reply
  32. while 1:
  33. # Silence pylint's warning about catching Exception.
  34. # pylint: disable=I0011,W0703
  35. try:
  36. gen = self.generator_stack[-1]
  37. if gen["finished_requests"]:
  38. gen["pending_requests"] = gen["generator"].send(gen["replies"])
  39. gen["finished_requests"] = False
  40. gen["replies"] = None
  41. ret = self.pop_requests()
  42. if ret[0]:
  43. return ret[1]
  44. except StopIteration:
  45. # Done, so remove the generator
  46. del self.generator_stack[-1]
  47. if self.generator_stack:
  48. # This generator was called from another generator.
  49. # Append 'None' to the caller's list of replies.
  50. self.append_reply(None)
  51. else:
  52. # Looks like we're done here.
  53. return None
  54. except primitive_functions.PrimitiveFinished as ex:
  55. # Done, so remove the generator
  56. del self.generator_stack[-1]
  57. if self.generator_stack:
  58. # This generator was called from another generator.
  59. # Append the callee's result to the caller's list of replies.
  60. self.append_reply(ex.result)
  61. else:
  62. # Looks like we're done here.
  63. return None
  64. def push_generator(self, gen):
  65. """Pushes a new generator onto the stack."""
  66. dd = defaultdict(lambda : None)
  67. dd["generator"] = gen
  68. dd["finished_requests"] = True
  69. self.generator_stack.append(dd)
  70. # print('Pushed generator %s. Generator count: %d' % (gen, len(self.generator_stack)))
  71. def append_reply(self, new_reply):
  72. """Appends a reply to the top-of-stack generator's list of pending replies."""
  73. if self.generator_stack[-1]["replies"] is None:
  74. self.generator_stack[-1]["replies"] = [new_reply]
  75. else:
  76. self.generator_stack[-1]["replies"].append(new_reply)
  77. def pop_requests(self):
  78. """Tries to pop a batch of Modelverse _state_ requests from the
  79. current list of requests. Known requests are executed immediately.
  80. A list of requests and a Boolean are returned. The latter is True
  81. if there are no more requests to process, and false otherwise."""
  82. requests = self.generator_stack[-1]["pending_requests"]
  83. if requests:
  84. if requests[0][0] in self.handlers:
  85. # First element is a known request
  86. elem = requests.pop(0)
  87. # The list of requests might be empty now. If so, then flag this
  88. # batch of requests as finished.
  89. if not requests:
  90. self.generator_stack[-1]["finished_requests"] = True
  91. # Handle the request.
  92. self.handlers[elem[0]](elem[1])
  93. return (False, None)
  94. else:
  95. for i, elem in enumerate(requests):
  96. if elem[0] in self.handlers:
  97. # Handle any requests that precede the known request first.
  98. pre_requests = requests[:i]
  99. del requests[:i]
  100. return pre_requests
  101. # We couldn't find a known request in the batch of requests, so we might as well
  102. # handle them all at once then.
  103. self.generator_stack[-1]["finished_requests"] = True
  104. #return requests
  105. return (True, requests)
  106. def execute_call(self, request_args):
  107. """Executes a CALL-request with the given argument list."""
  108. # Format: ("CALL", [gen])
  109. gen, = request_args
  110. self.push_generator(gen)
  111. def execute_call_kwargs(self, request_args):
  112. """Executes a CALL_KWARGS-request with the given argument list."""
  113. # Format: ("CALL_KWARGS", [func, kwargs])
  114. # This format is useful because it also works for functions that
  115. # throw an exception but never yield.
  116. func, kwargs = request_args
  117. # We need to be extra careful here, because func(**kwargs) might
  118. # not be a generator at all: it might simply be a method that
  119. # raises an exception. To cope with this we need to push a dummy
  120. # entry onto the stack if a StopIteration or PrimtiveFinished
  121. # exception is thrown. The logic in execute_yields will then pop
  122. # that dummy entry.
  123. try:
  124. self.push_generator(func(**kwargs))
  125. except primitive_functions.PrimitiveFinished, StopIteration:
  126. self.push_generator(None)
  127. raise
  128. except:
  129. print("EXCEPTION for " + str(locals()))
  130. raise
  131. def execute_call_args(self, request_args):
  132. """Executes a CALL_ARGS-request with the given argument list."""
  133. # Format: ("CALL_ARGS", [gen, args])
  134. func, args = request_args
  135. # We need to be extra careful here, because func(*args) might
  136. # not be a generator at all: it might simply be a method that
  137. # raises an exception. To cope with this we need to push a dummy
  138. # entry onto the stack if a StopIteration or PrimtiveFinished
  139. # exception is thrown. The logic in execute_yields will then pop
  140. # that dummy entry.
  141. try:
  142. self.push_generator(func(*args))
  143. except primitive_functions.PrimitiveFinished, StopIteration:
  144. self.push_generator(None)
  145. raise
  146. def execute_sleep(self, request_args):
  147. """Executes a SLEEP-request with the given argument list."""
  148. self.append_reply(None)
  149. raise primitive_functions.SleepKernel(request_args[0], request_args[1])