Update to Tornado 3.0
This commit is contained in:
+161
-80
@@ -36,11 +36,12 @@ import logging
|
||||
import numbers
|
||||
import os
|
||||
import select
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from tornado.concurrent import DummyFuture
|
||||
from tornado.concurrent import Future, TracebackFuture
|
||||
from tornado.log import app_log, gen_log
|
||||
from tornado import stack_context
|
||||
from tornado.util import Configurable
|
||||
@@ -50,11 +51,6 @@ try:
|
||||
except ImportError:
|
||||
signal = None
|
||||
|
||||
try:
|
||||
from concurrent import futures
|
||||
except ImportError:
|
||||
futures = None
|
||||
|
||||
try:
|
||||
import thread # py2
|
||||
except ImportError:
|
||||
@@ -63,14 +59,18 @@ except ImportError:
|
||||
from tornado.platform.auto import set_close_exec, Waker
|
||||
|
||||
|
||||
class TimeoutError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class IOLoop(Configurable):
|
||||
"""A level-triggered I/O loop.
|
||||
|
||||
We use epoll (Linux) or kqueue (BSD and Mac OS X; requires python
|
||||
2.6+) if they are available, or else we fall back on select(). If
|
||||
you are implementing a system that needs to handle thousands of
|
||||
simultaneous connections, you should use a system that supports either
|
||||
epoll or kqueue.
|
||||
We use ``epoll`` (Linux) or ``kqueue`` (BSD and Mac OS X) if they
|
||||
are available, or else we fall back on select(). If you are
|
||||
implementing a system that needs to handle thousands of
|
||||
simultaneous connections, you should use a system that supports
|
||||
either ``epoll`` or ``kqueue``.
|
||||
|
||||
Example usage for a simple TCP server::
|
||||
|
||||
@@ -125,19 +125,11 @@ class IOLoop(Configurable):
|
||||
|
||||
@staticmethod
|
||||
def instance():
|
||||
"""Returns a global IOLoop instance.
|
||||
"""Returns a global `IOLoop` instance.
|
||||
|
||||
Most single-threaded applications have a single, global IOLoop.
|
||||
Use this method instead of passing around IOLoop instances
|
||||
throughout your code.
|
||||
|
||||
A common pattern for classes that depend on IOLoops is to use
|
||||
a default argument to enable programs with multiple IOLoops
|
||||
but not require the argument for simpler applications::
|
||||
|
||||
class MyClass(object):
|
||||
def __init__(self, io_loop=None):
|
||||
self.io_loop = io_loop or IOLoop.instance()
|
||||
Most applications have a single, global `IOLoop` running on the
|
||||
main thread. Use this method to get this instance from
|
||||
another thread. To get the current thread's `IOLoop`, use `current()`.
|
||||
"""
|
||||
if not hasattr(IOLoop, "_instance"):
|
||||
with IOLoop._instance_lock:
|
||||
@@ -152,27 +144,54 @@ class IOLoop(Configurable):
|
||||
return hasattr(IOLoop, "_instance")
|
||||
|
||||
def install(self):
|
||||
"""Installs this IOloop object as the singleton instance.
|
||||
"""Installs this `IOLoop` object as the singleton instance.
|
||||
|
||||
This is normally not necessary as `instance()` will create
|
||||
an IOLoop on demand, but you may want to call `install` to use
|
||||
a custom subclass of IOLoop.
|
||||
an `IOLoop` on demand, but you may want to call `install` to use
|
||||
a custom subclass of `IOLoop`.
|
||||
"""
|
||||
assert not IOLoop.initialized()
|
||||
IOLoop._instance = self
|
||||
|
||||
@staticmethod
|
||||
def current():
|
||||
"""Returns the current thread's `IOLoop`.
|
||||
|
||||
If an `IOLoop` is currently running or has been marked as current
|
||||
by `make_current`, returns that instance. Otherwise returns
|
||||
`IOLoop.instance()`, i.e. the main thread's `IOLoop`.
|
||||
|
||||
A common pattern for classes that depend on ``IOLoops`` is to use
|
||||
a default argument to enable programs with multiple ``IOLoops``
|
||||
but not require the argument for simpler applications::
|
||||
|
||||
class MyClass(object):
|
||||
def __init__(self, io_loop=None):
|
||||
self.io_loop = io_loop or IOLoop.current()
|
||||
|
||||
In general you should use `IOLoop.current` as the default when
|
||||
constructing an asynchronous object, and use `IOLoop.instance`
|
||||
when you mean to communicate to the main thread from a different
|
||||
one.
|
||||
"""
|
||||
current = getattr(IOLoop._current, "instance", None)
|
||||
if current is None:
|
||||
raise ValueError("no current IOLoop")
|
||||
return IOLoop.instance()
|
||||
return current
|
||||
|
||||
def make_current(self):
|
||||
"""Makes this the `IOLoop` for the current thread.
|
||||
|
||||
An `IOLoop` automatically becomes current for its thread
|
||||
when it is started, but it is sometimes useful to call
|
||||
`make_current` explictly before starting the `IOLoop`,
|
||||
so that code run at startup time can find the right
|
||||
instance.
|
||||
"""
|
||||
IOLoop._current.instance = self
|
||||
|
||||
def clear_current(self):
|
||||
assert IOLoop._current.instance is self
|
||||
@staticmethod
|
||||
def clear_current():
|
||||
IOLoop._current.instance = None
|
||||
|
||||
@classmethod
|
||||
@@ -195,19 +214,20 @@ class IOLoop(Configurable):
|
||||
pass
|
||||
|
||||
def close(self, all_fds=False):
|
||||
"""Closes the IOLoop, freeing any resources used.
|
||||
"""Closes the `IOLoop`, freeing any resources used.
|
||||
|
||||
If ``all_fds`` is true, all file descriptors registered on the
|
||||
IOLoop will be closed (not just the ones created by the IOLoop itself).
|
||||
IOLoop will be closed (not just the ones created by the
|
||||
`IOLoop` itself).
|
||||
|
||||
Many applications will only use a single IOLoop that runs for the
|
||||
entire lifetime of the process. In that case closing the IOLoop
|
||||
Many applications will only use a single `IOLoop` that runs for the
|
||||
entire lifetime of the process. In that case closing the `IOLoop`
|
||||
is not necessary since everything will be cleaned up when the
|
||||
process exits. `IOLoop.close` is provided mainly for scenarios
|
||||
such as unit tests, which create and destroy a large number of
|
||||
IOLoops.
|
||||
``IOLoops``.
|
||||
|
||||
An IOLoop must be completely stopped before it can be closed. This
|
||||
An `IOLoop` must be completely stopped before it can be closed. This
|
||||
means that `IOLoop.stop()` must be called *and* `IOLoop.start()` must
|
||||
be allowed to return before attempting to call `IOLoop.close()`.
|
||||
Therefore the call to `close` will usually appear just after
|
||||
@@ -216,7 +236,13 @@ class IOLoop(Configurable):
|
||||
raise NotImplementedError()
|
||||
|
||||
def add_handler(self, fd, handler, events):
|
||||
"""Registers the given handler to receive the given events for fd."""
|
||||
"""Registers the given handler to receive the given events for fd.
|
||||
|
||||
The ``events`` argument is a bitwise or of the constants
|
||||
``IOLoop.READ``, ``IOLoop.WRITE``, and ``IOLoop.ERROR``.
|
||||
|
||||
When an event occurs, ``handler(fd, events)`` will be run.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def update_handler(self, fd, events):
|
||||
@@ -228,28 +254,32 @@ class IOLoop(Configurable):
|
||||
raise NotImplementedError()
|
||||
|
||||
def set_blocking_signal_threshold(self, seconds, action):
|
||||
"""Sends a signal if the ioloop is blocked for more than s seconds.
|
||||
"""Sends a signal if the `IOLoop` is blocked for more than
|
||||
``s`` seconds.
|
||||
|
||||
Pass seconds=None to disable. Requires python 2.6 on a unixy
|
||||
Pass ``seconds=None`` to disable. Requires Python 2.6 on a unixy
|
||||
platform.
|
||||
|
||||
The action parameter is a python signal handler. Read the
|
||||
documentation for the python 'signal' module for more information.
|
||||
If action is None, the process will be killed if it is blocked for
|
||||
too long.
|
||||
The action parameter is a Python signal handler. Read the
|
||||
documentation for the `signal` module for more information.
|
||||
If ``action`` is None, the process will be killed if it is
|
||||
blocked for too long.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def set_blocking_log_threshold(self, seconds):
|
||||
"""Logs a stack trace if the ioloop is blocked for more than s seconds.
|
||||
Equivalent to set_blocking_signal_threshold(seconds, self.log_stack)
|
||||
"""Logs a stack trace if the `IOLoop` is blocked for more than
|
||||
``s`` seconds.
|
||||
|
||||
Equivalent to ``set_blocking_signal_threshold(seconds,
|
||||
self.log_stack)``
|
||||
"""
|
||||
self.set_blocking_signal_threshold(seconds, self.log_stack)
|
||||
|
||||
def log_stack(self, signal, frame):
|
||||
"""Signal handler to log the stack trace of the current thread.
|
||||
|
||||
For use with set_blocking_signal_threshold.
|
||||
For use with `set_blocking_signal_threshold`.
|
||||
"""
|
||||
gen_log.warning('IOLoop blocked for %f seconds in\n%s',
|
||||
self._blocking_signal_threshold,
|
||||
@@ -258,7 +288,7 @@ class IOLoop(Configurable):
|
||||
def start(self):
|
||||
"""Starts the I/O loop.
|
||||
|
||||
The loop will run until one of the I/O handlers calls stop(), which
|
||||
The loop will run until one of the callbacks calls `stop()`, which
|
||||
will make the loop stop after the current event iteration completes.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
@@ -266,7 +296,7 @@ class IOLoop(Configurable):
|
||||
def stop(self):
|
||||
"""Stop the I/O loop.
|
||||
|
||||
If the event loop is not currently running, the next call to start()
|
||||
If the event loop is not currently running, the next call to `start()`
|
||||
will return immediately.
|
||||
|
||||
To use asynchronous methods from otherwise-synchronous code (such as
|
||||
@@ -276,23 +306,71 @@ class IOLoop(Configurable):
|
||||
async_method(ioloop=ioloop, callback=ioloop.stop)
|
||||
ioloop.start()
|
||||
|
||||
ioloop.start() will return after async_method has run its callback,
|
||||
whether that callback was invoked before or after ioloop.start.
|
||||
``ioloop.start()`` will return after ``async_method`` has run
|
||||
its callback, whether that callback was invoked before or
|
||||
after ``ioloop.start``.
|
||||
|
||||
Note that even after `stop` has been called, the IOLoop is not
|
||||
Note that even after `stop` has been called, the `IOLoop` is not
|
||||
completely stopped until `IOLoop.start` has also returned.
|
||||
Some work that was scheduled before the call to `stop` may still
|
||||
be run before the IOLoop shuts down.
|
||||
be run before the `IOLoop` shuts down.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def run_sync(self, func, timeout=None):
|
||||
"""Starts the `IOLoop`, runs the given function, and stops the loop.
|
||||
|
||||
If the function returns a `.Future`, the `IOLoop` will run
|
||||
until the future is resolved. If it raises an exception, the
|
||||
`IOLoop` will stop and the exception will be re-raised to the
|
||||
caller.
|
||||
|
||||
The keyword-only argument ``timeout`` may be used to set
|
||||
a maximum duration for the function. If the timeout expires,
|
||||
a `TimeoutError` is raised.
|
||||
|
||||
This method is useful in conjunction with `tornado.gen.coroutine`
|
||||
to allow asynchronous calls in a ``main()`` function::
|
||||
|
||||
@gen.coroutine
|
||||
def main():
|
||||
# do stuff...
|
||||
|
||||
if __name__ == '__main__':
|
||||
IOLoop.instance().run_sync(main)
|
||||
"""
|
||||
future_cell = [None]
|
||||
|
||||
def run():
|
||||
try:
|
||||
result = func()
|
||||
except Exception:
|
||||
future_cell[0] = TracebackFuture()
|
||||
future_cell[0].set_exc_info(sys.exc_info())
|
||||
else:
|
||||
if isinstance(result, Future):
|
||||
future_cell[0] = result
|
||||
else:
|
||||
future_cell[0] = Future()
|
||||
future_cell[0].set_result(result)
|
||||
self.add_future(future_cell[0], lambda future: self.stop())
|
||||
self.add_callback(run)
|
||||
if timeout is not None:
|
||||
timeout_handle = self.add_timeout(self.time() + timeout, self.stop)
|
||||
self.start()
|
||||
if timeout is not None:
|
||||
self.remove_timeout(timeout_handle)
|
||||
if not future_cell[0].done():
|
||||
raise TimeoutError('Operation timed out after %s seconds' % timeout)
|
||||
return future_cell[0].result()
|
||||
|
||||
def time(self):
|
||||
"""Returns the current time according to the IOLoop's clock.
|
||||
"""Returns the current time according to the `IOLoop`'s clock.
|
||||
|
||||
The return value is a floating-point number relative to an
|
||||
unspecified time in the past.
|
||||
|
||||
By default, the IOLoop's time function is `time.time`. However,
|
||||
By default, the `IOLoop`'s time function is `time.time`. However,
|
||||
it may be configured to use e.g. `time.monotonic` instead.
|
||||
Calls to `add_timeout` that pass a number instead of a
|
||||
`datetime.timedelta` should use this function to compute the
|
||||
@@ -302,24 +380,26 @@ class IOLoop(Configurable):
|
||||
return time.time()
|
||||
|
||||
def add_timeout(self, deadline, callback):
|
||||
"""Calls the given callback at the time deadline from the I/O loop.
|
||||
"""Runs the ``callback`` at the time ``deadline`` from the I/O loop.
|
||||
|
||||
Returns a handle that may be passed to remove_timeout to cancel.
|
||||
Returns an opaque handle that may be passed to
|
||||
`remove_timeout` to cancel.
|
||||
|
||||
``deadline`` may be a number denoting a time relative to
|
||||
`IOLoop.time`, or a ``datetime.timedelta`` object for a
|
||||
deadline relative to the current time.
|
||||
``deadline`` may be a number denoting a time (on the same
|
||||
scale as `IOLoop.time`, normally `time.time`), or a
|
||||
`datetime.timedelta` object for a deadline relative to the
|
||||
current time.
|
||||
|
||||
Note that it is not safe to call `add_timeout` from other threads.
|
||||
Instead, you must use `add_callback` to transfer control to the
|
||||
IOLoop's thread, and then call `add_timeout` from there.
|
||||
`IOLoop`'s thread, and then call `add_timeout` from there.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def remove_timeout(self, timeout):
|
||||
"""Cancels a pending timeout.
|
||||
|
||||
The argument is a handle as returned by add_timeout. It is
|
||||
The argument is a handle as returned by `add_timeout`. It is
|
||||
safe to call `remove_timeout` even if the callback has already
|
||||
been run.
|
||||
"""
|
||||
@@ -329,11 +409,11 @@ class IOLoop(Configurable):
|
||||
"""Calls the given callback on the next I/O loop iteration.
|
||||
|
||||
It is safe to call this method from any thread at any time,
|
||||
except from a signal handler. Note that this is the *only*
|
||||
method in IOLoop that makes this thread-safety guarantee; all
|
||||
other interaction with the IOLoop must be done from that
|
||||
IOLoop's thread. add_callback() may be used to transfer
|
||||
control from other threads to the IOLoop's thread.
|
||||
except from a signal handler. Note that this is the **only**
|
||||
method in `IOLoop` that makes this thread-safety guarantee; all
|
||||
other interaction with the `IOLoop` must be done from that
|
||||
`IOLoop`'s thread. `add_callback()` may be used to transfer
|
||||
control from other threads to the `IOLoop`'s thread.
|
||||
|
||||
To add a callback from a signal handler, see
|
||||
`add_callback_from_signal`.
|
||||
@@ -347,22 +427,19 @@ class IOLoop(Configurable):
|
||||
otherwise.
|
||||
|
||||
Callbacks added with this method will be run without any
|
||||
stack_context, to avoid picking up the context of the function
|
||||
`.stack_context`, to avoid picking up the context of the function
|
||||
that was interrupted by the signal.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
if futures is not None:
|
||||
_FUTURE_TYPES = (futures.Future, DummyFuture)
|
||||
else:
|
||||
_FUTURE_TYPES = DummyFuture
|
||||
|
||||
def add_future(self, future, callback):
|
||||
"""Schedules a callback on the IOLoop when the given future is finished.
|
||||
"""Schedules a callback on the ``IOLoop`` when the given
|
||||
`.Future` is finished.
|
||||
|
||||
The callback is invoked with one argument, the future.
|
||||
The callback is invoked with one argument, the
|
||||
`.Future`.
|
||||
"""
|
||||
assert isinstance(future, IOLoop._FUTURE_TYPES)
|
||||
assert isinstance(future, Future)
|
||||
callback = stack_context.wrap(callback)
|
||||
future.add_done_callback(
|
||||
lambda future: self.add_callback(callback, future))
|
||||
@@ -378,14 +455,14 @@ class IOLoop(Configurable):
|
||||
self.handle_callback_exception(callback)
|
||||
|
||||
def handle_callback_exception(self, callback):
|
||||
"""This method is called whenever a callback run by the IOLoop
|
||||
"""This method is called whenever a callback run by the `IOLoop`
|
||||
throws an exception.
|
||||
|
||||
By default simply logs the exception as an error. Subclasses
|
||||
may override this method to customize reporting of exceptions.
|
||||
|
||||
The exception itself is not passed explicitly, but is available
|
||||
in sys.exc_info.
|
||||
in `sys.exc_info`.
|
||||
"""
|
||||
app_log.error("Exception in callback %r", callback, exc_info=True)
|
||||
|
||||
@@ -428,7 +505,11 @@ class PollIOLoop(IOLoop):
|
||||
if all_fds:
|
||||
for fd in self._handlers.keys():
|
||||
try:
|
||||
os.close(fd)
|
||||
close_method = getattr(fd, 'close', None)
|
||||
if close_method is not None:
|
||||
close_method()
|
||||
else:
|
||||
os.close(fd)
|
||||
except Exception:
|
||||
gen_log.debug("error closing fd %s", fd, exc_info=True)
|
||||
self._waker.close()
|
||||
@@ -684,16 +765,16 @@ class _Timeout(object):
|
||||
class PeriodicCallback(object):
|
||||
"""Schedules the given callback to be called periodically.
|
||||
|
||||
The callback is called every callback_time milliseconds.
|
||||
The callback is called every ``callback_time`` milliseconds.
|
||||
|
||||
`start` must be called after the PeriodicCallback is created.
|
||||
`start` must be called after the `PeriodicCallback` is created.
|
||||
"""
|
||||
def __init__(self, callback, callback_time, io_loop=None):
|
||||
self.callback = callback
|
||||
if callback_time <= 0:
|
||||
raise ValueError("Periodic callback must have a positive callback_time")
|
||||
self.callback_time = callback_time
|
||||
self.io_loop = io_loop or IOLoop.instance()
|
||||
self.io_loop = io_loop or IOLoop.current()
|
||||
self._running = False
|
||||
self._timeout = None
|
||||
|
||||
|
||||
Reference in New Issue
Block a user