Repository: tornadoweb/tornado

Issue:
Too many TimeoutErrors
This would probably warrant unifying.
```
>>> ioloop.TimeoutError
tornado.ioloop.TimeoutError
>>> gen.TimeoutError
tornado.gen.TimeoutError
>>> gen.TimeoutError is ioloop.TimeoutError
False
```


Discussion:

Patch:
diff --git a/docs/gen.rst b/docs/gen.rst
index 52c7f55d..cf42269f 100644
--- a/docs/gen.rst
+++ b/docs/gen.rst
@@ -21,7 +21,6 @@
    .. autoexception:: Return
 
    .. autofunction:: with_timeout
-   .. autoexception:: TimeoutError
 
    .. autofunction:: sleep
 
diff --git a/docs/releases/v4.2.0.rst b/docs/releases/v4.2.0.rst
index d9484379..93493ee1 100644
--- a/docs/releases/v4.2.0.rst
+++ b/docs/releases/v4.2.0.rst
@@ -49,7 +49,7 @@ be unlocked *without* acquiring it. This encouraged unorthodox patterns; in
 Tornado, just use `~.Semaphore.acquire`.
 
 Toro's ``Event.wait`` raised a ``Timeout`` exception after a timeout. In
-Tornado, `.Event.wait` raises `tornado.gen.TimeoutError`.
+Tornado, `.Event.wait` raises ``tornado.gen.TimeoutError``.
 
 Toro's ``Condition.wait`` also raised ``Timeout``, but in Tornado, the `.Future`
 returned by `.Condition.wait` resolves to False after a timeout::
diff --git a/tornado/gen.py b/tornado/gen.py
index 2e9e9a6a..5558f986 100644
--- a/tornado/gen.py
+++ b/tornado/gen.py
@@ -89,7 +89,7 @@ from tornado.concurrent import Future, TracebackFuture, is_future, chain_future
 from tornado.ioloop import IOLoop
 from tornado.log import app_log
 from tornado import stack_context
-from tornado.util import PY3, raise_exc_info
+from tornado.util import PY3, raise_exc_info, TimeoutError
 
 try:
     try:
@@ -154,10 +154,6 @@ class ReturnValueIgnoredError(Exception):
     pass
 
 
-class TimeoutError(Exception):
-    """Exception raised by ``with_timeout``."""
-
-
 def _value_from_stopiteration(e):
     try:
         # StopIteration has a value attribute beginning in py33.
@@ -871,10 +867,10 @@ def maybe_future(x):
 def with_timeout(timeout, future, quiet_exceptions=()):
     """Wraps a `.Future` (or other yieldable object) in a timeout.
 
-    Raises `TimeoutError` if the input future does not complete before
-    ``timeout``, which may be specified in any form allowed by
-    `.IOLoop.add_timeout` (i.e. a `datetime.timedelta` or an absolute time
-    relative to `.IOLoop.time`)
+    Raises `tornado.util.TimeoutError` if the input future does not
+    complete before ``timeout``, which may be specified in any form
+    allowed by `.IOLoop.add_timeout` (i.e. a `datetime.timedelta` or
+    an absolute time relative to `.IOLoop.time`)
 
     If the wrapped `.Future` fails after it has timed out, the exception
     will be logged unless it is of a type contained in ``quiet_exceptions``
diff --git a/tornado/ioloop.py b/tornado/ioloop.py
index 0527e922..73d0cbdb 100644
--- a/tornado/ioloop.py
+++ b/tornado/ioloop.py
@@ -48,7 +48,7 @@ from tornado.concurrent import TracebackFuture, is_future
 from tornado.log import app_log, gen_log
 from tornado.platform.auto import set_close_exec, Waker
 from tornado import stack_context
-from tornado.util import PY3, Configurable, errno_from_exception, timedelta_to_seconds
+from tornado.util import PY3, Configurable, errno_from_exception, timedelta_to_seconds, TimeoutError
 
 try:
     import signal
@@ -70,10 +70,6 @@ except ImportError:
 _POLL_TIMEOUT = 3600.0
 
 
-class TimeoutError(Exception):
-    pass
-
-
 class IOLoop(Configurable):
     """A level-triggered I/O loop.
 
@@ -457,7 +453,7 @@ class IOLoop(Configurable):
 
         The keyword-only argument ``timeout`` may be used to set
         a maximum duration for the function.  If the timeout expires,
-        a `TimeoutError` is raised.
+        a `tornado.util.TimeoutError` is raised.
 
         This method is useful in conjunction with `tornado.gen.coroutine`
         to allow asynchronous calls in a ``main()`` function::
diff --git a/tornado/locks.py b/tornado/locks.py
index 4f9ecf6d..c883aa23 100644
--- a/tornado/locks.py
+++ b/tornado/locks.py
@@ -99,7 +99,7 @@ class Condition(_TimeoutGarbageCollector):
         # Wait up to 1 second.
         yield condition.wait(timeout=datetime.timedelta(seconds=1))
 
-    The method raises `tornado.gen.TimeoutError` if there's no notification
+    The method raises `tornado.util.TimeoutError` if there's no notification
     before the deadline.
     """
 
@@ -220,7 +220,7 @@ class Event(object):
     def wait(self, timeout=None):
         """Block until the internal flag is true.
 
-        Returns a Future, which raises `tornado.gen.TimeoutError` after a
+        Returns a Future, which raises `tornado.util.TimeoutError` after a
         timeout.
         """
         if timeout is None:
@@ -480,7 +480,7 @@ class Lock(object):
     def acquire(self, timeout=None):
         """Attempt to lock. Returns a Future.
 
-        Returns a Future, which raises `tornado.gen.TimeoutError` after a
+        Returns a Future, which raises `tornado.util.TimeoutError` after a
         timeout.
         """
         return self._block.acquire(timeout)
diff --git a/tornado/queues.py b/tornado/queues.py
index 00fa4e22..73589611 100644
--- a/tornado/queues.py
+++ b/tornado/queues.py
@@ -166,7 +166,7 @@ class Queue(object):
     def put(self, item, timeout=None):
         """Put an item into the queue, perhaps waiting until there is room.
 
-        Returns a Future, which raises `tornado.gen.TimeoutError` after a
+        Returns a Future, which raises `tornado.util.TimeoutError` after a
         timeout.
 
         ``timeout`` may be a number denoting a time (on the same
@@ -204,7 +204,7 @@ class Queue(object):
         """Remove and return an item from the queue.
 
         Returns a Future which resolves once an item is available, or raises
-        `tornado.gen.TimeoutError` after a timeout.
+        `tornado.util.TimeoutError` after a timeout.
 
         ``timeout`` may be a number denoting a time (on the same
         scale as `tornado.ioloop.IOLoop.time`, normally `time.time`), or a
@@ -258,7 +258,7 @@ class Queue(object):
     def join(self, timeout=None):
         """Block until all items in the queue are processed.
 
-        Returns a Future, which raises `tornado.gen.TimeoutError` after a
+        Returns a Future, which raises `tornado.util.TimeoutError` after a
         timeout.
         """
         return self._finished.wait(timeout)
diff --git a/tornado/util.py b/tornado/util.py
index bfd80beb..aee718ae 100644
--- a/tornado/util.py
+++ b/tornado/util.py
@@ -84,6 +84,16 @@ except ImportError:
     is_finalizing = _get_emulated_is_finalizing()
 
 
+class TimeoutError(Exception):
+    """Exception raised by `.with_timeout` and `.IOLoop.run_sync`.
+
+    .. versionchanged:: 5.0:
+       Unified ``tornado.gen.TimeoutError`` and
+       ``tornado.ioloop.TimeoutError`` as ``tornado.util.TimeoutError``.
+       Both former names remain as aliases.
+    """
+
+
 class ObjectDict(_ObjectDictBase):
     """Makes a dictionary behave like an object, with attribute-style access.
     """


Test patch:
diff --git a/tornado/test/import_test.py b/tornado/test/import_test.py
index 88d02e02..d8cf14a5 100644
--- a/tornado/test/import_test.py
+++ b/tornado/test/import_test.py
@@ -45,3 +45,9 @@ class ImportTest(unittest.TestCase):
             pass
         else:
             import tornado.curl_httpclient
+
+    def test_import_aliases(self):
+        # Ensure we don't delete formerly-documented aliases accidentally.
+        import tornado.ioloop, tornado.gen, tornado.util
+        self.assertIs(tornado.ioloop.TimeoutError, tornado.util.TimeoutError)
+        self.assertIs(tornado.gen.TimeoutError, tornado.util.TimeoutError)