def __init__(self, socket): self.socket = socket self.interfaces = [] self.listeners = [] # List of tuples, each of which contains an # interface name and an event name. self.watches = [] # List of tuples, each of which contains an # interface name and an object name. self.message_queue = Queue() self.id = get_next_id() self.pending_responses = {} # Maps message ids of commands sent to this # connection to tuples consisting of the id of a connection that the # response should be forwarded to and the message id that the response # should have self.input_thread = InputThread(socket, self.message_arrived, self.input_closed) self.output_thread = OutputThread(socket, self.read_next_message)
class Connection(object): """ Represents a connection. Connections are created when someone connects to autobus. They are destroyed when autobus exits or the connection is closed. Connections have an id, a list of interfaces they have registered, a queue containing protobuf Message objects to be sent to the corresponding socket as soon as possible, and an input and output thread. """ def __init__(self, socket, address): self.socket = socket self.socket_address = address self.interfaces = [] self.listeners = [] # List of tuples, each of which contains an # interface name and an event name. self.watches = [] # List of tuples, each of which contains an # interface name and an object name. self.message_queue = Queue() self.id = get_next_id() self.pending_responses = {} # Maps message ids of commands sent to this # connection to tuples consisting of the id of a connection that the # response should be forwarded to and the message id that the response # should have self.input_thread = InputThread(socket, self.message_arrived, self.input_closed) self.output_thread = OutputThread(socket, self.read_next_message) def register(self): """ Registers this connection with the global connection map. This must only be called on the event thread. """ print "New connection " + str(self.id) + " from " + str(self.socket_address) connection_map[self.id] = self def message_arrived(self, message): try: function = find_function_for_message(message) except: print "Client sent an invalid command (they will be disconnected):" print_exc() raise event_queue.put((self.id, partial(function, message)), block=True) def input_closed(self): event_queue.put((self.id, discard_args(self.shutdown_and_deregister)), block=True) self.message_queue.put(None, block=True) def read_next_message(self): return self.message_queue.get(block=True) def close(self): """ Closes the underlying socket for this connection. This, in turn, causes the input and output threads to shut down. This can be called from any thread. """ self.socket.close() def send(self, message): """ Adds the specified message to this connection's message queue. It will be sent to the underlying socket as soon as possible by the connection's output thread. This can be called from any thread. If the message is None, this function returns without doing anything. This allows for code that constructs a message pair with create_message_pair and then sends the message without worrying about whether or not create_message_pair returned an empty message (which it would if it was used to "reply" to a notification instead of to a command). """ if message is not None: if isinstance(message, dict) and message["message_type"] is None: return self.message_queue.put(message, block=True) def send_new(self, *args, **kwargs): """ Exactly the same as self.send(libautobus.create_message(*args, **kwargs)). """ self.send(create_message(*args, **kwargs)) def send_error(self, in_reply_to=None, **kwargs): """ Sends an error back to the client. If in_reply_to is itself a notification message, no message will be sent back. Otherwise, an ErrorResponse to the specified message will be sent. The error's fields will be initialized to have any additional keyword arugments specified. You'll typically do: send_error(some_message, text="A random problem happened") """ if in_reply_to == None: in_reply_to = NOTIFICATION message = create_message(ErrorResponse, in_reply_to, **kwargs) self.send(message) def start(self): """ Starts this connection's input and output threads. """ self.input_thread.start() self.output_thread.start() def shutdown_and_deregister(self): """ Shuts this connection down and deregisters it. This also pushes error messages onto the message queues of any connections that were waiting for a response from this connection. This must only be called from the event thread. """ print "Connection " + str(self.id) + " from " + str(self.socket_address) + " shutting down" try: self.socket.shutdown(SHUT_RDWR) except: pass self.socket.close() if self.id in connection_map: del connection_map[self.id] for connection_id, message_id in self.pending_responses.values(): if connection_id in connection_map: connection_map[connection_id].send(create_error_response( message_id, "The connection this response was waiting " "on closed unexpectedly")) for interface in self.interfaces: interface.deregister() for interface_name, event_name in self.listeners: deregister_event_listener(interface_name, event_name, self.id) for interface_name, object_name in self.watches: deregister_object_watch(interface_name, object_name, self.id)