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
def write(self, data): """ Writes the given data to the channel. :param data: the data to be written to the channel. :type data: string :returns: An :class:`~kaa.InProgress` object which is finished when the given data is fully written to the channel. The InProgress is finished with the number of bytes sent in the last write required to commit the given data to the channel. (This may not be the actual number of bytes of the given data.) If the channel closes unexpectedly before the data was written, an IOError is thrown to the InProgress. It is not required that the channel be open in order to write to it. Written data is queued until the channel open and then flushed. As writes are asynchronous, all written data is queued. It is the caller's responsibility to ensure the internal write queue does not exceed the desired size by waiting for past write() InProgress to finish before writing more data. If a write does not complete because the channel was closed prematurely, an IOError is thrown to the InProgress. """ if not (self._mode & IO_WRITE): raise IOError(9, 'Cannot write to a read-only channel') if not self.writable: raise IOError(9, 'Channel is not writable') if self.write_queue_used + len(data) > self._queue_size: raise ValueError('Data would exceed write queue limit') inprogress = InProgress() if data: def abort(exc): try: self._write_queue.remove((data, inprogress)) except ValueError: # Too late to abort. return False inprogress.signals['abort'].connect(abort) self._write_queue.append((data, inprogress)) if self._channel and self._wmon and not self._wmon.active: self._wmon.register(self.fileno, IO_WRITE) else: # We're writing the null string, nothing really to do. We're # implicitly done. inprogress.finish(0) return inprogress