Repository: aio-libs/aiohttp

Issue:
aiohttp.Timeout causes CancelledError
## Long story short

I use aiohttp.Timeout so that my code could react to connectivity problems and reattempt an unsuccessful connection. When timeout happens, the traceback contains more than that, misleading into a thought that something was prematurely cancelled due to other reasons.
## Expected behaviour

Only asyncio.TimeoutError exception is thrown when the specified timeout expires and all other exceptions which result from it are muffled.
## Actual behaviour

CancelledError exception is thrown and appears in traceback along with TimeoutError:

```
Traceback (most recent call last):
  File "timeouttest.py", line 9, in test
    async with session.get("https://www.google.com") as response:
  File "/home/user/test/venv/lib/python3.5/site-packages/aiohttp/client.py", line 539, in __aenter__
    self._resp = yield from self._coro
  File "/home/user/test/venv/lib/python3.5/site-packages/aiohttp/client.py", line 183, in _request
    conn = yield from self._connector.connect(req)
  File "/home/user/test/venv/lib/python3.5/site-packages/aiohttp/connector.py", line 310, in connect
    transport, proto = yield from self._create_connection(req)
  File "/home/user/test/venv/lib/python3.5/site-packages/aiohttp/connector.py", line 581, in _create_connection
    local_addr=self._local_addr)
  File "/home/user/src/python3/lib/python3.5/asyncio/base_events.py", line 599, in create_connection
    yield from tasks.wait(fs, loop=self)
  File "/home/user/src/python3/lib/python3.5/asyncio/tasks.py", line 341, in wait
    return (yield from _wait(fs, timeout, return_when, loop))
  File "/home/user/src/python3/lib/python3.5/asyncio/tasks.py", line 424, in _wait
    yield from waiter
  File "/home/user/src/python3/lib/python3.5/asyncio/futures.py", line 358, in __iter__
    yield self  # This tells Task to wait for completion.
  File "/home/user/src/python3/lib/python3.5/asyncio/tasks.py", line 290, in _wakeup
    future.result()
  File "/home/user/src/python3/lib/python3.5/asyncio/futures.py", line 266, in result
    raise CancelledError
concurrent.futures._base.CancelledError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "timeouttest.py", line 17, in <module>
    response = asyncio.get_event_loop().run_until_complete(test(timeout))
  File "/home/user/src/python3/lib/python3.5/asyncio/base_events.py", line 337, in run_until_complete
    return future.result()
  File "/home/user/src/python3/lib/python3.5/asyncio/futures.py", line 274, in result
    raise self._exception
  File "/home/user/src/python3/lib/python3.5/asyncio/tasks.py", line 241, in _step
    result = coro.throw(exc)
  File "timeouttest.py", line 10, in test
    return await response.read()
  File "/home/user/test/venv/lib/python3.5/site-packages/aiohttp/helpers.py", line 488, in __exit__
    raise asyncio.TimeoutError
concurrent.futures._base.TimeoutError
```
## Steps to reproduce

Consider the simple sample:

```
import asyncio
import aiohttp
import traceback

async def test(timeout):
    try:
        with aiohttp.Timeout(timeout):
            with aiohttp.ClientSession() as session:
                async with session.get("https://www.google.com") as response:
                    return await response.read()
    except asyncio.TimeoutError:
        raise

timeout = 0.000001 # To make aiohttp.Timeout trigger

try:
    response = asyncio.get_event_loop().run_until_complete(test(timeout))
    print(response)
except:
    traceback.print_exc()

```
## Your environment

Python 3.5.1
aiohttp (0.21.6)
asyncio (3.4.3)



Discussion:
So you are asking just for suppressing chained exception, isn't it?


Resolving patch:
diff --git a/aiohttp/helpers.py b/aiohttp/helpers.py
--- a/aiohttp/helpers.py
+++ b/aiohttp/helpers.py
@@ -561,7 +561,7 @@ def __exit__(self, exc_type, exc_val, exc_tb):
         if exc_type is asyncio.CancelledError and self._cancelled:
             self._cancel_handler = None
             self._task = None
-            raise asyncio.TimeoutError
+            raise asyncio.TimeoutError from None
         if self._timeout is not None:
             self._cancel_handler.cancel()
             self._cancel_handler = None


Test patch:
diff --git a/tests/test_timeout.py b/tests/test_timeout.py
--- a/tests/test_timeout.py
+++ b/tests/test_timeout.py
@@ -165,3 +165,13 @@ def outer():
         yield from task
     assert task.cancelled()
     assert task.done()
+
+
+@pytest.mark.run_loop
+def test_timeout_suppress_exception_chain(loop):
+
+    with pytest.raises(asyncio.TimeoutError) as ctx:
+        with Timeout(0.01, loop=loop) as t:
+            yield from asyncio.sleep(10, loop=loop)
+            assert t._loop is loop
+    assert ctx.value.__suppress_context__