Repository: aio-libs/aiohttp

Issue:
ContextVar support?
## Long story short
I am trying to check ContextVar integration with aiohttp.  https://docs.python.org/3/library/contextvars.html#asyncio-support
ContextVar seems not deals well with aiohttp server... Am I doing something wrong?

## Expected behaviour

ContextVar should be different for each request.

## Actual behaviour

ContextVar has the same value between requests, but  seems to be cleared on failure.

## Steps to reproduce

### server.py
```python
from contextvars import ContextVar
from aiohttp import web

ctx = ContextVar("test")

async def handle(request):
    try:
        ctx.get()
        assert False, "Shouldn't ever be called. ContextVar has value"
    except LookupError:
        pass

    ctx.set("Test")

    return web.Response(text="Hello, " + ctx.get())

app = web.Application()
app.add_routes([web.get('/', handle)])

web.run_app(app, port=8000)
```

### Other process client.py
Client is working fine, just for example
```python
import aiohttp
import asyncio

async def main():
    async with aiohttp.ClientSession() as session:
        while True:
            async with session.get("http://127.0.0.1:8000/") as response:
                html = await response.text()

            print(html)

asyncio.run(main())
```

### Response
Server is constantly being failed on each even request.

```text
Hello, Test
<html><head><title>500 Internal Server Error</title></head><body><h1>500 Internal Server Error</h1>Server got itself in trouble</body></html>
Hello, Test
<html><head><title>500 Internal Server Error</title></head><body><h1>500 Internal Server Error</h1>Server got itself in trouble</body></html>
Hello, Test
...
```

### Extra example

I tried almost the same for `sanic`, and seems that it works fine.
```python
from contextvars import ContextVar

from sanic import Sanic
from sanic.response import text

app = Sanic()

ctx = ContextVar("test")


@app.route('/')
async def test(request):
    try:
        ctx.get()
        assert False, "Shouldn't ever be called. ContextVar has value"
    except LookupError:
        pass

    ctx.set("Test")

    return text('hello, ' + ctx.get())
app.run(host='0.0.0.0', port=8000)
```

### Response
```text
hello, Test
hello, Test
hello, Test
hello, Test
hello, Test
hello, Test
hello, Test
```
## Your environment
macOs Mojave
Python 3.7.0 (default, Jun 29 2018, 20:13:13) \n[Clang 9.1.0 (clang-902.0.39.2)]
aiohttp 3.4.4, also tried 3.5.0a1 from master branch



Discussion:
[GitMate.io](https://gitmate.io) thinks the contributor most likely able to help you is @asvetlov.

Possibly related issues are https://github.com/aio-libs/aiohttp/issues/667 (Please support multi language url), https://github.com/aio-libs/aiohttp/issues/105 (chardet support), https://github.com/aio-libs/aiohttp/issues/33 (HTTPS Support), https://github.com/aio-libs/aiohttp/issues/3084 (tracing support enhancements), and https://github.com/aio-libs/aiohttp/issues/6 (HTTP pipelining support improvements).
Good point!
Both aiohttp and sanic doesn't support context vars explicitly but aiohttp creates a task for response handling once per a connection. Sanic IIRC creates a task per request.

aiohttp can be modified to create a new contextvar execution context for each web-handler call easily (will work only on Python 3.7 obviously).

We need a champion for the issue.
For now, quick workaround is:
```python
@middleware
def fix_middleware(request, handler):
    return asyncio.create_task(handler(request))

app = web.Application(middlewares=[fix_middleware])
```
@Reskov thanks for the hint.
Maybe it will be useful for somebody.

Resolving patch:
diff --git a/aiohttp/web_protocol.py b/aiohttp/web_protocol.py
--- a/aiohttp/web_protocol.py
+++ b/aiohttp/web_protocol.py
@@ -400,7 +400,10 @@ async def start(self) -> None:
                 message, payload, self, writer, handler)
             try:
                 try:
-                    resp = await self._request_handler(request)
+                    # a new task is used for copy context vars (#3406)
+                    task = self._loop.create_task(
+                        self._request_handler(request))
+                    resp = await task
                 except HTTPException as exc:
                     resp = exc
                 except asyncio.CancelledError:


Test patch:
diff --git a/tests/test_web_protocol.py b/tests/test_web_protocol.py
--- a/tests/test_web_protocol.py
+++ b/tests/test_web_protocol.py
@@ -237,7 +237,7 @@ async def test_simple(srv, buf) -> None:
     srv.data_received(
         b'GET / HTTP/1.1\r\n\r\n')
 
-    await asyncio.sleep(0)
+    await asyncio.sleep(0.05)
     assert buf.startswith(b'HTTP/1.1 200 OK\r\n')
 
 
@@ -450,7 +450,7 @@ async def handle_request(request):
         b'Content-Length: 50\r\n\r\n')
     await asyncio.sleep(0)
     assert not transport.close.called
-    await asyncio.sleep(0)
+    await asyncio.sleep(0.05)
     transport.close.assert_called_with()
 
 
@@ -465,7 +465,7 @@ async def handle_request(request):
     srv.connection_made(transport)
     request_handler.side_effect = handle_request
 
-    await asyncio.sleep(0)
+    await asyncio.sleep(0.05)
     assert not transport.close.called
 
     srv.data_received(
@@ -475,7 +475,7 @@ async def handle_request(request):
     await asyncio.sleep(0)
     assert not transport.close.called
 
-    await asyncio.sleep(0)
+    await asyncio.sleep(0.05)
     transport.close.assert_called_with()
 
 
@@ -490,7 +490,7 @@ async def test_handle_payload_access_error(
         b'some data'
     )
     # start request_handler task
-    await asyncio.sleep(0)
+    await asyncio.sleep(0.05)
 
     with pytest.raises(web.PayloadAccessError):
         await request_handler.call_args[0][0].content.read()
@@ -582,9 +582,10 @@ async def test_keep_alive(make_srv, transport, ceil) -> None:
         b'Host: example.com\r\n'
         b'Content-Length: 0\r\n\r\n')
 
-    await asyncio.sleep(0)
-    waiter = srv._waiter
-    assert waiter
+    waiter = None
+    while waiter is None:
+        await asyncio.sleep(0)
+        waiter = srv._waiter
     assert srv._keepalive_handle is not None
     assert not transport.close.called
 
@@ -681,7 +682,7 @@ async def test_close(srv, transport) -> None:
         b'Host: example.com\r\n'
         b'Content-Length: 0\r\n\r\n')
 
-    await asyncio.sleep(0)
+    await asyncio.sleep(0.05)
     assert srv._task_handler
     assert srv._waiter
 
@@ -721,7 +722,7 @@ async def handle(request):
     assert len(srv._messages) == 2
     assert srv._waiter is not None
 
-    await asyncio.sleep(0)
+    await asyncio.sleep(0.05)
     assert srv._task_handler is not None
     assert srv._waiter is not None
     assert processed == 2
@@ -856,3 +857,4 @@ async def test_two_data_received_without_waking_up_start_task(srv) -> None:
 
     assert len(srv._messages) == 2
     assert srv._waiter.done()
+    await asyncio.sleep(0.01)