def enter_main_loop(self): """Create and maintain active channels for all running guests.""" with GracefulShutdown(): try: while True: self.update_workers() time.sleep(10) finally: for channel in self.workers.values(): channel.terminate()
def raw_readline(self): """ Read a newline terminated string from the remote side. This method overrides the :py:func:`~negotiator_common.NegotiatorInterface.raw_readline()` method of the :py:func:`~negotiator_common.NegotiatorInterface` class to implement blocking reads based on :py:data:`os.O_ASYNC` and :py:data:`signal.SIGIO` (see also :py:class:`WaitForRead`). :returns: The data read from the remote side (a string). """ while True: # Check if the channel contains data. logger.debug("Preparing to read line from %s ..", self.conn_label) data = self.conn_handle.readline() if data: break # If the readline() above returns an empty string the channel # is (probably) not connected. At this point we'll bother to # prepare a convoluted way to block until the channel does # become connected. logger.debug("Got an empty read, emulating blocking read of %s ..", self.conn_label) # Set the O_ASYNC flag on the file descriptor connected to the # character device (this is required to use SIGIO signals). flags = fcntl.fcntl(self.conn_handle, fcntl.F_GETFL) fcntl.fcntl(self.conn_handle, fcntl.F_SETFL, flags | os.O_ASYNC) # Spawn a subprocess to reliably handle SIGIO signals. Due to the # nature of (SIGIO) signals more than one signal may be delivered # and this is a big problem when you want to do more than just call # sys.exit(). The alternative to this would be signal.pause() but # that function has an inherent race condition. To fix that race # condition there is sigsuspend() but this function is not # available in the Python standard library. waiter = WaitForRead() # If we get killed we need to make sure we take the subprocess # down with us, otherwise the subprocess may still be reading # from the character device when we are restarted and that's a # problem because the character device doesn't allow multiple # readers; all but the first reader will get the error # `IOError: [Errno 16] Device or resource busy'. with GracefulShutdown(): try: # Start the subprocess. waiter.start() # Connect the file descriptor to the subprocess. fcntl.fcntl(self.conn_handle, fcntl.F_SETOWN, waiter.pid) # The channel may have become connected after we last got an empty # read but before we spawned our subprocess, so check one more # time to make sure. data = self.conn_handle.readline() if data: break # If there is still no data available we'll wait for the # subprocess to indicate that data has become available. waiter.join() # Let's see if the subprocess is right :-) data = self.conn_handle.readline() if data: break finally: logger.debug( "Terminating subprocess with process id %i ..", waiter.pid) waiter.terminate() # If the convoluted way to simulate blocking reads above ever # fails we don't want this method to turn into a `busy loop'. logger.debug( "Blocking read emulation seems to have failed, falling back to 1 second polling interval .." ) time.sleep(1) logger.debug("Read %i bytes from %s: %r", len(data), self.conn_label, data) return data