Repository: neo4j/neo4j-python-driver

Issue:
Async driver keeping connections from pool open?
I encountered a new issue with the Async driver version 5.0.0a2.

The error states: 

ERROR in app: Exception on request POST /recommend_contact
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/quart/app.py", line 1489, in handle_request
    return await self.full_dispatch_request(request_context)
  File "/usr/local/lib/python3.7/site-packages/quart/app.py", line 1514, in full_dispatch_request
    result = await self.handle_user_exception(error)
  File "/usr/local/lib/python3.7/site-packages/quart/app.py", line 964, in handle_user_exception
    raise error
  File "/usr/local/lib/python3.7/site-packages/quart/app.py", line 1512, in full_dispatch_request
    result = await self.dispatch_request(request_context)
  File "/usr/local/lib/python3.7/site-packages/quart/app.py", line 1557, in dispatch_request
    return await self.ensure_async(handler)(**request_.view_args)
  File "/usr/local/lib/python3.7/site-packages/quart/views.py", line 57, in view
    return await current_app.ensure_async(self.dispatch_request)(*args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/quart_openapi/resource.py", line 71, in dispatch_request
    return await handler(*args, **kwargs)
  File "/api/app.py", line 119, in post
    recommendations = await asyncio.gather(*tasks)
  File "/api/app.py", line 108, in recommendation_task
    el['offsite'])
  File "/usr/local/lib/python3.7/site-packages/recommendation_engine-0.0.1-py3.7.egg/recommendation_engine/recommendation/recommender.py", line 32, in get_popular_contact
    ret = await self.graph_handler.run_query_async(graph_query, contact_id=contact_id, p=p, offsite=offsite)
  File "/usr/local/lib/python3.7/site-packages/recommendation_engine-0.0.1-py3.7.egg/recommendation_engine/graph_handler.py", line 636, in run_query_async
    return await session.read_transaction(self.run_transaction, query, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/neo4j/_meta.py", line 73, in inner
    return await f(*args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/neo4j/_async/work/session.py", line 656, in read_transaction
    READ_ACCESS, transaction_function, *args, **kwargs
  File "/usr/local/lib/python3.7/site-packages/neo4j/_async/work/session.py", line 478, in _run_transaction
    timeout=timeout
  File "/usr/local/lib/python3.7/site-packages/neo4j/_async/work/session.py", line 392, in _open_transaction
    await self._connect(access_mode=access_mode)
  File "/usr/local/lib/python3.7/site-packages/neo4j/_async/work/session.py", line 122, in _connect
    await super()._connect(access_mode, **access_kwargs)
  File "/usr/local/lib/python3.7/site-packages/neo4j/_async/work/workspace.py", line 194, in _connect
    self._connection = await self._pool.acquire(**acquire_kwargs_)
  File "/usr/local/lib/python3.7/site-packages/neo4j/_async/io/_pool.py", line 403, in acquire
    self.address, deadline, liveness_check_timeout
  File "/usr/local/lib/python3.7/site-packages/neo4j/_async/io/_pool.py", line 216, in _acquire
    "{!r}s (timeout)".format(deadline.original_timeout)
neo4j.exceptions.ClientError: {code: None} {message: None}

I suspected it is the issue with connections being kept alive or being alive for a prolonged period of time, as when I checked "/usr/local/lib/python3.7/site-packages/neo4j/_async/io/_pool.py" I noticed the error is thrown when the pool is full and there are no avaliable connections. Also the code and message are shown as None, so I am not sure if that is a bug also.

We are running Neo4j Version 4.4.3 Community edition as a single instance.

Discussion:

Patch:
diff --git a/neo4j/io/__init__.py b/neo4j/io/__init__.py
index fb71d5ef..862895d7 100644
--- a/neo4j/io/__init__.py
+++ b/neo4j/io/__init__.py
@@ -756,9 +756,9 @@ class IOPool:
                          + self.connections_reservations[address])
             can_create_new_connection = (infinite_pool_size
                                          or pool_size < max_pool_size)
-            self.connections_reservations[address] += 1
-        if can_create_new_connection:
-            return connection_creator
+            if can_create_new_connection:
+                self.connections_reservations[address] += 1
+                return connection_creator
 
     def _acquire(self, address, deadline):
 


Test patch:
diff --git a/tests/unit/io/test_neo4j_pool.py b/tests/unit/io/test_neo4j_pool.py
index 5853ceac..73087534 100644
--- a/tests/unit/io/test_neo4j_pool.py
+++ b/tests/unit/io/test_neo4j_pool.py
@@ -35,6 +35,7 @@ from neo4j.conf import (
     RoutingConfig,
     WorkspaceConfig
 )
+from neo4j._deadline import Deadline
 from neo4j.exceptions import (
     ServiceUnavailable,
     SessionExpired
@@ -271,3 +272,29 @@ def test_failing_opener_leaves_connections_in_use_alone(opener):
         pool.acquire(READ_ACCESS, 30, 60, "test_db", None)
 
     assert not cx1.closed()
+
+
+def test__acquire_new_later_with_room(opener):
+    config = PoolConfig()
+    config.max_connection_pool_size = 1
+    pool = Neo4jPool(
+        opener, config, WorkspaceConfig(), ROUTER_ADDRESS
+    )
+    assert pool.connections_reservations[READER_ADDRESS] == 0
+    creator = pool._acquire_new_later(READER_ADDRESS, Deadline(1))
+    assert pool.connections_reservations[READER_ADDRESS] == 1
+    assert callable(creator)
+
+
+def test__acquire_new_later_without_room(opener):
+    config = PoolConfig()
+    config.max_connection_pool_size = 1
+    pool = Neo4jPool(
+        opener, config, WorkspaceConfig(), ROUTER_ADDRESS
+    )
+    _ = pool.acquire(READ_ACCESS, 30, 60, "test_db", None)
+    # pool is full now
+    assert pool.connections_reservations[READER_ADDRESS] == 0
+    creator = pool._acquire_new_later(READER_ADDRESS, Deadline(1))
+    assert pool.connections_reservations[READER_ADDRESS] == 0
+    assert creator is None