def _delay(self): d = Deferred() ioloop.IOLoop.current().add_timeout(timedelta(seconds=self._cur_delay), lambda: d.trigger(None)) self.logger.debug('Delaying for {:.2f} s'.format(self._cur_delay)) yield d self.logger.debug('Resuming from delay...') self._cur_delay = min(self._cur_delay * self.delay_exp, self.max_delay)
def __init__(self, sock, ioLoop=None): self.address = None self.sock = sock self.sock.setblocking(False) if self.sock.type == socket.SOL_TCP: self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) fcntl.fcntl(self.sock.fileno(), fcntl.F_SETFD, fcntl.FD_CLOEXEC) self._ioLoop = ioLoop or Loop.instance() self._state = self.NOT_CONNECTED self._onConnectedDeferred = Deferred() self._connectionTimeoutTuple = None
def wrapper(*args, **kwargs): future = Deferred() timeout = kwargs.get('timeout', None) if timeout is not None: timeoutId = self._ioLoop.add_timeout(time() + timeout, lambda: future.error(TimeoutError(timeout))) def timeoutRemover(func): def wrapper(*args, **kwargs): self._ioLoop.remove_timeout(timeoutId) return func(*args, **kwargs) return wrapper future.close = timeoutRemover(future.close) self._session += 1 self._writableStream.write([methodId, self._session, args]) self._subscribers[self._session] = future return Chain([lambda: future], ioLoop=self._ioLoop)
def wrapper(*args, **kwargs): future = Deferred() timeout = kwargs.get('timeout', None) if timeout is not None: timeoutId = self._ioLoop.add_timeout( time() + timeout, lambda: future.error(TimeoutError(timeout))) def timeoutRemover(func): def wrapper(*args, **kwargs): self._ioLoop.remove_timeout(timeoutId) return func(*args, **kwargs) return wrapper future.close = timeoutRemover(future.close) self._session += 1 self._writableStream.write([methodId, self._session, args]) self._subscribers[self._session] = future return Chain([lambda: future], ioLoop=self._ioLoop)
def connect(self, address, timeout=None, blocking=False): if self.isConnecting(): return self._onConnectedDeferred if self.isConnected(): raise IllegalStateError('already connected') self._onConnectedDeferred = Deferred() self._state = self.CONNECTING if blocking: return self._blockingConnect(address, timeout) else: return self._nonBlockingConnect(address, timeout)
class Pipe(object): NOT_CONNECTED, CONNECTING, CONNECTED = range(3) def __init__(self, sock, ioLoop=None): self.address = None self.sock = sock self.sock.setblocking(False) if self.sock.type == socket.SOL_TCP: self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) fcntl.fcntl(self.sock.fileno(), fcntl.F_SETFD, fcntl.FD_CLOEXEC) self._ioLoop = ioLoop or Loop.instance() self._state = self.NOT_CONNECTED self._onConnectedDeferred = None self._connectionTimeoutTuple = None def fileno(self): return self.sock.fileno() def isConnected(self): return self._state == self.CONNECTED def isConnecting(self): return self._state == self.CONNECTING def connect(self, address, timeout=None, blocking=False): if self.isConnecting(): return self._onConnectedDeferred if self.isConnected(): raise IllegalStateError('already connected') self._onConnectedDeferred = Deferred() self._state = self.CONNECTING if blocking: return self._blockingConnect(address, timeout) else: return self._nonBlockingConnect(address, timeout) def _blockingConnect(self, address, timeout=None): try: self.sock.settimeout(timeout) self.sock.connect(address) self.address = address self._state = self.CONNECTED except socket.error as err: if err.errno == errno.ECONNREFUSED: raise ConnectionRefusedError(address) elif err.errno == errno.ETIMEDOUT: raise ConnectionTimeoutError(address, timeout) else: raise ConnectionError(address, err) finally: self.sock.setblocking(False) def _nonBlockingConnect(self, address, timeout): try: self.sock.connect(address) except socket.error as err: if err.errno in (errno.EINPROGRESS, errno.EWOULDBLOCK): callback = functools.partial(self._onConnectedCallback, address) self._ioLoop.add_handler(self.sock.fileno(), callback, self._ioLoop.WRITE) if timeout: start = time.time() errorback = functools.partial(self._onConnectionTimeout, address) timeoutId = self._ioLoop.add_timeout(start + timeout, errorback) self._connectionTimeoutTuple = timeoutId, start, timeout else: log.warning('connect error on fd {0}: {1}'.format(self.sock.fileno(), err)) self.close() self._ioLoop.add_callback(lambda: self._onConnectedDeferred.error(ConnectionError(address, err))) return self._onConnectedDeferred def close(self): if self._state == self.NOT_CONNECTED: return self._state = self.NOT_CONNECTED self.address = None self._ioLoop.stop_listening(self.sock.fileno()) self.sock.close() self.sock = None def _onConnectionTimeout(self, address): if self._connectionTimeoutTuple: timeoutId, start, timeout = self._connectionTimeoutTuple self._ioLoop.remove_timeout(timeoutId) self._connectionTimeoutTuple = None self.close() df = self._onConnectedDeferred self._onConnectedDeferred = None df.error(ConnectionTimeoutError(address, timeout)) def _onConnectedCallback(self, address, fd, event): assert fd == self.sock.fileno(), 'Incoming fd must be socket fd' assert (event & self._ioLoop.WRITE) or (event & self._ioLoop.ERROR), 'Event must be either write or error' def removeConnectionTimeout(): if self._connectionTimeoutTuple: fd, start, timeout = self._connectionTimeoutTuple self._ioLoop.remove_timeout(fd) self._connectionTimeoutTuple = None df = self._onConnectedDeferred self._onConnectedDeferred = None err = self.sock.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) if err == 0: self._state = self.CONNECTED self.address = address removeConnectionTimeout() self._ioLoop.stop_listening(self.sock.fileno()) df.trigger(None) elif err not in (errno.EINPROGRESS, errno.EAGAIN, errno.EALREADY): self.close() removeConnectionTimeout() df.error(ConnectionError(address, os.strerror(err))) def read(self, buff, size): return self._handle(self.sock.recv_into, buff, size) def write(self, buff): return self._handle(self.sock.send, buff) def _handle(self, func, *args): try: return func(*args) except socket.error as e: if e.errno in (errno.EAGAIN, errno.EWOULDBLOCK): return 0 elif e.errno in (errno.ECONNRESET, errno.ECONNABORTED, errno.EPIPE): self.close() return 0 else: raise def __del__(self): self.close()
def async_subprocess(command, callbacks=None, cwd=None, io_loop=None): """Run subprocess asynchronously and get bound `Deferred` object. This function runs separate subprocess `command` and attaches to the standard output stream (`stdout`) and error stream (`stderr`), providing ability to read them asynchronously. This can be useful when running multiple subprocesses simultaneously, e.g. in web server. You can attach up to two callbacks as list for `callbacks` parameter, first of them will be callback for `stdout`, second - for `stderr`. For example:: engine.subprocess(['echo 123'], callbacks=[sys.stdout.write, None]) means `sys.stdout` function as callback for `stdout` and there will be no callback for `stderr`. Returned `Deferred` object will trigger immediately after subprocess is finished, transferring error code as parameter. If process exits with error code differed from 0 an `IOError` exception will be thrown. An subprocess pipe exception will be raised if subprocess can not be started. .. note:: You can `yield` this function in `engine.asynchronous` context. :param command: command for subprocess to start, same as `subprocess.Popen` first argument. :param callbacks: list of two callbacks for `stdout` and `stderr` respectively. If you don't want to attach any callback, you can pass `None` as function. :param cwd: current working directory for subprocess. :param io_loop: tornado event loop, current by default. """ io_loop = io_loop or IOLoop.current() PIPE = subprocess.PIPE process = subprocess.Popen(command, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True, cwd=cwd) fhs = [process.stdout, process.stderr] deferred = Deferred() def create_handler(fh, callback): def handle(fd, events): assert events == io_loop.READ data = fh.readline() if callback is not None and data: callback(data) if process.poll() is not None: io_loop.remove_handler(fd) if process.returncode == 0: deferred.trigger(process.returncode) else: deferred.error(IOError(process.returncode)) return handle for fh, callback in zip(fhs, callbacks): io_loop.add_handler(fh.fileno(), create_handler(fh, callback), io_loop.READ) return deferred
class Pipe(object): NOT_CONNECTED, CONNECTING, CONNECTED = range(3) def __init__(self, sock, ioLoop=None): self.address = None self.sock = sock self.sock.setblocking(False) if self.sock.type == socket.SOL_TCP: self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) fcntl.fcntl(self.sock.fileno(), fcntl.F_SETFD, fcntl.FD_CLOEXEC) self._ioLoop = ioLoop or Loop.instance() self._state = self.NOT_CONNECTED self._onConnectedDeferred = Deferred() self._connectionTimeoutTuple = None def fileno(self): return self.sock.fileno() def isConnected(self): return self._state == self.CONNECTED def isConnecting(self): return self._state == self.CONNECTING def connect(self, address, timeout=None, blocking=False): if self.isConnecting(): return self._onConnectedDeferred if self.isConnected(): raise IllegalStateError('already connected') self._state = self.CONNECTING if blocking: return self._blockingConnect(address, timeout) else: return self._nonBlockingConnect(address, timeout) def _blockingConnect(self, address, timeout=None): try: self.sock.settimeout(timeout) self.sock.connect(address) self.address = address self._state = self.CONNECTED except socket.error as err: if err.errno == errno.ECONNREFUSED: raise ConnectionRefusedError(address) elif err.errno == errno.ETIMEDOUT: raise ConnectionTimeoutError(address, timeout) else: raise ConnectionError(address, err) finally: self.sock.setblocking(False) def _nonBlockingConnect(self, address, timeout): try: self.sock.connect(address) except socket.error as err: if err.errno in (errno.EINPROGRESS, errno.EWOULDBLOCK): callback = functools.partial(self._onConnectedCallback, address) self._ioLoop.add_handler(self.sock.fileno(), callback, self._ioLoop.WRITE) if timeout: start = time.time() errorback = functools.partial(self._onConnectionTimeout, address) timeoutId = self._ioLoop.add_timeout( start + timeout, errorback) self._connectionTimeoutTuple = timeoutId, start, timeout else: log.warning('connect error on fd {0}: {1}'.format( self.sock.fileno(), err)) self.close() self._ioLoop.add_callback(lambda: self._onConnectedDeferred. error(ConnectionError(address, err))) return self._onConnectedDeferred def close(self): if self._state == self.NOT_CONNECTED: return self._state = self.NOT_CONNECTED self.address = None self._ioLoop.stop_listening(self.sock.fileno()) self.sock.close() self.sock = None def _onConnectionTimeout(self, address): if self._connectionTimeoutTuple: timeoutId, start, timeout = self._connectionTimeoutTuple self._ioLoop.remove_timeout(timeoutId) self._connectionTimeoutTuple = None self.close() self._onConnectedDeferred.error( ConnectionTimeoutError(address, timeout)) def _onConnectedCallback(self, address, fd, event): assert fd == self.sock.fileno(), 'Incoming fd must be socket fd' assert (event & self._ioLoop.WRITE) or ( event & self._ioLoop.ERROR), 'Event must be either write or error' def removeConnectionTimeout(): if self._connectionTimeoutTuple: fd, start, timeout = self._connectionTimeoutTuple self._ioLoop.remove_timeout(fd) self._connectionTimeoutTuple = None err = self.sock.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) if err == 0: self._state = self.CONNECTED self.address = address removeConnectionTimeout() self._ioLoop.stop_listening(self.sock.fileno()) self._onConnectedDeferred.trigger(None) elif err not in (errno.EINPROGRESS, errno.EAGAIN, errno.EALREADY): self.close() removeConnectionTimeout() self._onConnectedDeferred.error( ConnectionError(address, os.strerror(err))) def read(self, buff, size): return self._handle(self.sock.recv_into, buff, size) def write(self, buff): return self._handle(self.sock.send, buff) def _handle(self, func, *args): try: return func(*args) except socket.error as e: if e.errno in (errno.EAGAIN, errno.EWOULDBLOCK): return 0 elif e.errno in (errno.ECONNRESET, errno.ECONNABORTED, errno.EPIPE): self.close() return 0 else: raise def __del__(self): self.close()