def request(self, event: EventEnvelope, timeout_seconds: float): timeout_value = self.util.get_float(timeout_seconds) if timeout_value <= 0: raise ValueError( 'timeout value in seconds must be positive number') if not isinstance(event, EventEnvelope): raise ValueError('event object must be an EventEnvelope') # restore distributed tracing info from current thread trace_info = self.get_trace() if trace_info: if trace_info.get_route() is not None and event.get_from() is None: event.set_from(trace_info.get_route()) if trace_info.get_id() is not None and trace_info.get_path( ) is not None: event.set_trace(trace_info.get_id(), trace_info.get_path()) # emulate RPC inbox = Inbox(self) temp_route = inbox.get_route() inbox_queue = inbox.get_queue() try: route = event.get_to() event.set_reply_to(temp_route, me=True) if route in self._function_queues: self._loop.call_soon_threadsafe(self._send, route, event.to_map()) else: if self._cloud.is_connected(): self._cloud.send_payload({ 'type': 'event', 'event': event.to_map() }) else: raise ValueError(f'route {route} not found') # wait until response event is delivered to the inbox return inbox_queue.get(True, timeout_value) except Empty: raise TimeoutError( f'Route {event.get_to()} timeout for {round(timeout_value, 3)} seconds' ) finally: inbox.close()
def send_event(self, event: EventEnvelope, broadcast=False) -> None: if not isinstance(event, EventEnvelope): raise ValueError("event object must be an EventEnvelope class") # restore distributed tracing info from current thread trace_info = self.get_trace() if trace_info: if trace_info.get_route() is not None and event.get_from() is None: event.set_from(trace_info.get_route()) if trace_info.get_id() is not None and trace_info.get_path( ) is not None: event.set_trace(trace_info.get_id(), trace_info.get_path()) # regulate rate for best performance self._seq += 1 self._throttle.regulate_rate(self._seq) route = event.get_to() if broadcast: event.set_broadcast(True) reply_to = event.get_reply_to() if reply_to: target = reply_to[2:] if reply_to.startswith('->') else reply_to if route == target: raise ValueError("route and reply_to must not be the same") if route in self._function_queues: if event.is_broadcast() and self._cloud.is_connected(): self._cloud.send_payload({ 'type': 'event', 'event': event.to_map() }) else: self._loop.call_soon_threadsafe(self._send, route, event.to_map()) else: if self._cloud.is_connected(): self._cloud.send_payload({ 'type': 'event', 'event': event.to_map() }) else: raise ValueError("route " + route + " not found")
def handle_event(self, event, instance): headers = dict() if 'headers' not in event else event['headers'] body = None if 'body' not in event else event['body'] result = None error_code = None error_msg = None # start distributed tracing if the event contains trace_id and trace_path if 'trace_id' in event and 'trace_path' in event: self.platform.start_tracing(self.route, trace_id=event['trace_id'], trace_path=event['trace_path']) else: self.platform.start_tracing(self.route) # execute user function begin = end = time.time() try: if instance == 0: # service is an interceptor. e.g. inbox for RPC call self.user_function(EventEnvelope().from_map(event)) elif instance == -1: # service is a singleton result = self.user_function(headers, body) else: # service with multiple instances result = self.user_function(headers, body, instance) end = time.time() except AppException as e: error_code = e.get_status() error_msg = e.get_message() except ValueError as e: error_code = 400 error_msg = str(e) except Exception as e: error_code = 500 error_msg = str(e) # execution time is rounded to 3 decimal points exec_time = float(format((end - begin) * 1000, '.3f')) if error_code: if 'reply_to' in event: # set exception as result result = EventEnvelope().set_status(error_code).set_body( error_msg) else: self.log.warn("Unhandled exception for " + self.route + " - code=" + str(error_code) + ", message=" + error_msg) # # interceptor should not send regular response because it will forward the request to another function. # However, if error_code exists, the system will send the exception response. # This allows interceptor to simply throw exception to indicate an error case. # if 'reply_to' in event and (error_code or not self.interceptor): reply_to = event['reply_to'] # in case this is a RPC call from within if reply_to.startswith('->'): reply_to = reply_to[2:] response = EventEnvelope().set_to(reply_to) if not error_code: response.set_exec_time(exec_time, False) if 'extra' in event: response.set_extra(event['extra']) if 'cid' in event: response.set_correlation_id(event['cid']) if 'trace_id' in event and 'trace_path' in event: response.set_trace(event['trace_id'], event['trace_path']) if isinstance(result, EventEnvelope): for h in result.get_headers(): response.set_header(h, result.get_header(h)) response.set_body(result.get_body()) response.set_status(result.get_status()) else: response.set_body(result) try: self.platform.send_event(response.set_from(self.route)) except Exception as e: self.log.warn("Event dropped because " + str(e)) # send tracing info to distributed trace logger trace_info = self.platform.stop_tracing() if self.tracing and trace_info is not None and isinstance(trace_info, TraceInfo) \ and trace_info.get_id() is not None and trace_info.get_path() is not None: dt = EventEnvelope().set_to(self.DISTRIBUTED_TRACING).set_body( trace_info.get_annotations()) dt.set_header('origin', self.platform.get_origin()) dt.set_header('id', trace_info.get_id()).set_header( 'path', trace_info.get_path()) dt.set_header('service', self.route).set_header('start', trace_info.get_start_time()) if not error_code: dt.set_header('success', 'true') dt.set_header('exec_time', exec_time) else: dt.set_header('success', 'false') dt.set_header('status', error_code) dt.set_header('exception', error_msg) self.platform.send_event(dt) self._loop.call_soon_threadsafe(self._ack)