def __init__(self, socket_path, io_loop=None): super(IPCMessageSubscriber, self).__init__( socket_path, io_loop=io_loop) self._read_stream_future = None self._saved_data = [] self._read_in_progress = Lock() self.callbacks = set()
class IPCMessageSubscriber(IPCClient): """ Salt IPC message subscriber Create an IPC client to receive messages from IPC publisher An example of a very simple IPCMessageSubscriber connecting to an IPCMessagePublisher. This example assumes an already running IPCMessagePublisher. IMPORTANT: The below example also assumes the IOLoop is NOT running. # Import Tornado libs import salt.ext.tornado.ioloop # Import Salt libs import salt.config import salt.transport.ipc # Create a new IO Loop. # We know that this new IO Loop is not currently running. io_loop = salt.ext.tornado.ioloop.IOLoop() ipc_publisher_socket_path = '/var/run/ipc_publisher.ipc' ipc_subscriber = salt.transport.ipc.IPCMessageSubscriber(ipc_server_socket_path, io_loop=io_loop) # Connect to the server # Use the associated IO Loop that isn't running. io_loop.run_sync(ipc_subscriber.connect) # Wait for some data package = ipc_subscriber.read_sync() """ def __init__(self, socket_path, io_loop=None): super(IPCMessageSubscriber, self).__init__(socket_path, io_loop=io_loop) self._read_stream_future = None self._saved_data = [] self._read_in_progress = Lock() @salt.ext.tornado.gen.coroutine def _read(self, timeout, callback=None): try: yield self._read_in_progress.acquire(timeout=0.00000001) except salt.ext.tornado.gen.TimeoutError: raise salt.ext.tornado.gen.Return(None) exc_to_raise = None ret = None try: while True: if self._read_stream_future is None: self._read_stream_future = self.stream.read_bytes( 4096, partial=True ) if timeout is None: wire_bytes = yield self._read_stream_future else: wire_bytes = yield FutureWithTimeout( self.io_loop, self._read_stream_future, timeout ) self._read_stream_future = None # Remove the timeout once we get some data or an exception # occurs. We will assume that the rest of the data is already # there or is coming soon if an exception doesn't occur. timeout = None self.unpacker.feed(wire_bytes) first_sync_msg = True for framed_msg in self.unpacker: if callback: self.io_loop.spawn_callback(callback, framed_msg["body"]) elif first_sync_msg: ret = framed_msg["body"] first_sync_msg = False else: self._saved_data.append(framed_msg["body"]) if not first_sync_msg: # We read at least one piece of data and we're on sync run break except TornadoTimeoutError: # In the timeout case, just return None. # Keep 'self._read_stream_future' alive. ret = None except StreamClosedError as exc: log.trace("Subscriber disconnected from IPC %s", self.socket_path) self._read_stream_future = None exc_to_raise = exc except Exception as exc: # pylint: disable=broad-except log.error("Exception occurred in Subscriber while handling stream: %s", exc) self._read_stream_future = None exc_to_raise = exc self._read_in_progress.release() if exc_to_raise is not None: raise exc_to_raise # pylint: disable=E0702 raise salt.ext.tornado.gen.Return(ret) def read_sync(self, timeout=None): """ Read a message from an IPC socket The socket must already be connected. The associated IO Loop must NOT be running. :param int timeout: Timeout when receiving message :return: message data if successful. None if timed out. Will raise an exception for all other error conditions. """ if self._saved_data: return self._saved_data.pop(0) return self.io_loop.run_sync(lambda: self._read(timeout)) @salt.ext.tornado.gen.coroutine def read_async(self, callback): """ Asynchronously read messages and invoke a callback when they are ready. :param callback: A callback with the received data """ while not self.connected(): try: yield self.connect(timeout=5) except StreamClosedError: log.trace( "Subscriber closed stream on IPC %s before connect", self.socket_path, ) yield salt.ext.tornado.gen.sleep(1) except Exception as exc: # pylint: disable=broad-except log.error("Exception occurred while Subscriber connecting: %s", exc) yield salt.ext.tornado.gen.sleep(1) yield self._read(None, callback) def close(self): """ Routines to handle any cleanup before the instance shuts down. Sockets and filehandles should be closed explicitly, to prevent leaks. """ if self._closing: return super(IPCMessageSubscriber, self).close() # This will prevent this message from showing up: # '[ERROR ] Future exception was never retrieved: # StreamClosedError' if self._read_stream_future is not None and self._read_stream_future.done(): exc = self._read_stream_future.exception() if exc and not isinstance(exc, StreamClosedError): log.error("Read future returned exception %r", exc) # pylint: disable=W1701 def __del__(self): if IPCMessageSubscriber in globals(): self.close()
def __init__(self, socket_path, io_loop=None): super().__init__(socket_path, io_loop=io_loop) self._read_stream_future = None self._saved_data = [] self._read_in_progress = Lock()