def close(self, immediate=False, expected=True): """ Closes the channel. :param immediate: if False and there is data in the write buffer, the channel is closed once the write buffer is emptied. Otherwise the channel is closed immediately and the *closed* signal is emitted. :type immediate: bool """ log.debug('IOChannel closed: channel=%s, immediate=%s, fd=%s', self, immediate, self.fileno) if not self._rmon and not self._wmon: # already closed return if not immediate and self._write_queue: # Immediate close not requested and we have some data left # to be written, so defer close until after write queue # is empty. self._queue_close = True return if self._rmon: self._rmon.unregister() if self._wmon: self._wmon.unregister() self._rmon = None self._wmon = None self._queue_close = False # Set _closing flag in case any callbacks connected to any pending # read/write InProgress objects test the 'alive' property, which we # want to be False. Yet we don't want to actually _close() before # finishing the pending InProgress in case _close() raises, which # we want to let propagate. self._closing = True # Finish any InProgress waiting on read() or readline() with whatever # is left in the read queue. s = self._read_queue.getvalue() self._read_signal.emit(s) self._readline_signal.emit(s) self._clear_read_queue() # Throw IOError to any pending InProgress in the write queue for data, inprogress in self._write_queue: if len(inprogress): # Somebody cares about this InProgress, so we need to finish # it. inprogress.throw(IOError, IOError(9, 'Channel closed prematurely'), None) del self._write_queue[:] try: self._close() except (IOError, socket.error), (errno, msg): # Channel may already be closed, which is ok. if errno != 9: # It isn't, this is some other error, so reraise exception. raise
def _handle_write(self): """ IOMonitor callback when the channel is writable. This callback is not registered then the write queue is empty, so we only get called when there is something to write. """ if not self._write_queue: # Can happen if a write was aborted. return try: while self._write_queue: data, inprogress = self._write_queue.pop(0) sent = self._write(data) log.debug2('IOChannel write data: channel=%s fd=%s len=%d (of %d)', self._channel, self.fileno, sent, len(data)) if sent != len(data): # Not all data was able to be sent; push remaining data # back onto the write buffer. self._write_queue.insert(0, (data[(sent if sent >= 0 else 0):], inprogress)) break else: # All data is written, finish the InProgress associated # with this write. inprogress.finish(sent) if not self._write_queue: if self._queue_close: return self.close(immediate=True) self._wmon.unregister() except Exception, e: tp, exc, tb = sys.exc_info() if tp in (OSError, IOError, socket.error) and e.args[0] == 11: # Resource temporarily unavailable -- we are trying to write # data to a socket which is not ready. To prevent a busy loop # (mainloop will keep calling us back) we sleep a tiny # bit. It's admittedly a bit kludgy, but it's a simple # solution to a condition which should not occur often. self._write_queue.insert(0, (data, inprogress)) time.sleep(0.001) return if tp in (IOError, socket.error, OSError): # Any of these are treated as fatal. We close, which # also throws to any other pending InProgress writes. self.close(immediate=True, expected=False) # Normalize exception into an IOError. tp, exc = IOError, IOError(*e.args) # Throw the current exception to the InProgress for this write. # If nobody is listening for it, it will eventually get logged # as unhandled. inprogress.throw(tp, exc, tb) # XXX: this seems to be necessary in order to get the unhandled # InProgress to log, but I've no idea why. del inprogress