Repository: aio-libs/aiohttp

Issue:
BaseConnector limit and limit_per_host don't work with tracing enabled
🐞 **Describe the bug**
The BaseConnector limit and limit_per_host attributes don't function properly when tracing is enabled and the connection is being reused.   BaseConnector.connect() calls _available_connections(), and when it gets back a number > 0, it will await any configured traces before acquiring the connection.  That is enough time for another thread to jump in and acquire the same connection, allowing more than the configured number of connections to exist simultaneously.  connect() needs to acquire the connection, possibly with a placeholder, before awaiting the traces.


💡 **To Reproduce**
1. Configure a limit and/or limit_per_host on a TCPConnection
2. Configure tracing for on_connection_reuseconn()
3. Queue up a large number of requests that exceed the limits specified
4. Observe that more than the configured limit number of queries are run simultaneously

💡 **Expected behavior**
There should not be more than the configured limit number of connections active at any given time

📋 **Logs/tracebacks**
<!-- If applicable, add logs/tracebacks to help explain your problem. -->
```python-traceback (paste your traceback in the next line)

```

📋 **Your version of the Python**
<!-- Attach your version of the Python. -->
```console
$ python --version
Python 3.7.3
...
```

📋 **Your version of the aiohttp/yarl/multidict distributions**
<!-- Attach your version of the distributions in the code blocks below. -->
```console
$ python -m pip show aiohttp
Name: aiohttp
Version: 3.6.2
Summary: Async http client/server framework (asyncio)
Home-page: https://github.com/aio-libs/aiohttp
Author: Nikolay Kim
Author-email: fafhrd91@gmail.com
License: Apache 2
Location: /usr/local/lib/python3.7/site-packages
Requires: chardet, yarl, attrs, async-timeout, multidict
Required-by: spe, faust, bravado-asyncio, aiohttp-cors
```
```console
$ python -m pip show multidict
Name: multidict
Version: 4.7.5
Summary: multidict implementation
Home-page: https://github.com/aio-libs/multidict
Author: Andrew Svetlov
Author-email: andrew.svetlov@gmail.com
License: Apache 2
Location: /usr/local/lib/python3.7/site-packages
Requires: 
Required-by: yarl, aiohttp

```
```console
$ python -m pip show yarl

Name: yarl
Version: 1.4.2
Summary: Yet another URL library
Home-page: https://github.com/aio-libs/yarl/
Author: Andrew Svetlov
Author-email: andrew.svetlov@gmail.com
License: Apache 2
Location: /usr/local/lib/python3.7/site-packages
Requires: idna, multidict
Required-by: faust, aiohttp
```

📋 **Additional context**
<!-- Add any other context about the problem here, in the next line. -->

<!-- Describe the environment you have that lead to your issue.
     This includes aiohttp version, OS, proxy server and other bits that
     are related to your case.

     IMPORTANT: aiohttp is both server framework and client library.
     For getting rid of confusing please put 'server', 'client' or 'both'
     word here.
     -->
client



Discussion:
Good catch!
Would you make a PR?
Writing tests for such things is hard; mock-based tests are not reliable.
I'm ok to review the patch even without the test suite updated (at least start the reviewing, maybe we figure out how to add a good test after the main patch appears).
Thanks @asvetlov - I agree on the unit test - I'll submit a PR without a test for now and if someone has a way to test it I'll be glad to add it in.

Resolving patch:
diff --git a/aiohttp/connector.py b/aiohttp/connector.py
--- a/aiohttp/connector.py
+++ b/aiohttp/connector.py
@@ -525,8 +525,14 @@ async def connect(
                     await trace.send_connection_create_end()
         else:
             if traces:
+                # Acquire the connection to prevent race conditions with limits
+                placeholder = cast(ResponseHandler, _TransportPlaceholder(self._loop))
+                self._acquired.add(placeholder)
+                self._acquired_per_host[key].add(placeholder)
                 for trace in traces:
                     await trace.send_connection_reuseconn()
+                self._acquired.remove(placeholder)
+                self._drop_acquired_per_host(key, placeholder)
 
         self._acquired.add(proto)
         self._acquired_per_host[key].add(proto)


Test patch: