class OnlyOnce(object): def __init__(self): self.tcp_server = TCPServer() try: self.tcp_server.listen(44556) except socket.error: raise OnlyOnceException() def __del__(self): if self.tcp_server is not None: self.tcp_server.stop()
def test_stop_twice(self): sock, port = bind_unused_port() server = TCPServer() server.add_socket(sock) server.stop() server.stop()
def test_stop_twice(self): sock, port = bind_unused_port() server = TCPServer() server.add_socket(sock) server.stop() server.stop()
class Service(object): """A TCP/IP listening service that responds to `denim.protocol.Msg`s. """ def __init__(self, msg_cb, port=None, host='localhost', procs=1): """Creates a new service that calls `msg_cb` with each new request received. If provided, the service will listen on `host`:`port`. Otherwise, an OS-provided port will be used. Optional parameter `procs` starts multiple processes. """ self.msg_cb = msg_cb self.port = port self.host = host self.procs = procs self.clients = {} self.pending = {} self.server = None @property def is_running(self): """Returns True if the server has been started. """ return self.server is not None def is_pending(self, msgid): """Returns True if `msgid` is awaiting a response. """ return msgid in self.pending def build_socket(self): """Builds the listener socket. If `self.port` is not set, uses an OS-assigned port. Note that the `host` and `port` values are updated by the `start` method. """ [sock] = bind_sockets(self.port, self.host, family=socket.AF_INET) host, port = sock.getsockname() return sock, port, host def start(self): """Starts the service on the configured port and host, or uses an OS-assigned port if not provided. """ sock, port, host = self.build_socket() self.port = port self.host = host self.server = TCPServer() self.server.handle_stream = self.handle_stream self.server.add_socket(sock) self.server.start(self.procs) def stop(self): """Stops the TCP/IP listening service. """ self.server.stop() self.server = None def handle_stream(self, stream, addr): """Triggered by the listener when a new client connects. This method starts the read loop. """ pipe = Pipe(stream, self.cleanup, self.on_receive) self.clients[pipe.fd] = pipe def cleanup(self, client): """Cleans up after a client connection. """ del self.clients[client.fd] for msgid in self.pending.keys(): if self.pending[msgid] == client.fd: # TODO trigger some sort of cancel callback so the logic # managing the Service can deal with a cancelled msg. del self.pending[msgid] def on_receive(self, msg, client): """Receives a line of data from the stream referred to by `addr`. The `msg_cb` callback is then called with the message object, the pipe from which the message was received, and a reference to the service itself. """ self.pending[msg.msgid] = client.fd self.msg_cb(msg, client, self) def reply(self, msg): """This method is used by the caller to respond to a `Msg` that has been received. A `KeyError` is raised if the message id is not recognized. """ if not self.is_pending(msg.msgid): raise KeyError('msgid not recognized') fd = self.pending[msg.msgid] pipe = self.clients[fd] pipe.send(msg) del self.pending[msg.msgid] def steal_pipe(self, pipe): """Allows the caller to "steal" a `Pipe` that is currently being managed as a client. This is particularly useful if a service wishes to handle certain clients differently (such as a `Manager` does when a `Worker` registers.) """ self.cleanup(pipe)