Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d5fa985

Browse files
authored
Fix connector (aio-libs#2567)
* Release a waiter after exception on connection establishment * Don't count waiters in a check for connection availability. self_acquired is enough. * Add extra test * Add missing changenote
1 parent 4e7a2c4 commit d5fa985

4 files changed

Lines changed: 100 additions & 9 deletions

File tree

CHANGES/2567.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Return client connection back to free pool on error in `connector.connect()`.

aiohttp/connector.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,15 @@ def _cleanup(self):
283283
self._cleanup_handle = helpers.weakref_handle(
284284
self, '_cleanup', timeout, self._loop)
285285

286+
def _drop_acquired_per_host(self, key, val):
287+
acquired_per_host = self._acquired_per_host
288+
if key not in acquired_per_host:
289+
return
290+
conns = acquired_per_host[key]
291+
conns.remove(val)
292+
if not conns:
293+
del self._acquired_per_host[key]
294+
286295
def _cleanup_closed(self):
287296
"""Double confirmation for transport close.
288297
Some broken ssl servers may leave socket open without proper close.
@@ -354,7 +363,7 @@ def connect(self, req):
354363

355364
if self._limit:
356365
# total calc available connections
357-
available = self._limit - len(self._waiters) - len(self._acquired)
366+
available = self._limit - len(self._acquired)
358367

359368
# check limit per host
360369
if (self._limit_per_host and available > 0 and
@@ -396,15 +405,16 @@ def connect(self, req):
396405
raise ClientConnectionError("Connector is closed.")
397406
except:
398407
# signal to waiter
399-
for waiter in self._waiters[key]:
400-
if not waiter.done():
401-
waiter.set_result(None)
402-
break
408+
if key in self._waiters:
409+
for waiter in self._waiters[key]:
410+
if not waiter.done():
411+
waiter.set_result(None)
412+
break
403413
raise
404414
finally:
405415
if not self._closed:
406416
self._acquired.remove(placeholder)
407-
self._acquired_per_host[key].remove(placeholder)
417+
self._drop_acquired_per_host(key, placeholder)
408418

409419
self._acquired.add(proto)
410420
self._acquired_per_host[key].add(proto)
@@ -463,9 +473,7 @@ def _release_acquired(self, key, proto):
463473

464474
try:
465475
self._acquired.remove(proto)
466-
self._acquired_per_host[key].remove(proto)
467-
if not self._acquired_per_host[key]:
468-
del self._acquired_per_host[key]
476+
self._drop_acquired_per_host(key, proto)
469477
except KeyError: # pragma: no cover
470478
# this may be result of undetermenistic order of objects
471479
# finalization due garbage collection.

tests/test_client_functional.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2288,3 +2288,63 @@ def close(self):
22882288
assert resp.status == 200
22892289
finally:
22902290
yield from client.close()
2291+
2292+
2293+
@asyncio.coroutine
2294+
def test_error_in_performing_request(loop, ssl_ctx,
2295+
test_client, test_server):
2296+
@asyncio.coroutine
2297+
def handler(request):
2298+
return web.Response()
2299+
2300+
app = web.Application()
2301+
app.router.add_route('GET', '/', handler)
2302+
2303+
server = yield from test_server(app, ssl=ssl_ctx)
2304+
2305+
conn = aiohttp.TCPConnector(limit=1, loop=loop)
2306+
client = yield from test_client(server, connector=conn)
2307+
2308+
with pytest.raises(aiohttp.ClientConnectionError):
2309+
yield from client.get('/')
2310+
2311+
# second try should not hang
2312+
with pytest.raises(aiohttp.ClientConnectionError):
2313+
yield from client.get('/')
2314+
2315+
2316+
@asyncio.coroutine
2317+
def test_await_after_cancelling(loop, test_client):
2318+
@asyncio.coroutine
2319+
def handler(request):
2320+
return web.Response()
2321+
2322+
app = web.Application()
2323+
app.router.add_route('GET', '/', handler)
2324+
2325+
client = yield from test_client(app)
2326+
2327+
fut1 = create_future(loop)
2328+
fut2 = create_future(loop)
2329+
2330+
@asyncio.coroutine
2331+
def fetch1():
2332+
resp = yield from client.get('/')
2333+
assert resp.status == 200
2334+
fut1.set_result(None)
2335+
with pytest.raises(asyncio.CancelledError):
2336+
yield from fut2
2337+
resp.release()
2338+
2339+
@asyncio.coroutine
2340+
def fetch2():
2341+
yield from fut1
2342+
resp = yield from client.get('/')
2343+
assert resp.status == 200
2344+
2345+
@asyncio.coroutine
2346+
def canceller():
2347+
yield from fut1
2348+
fut2.cancel()
2349+
2350+
yield from asyncio.gather(fetch1(), fetch2(), canceller(), loop=loop)

tests/test_connector.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,28 @@ def test_release_close(loop):
343343
assert proto.close.called
344344

345345

346+
def test__drop_acquire_per_host1(loop):
347+
conn = aiohttp.BaseConnector(loop=loop)
348+
conn._drop_acquired_per_host(123, 456)
349+
assert len(conn._acquired_per_host) == 0
350+
351+
352+
def test__drop_acquire_per_host2(loop):
353+
conn = aiohttp.BaseConnector(loop=loop)
354+
conn._acquired_per_host[123].add(456)
355+
conn._drop_acquired_per_host(123, 456)
356+
assert len(conn._acquired_per_host) == 0
357+
358+
359+
def test__drop_acquire_per_host3(loop):
360+
conn = aiohttp.BaseConnector(loop=loop)
361+
conn._acquired_per_host[123].add(456)
362+
conn._acquired_per_host[123].add(789)
363+
conn._drop_acquired_per_host(123, 456)
364+
assert len(conn._acquired_per_host) == 1
365+
assert conn._acquired_per_host[123] == {789}
366+
367+
346368
@asyncio.coroutine
347369
def test_tcp_connector_certificate_error(loop):
348370
req = ClientRequest('GET', URL('https://127.0.0.1:443'), loop=loop)

0 commit comments

Comments
 (0)