Repository: DataDog/dd-trace-py

Issue:
AttributeError: 'coroutine' object has no attribute 'add_done_callback'

### Which version of dd-trace-py are you using?

ddtrace 0.57.1

### Which version of pip are you using?

pip 21.3.1

### Which version of the libraries are you using?

aioredis 1.3.1

### What is the result that you get?

```
  File "aioredis/commands/list.py", line 97, in lrange
    return self.execute(b'LRANGE', key, start, stop, encoding=encoding)
  File "ddtrace/contrib/aioredis/patch.py", line 147, in traced_13_execute_command
    task.add_done_callback(_finish_span)
```

### What is the result that you expected?

No errors




Discussion:

Resolving patch:
diff --git a/ddtrace/contrib/aioredis/patch.py b/ddtrace/contrib/aioredis/patch.py
--- a/ddtrace/contrib/aioredis/patch.py
+++ b/ddtrace/contrib/aioredis/patch.py
@@ -1,3 +1,4 @@
+import asyncio
 import sys
 
 import aioredis
@@ -144,6 +145,9 @@ def _finish_span(future):
             span.finish()
 
     task = func(*args, **kwargs)
+    # Execute command returns a coroutine when no free connections are available
+    # https://github.com/aio-libs/aioredis-py/blob/v1.3.1/aioredis/pool.py#L191
+    task = asyncio.ensure_future(task)
     task.add_done_callback(_finish_span)
     return task
 


Test patch:
diff --git a/tests/contrib/aioredis/test_aioredis.py b/tests/contrib/aioredis/test_aioredis.py
--- a/tests/contrib/aioredis/test_aioredis.py
+++ b/tests/contrib/aioredis/test_aioredis.py
@@ -1,3 +1,5 @@
+import asyncio
+
 import aioredis
 import pytest
 
@@ -15,14 +17,21 @@
 @pytest.mark.asyncio
 @pytest.fixture
 async def redis_client():
-    r = await get_redis_instance()
+    r = await get_redis_instance(max_connections=10)  # default values
     yield r
 
 
-def get_redis_instance():
+@pytest.mark.asyncio
+@pytest.fixture
+async def single_pool_redis_client():
+    r = await get_redis_instance(max_connections=1)
+    yield r
+
+
+def get_redis_instance(max_connections: int):
     if aioredis_version >= (2, 0):
-        return aioredis.from_url("redis://127.0.0.1:%s" % REDIS_CONFIG["port"])
-    return aioredis.create_redis(("localhost", REDIS_CONFIG["port"]))
+        return aioredis.from_url("redis://127.0.0.1:%s" % REDIS_CONFIG["port"], max_connections=max_connections)
+    return aioredis.create_redis_pool(("127.0.0.1", REDIS_CONFIG["port"]), maxsize=max_connections)
 
 
 @pytest.mark.asyncio
@@ -97,6 +106,26 @@ async def test_decoding_non_utf8_pipeline_args(redis_client):
     assert response_list[3] == b"\x80abc"
 
 
+@pytest.mark.skipif(aioredis_version > (2, 0), reason="only affects aioredis < 2.0")
+@pytest.mark.asyncio
+@pytest.mark.snapshot
+async def test_closed_connection_pool(single_pool_redis_client):
+    """
+    Make sure it doesn't raise error when no free connections are available.
+    After aioredis 2.0 it raises Too many connections error when it happens.
+    """
+
+    async def execute_task():
+        """Execute call in the background as a task."""
+        return single_pool_redis_client.get("cheese")
+
+    # start running the task after blocking the pool
+    with (await single_pool_redis_client):
+        task = [asyncio.ensure_future(execute_task())]
+    # Pool is released, make sure we wait for the task to finish
+    await asyncio.gather(*task, return_exceptions=True)
+
+
 @pytest.mark.asyncio
 @pytest.mark.snapshot
 async def test_long_command(redis_client):
diff --git a/tests/snapshots/tests.contrib.aioredis.test_aioredis.test_closed_connection_pool.json b/tests/snapshots/tests.contrib.aioredis.test_aioredis.test_closed_connection_pool.json
new file mode 100644
--- /dev/null
+++ b/tests/snapshots/tests.contrib.aioredis.test_aioredis.test_closed_connection_pool.json
@@ -0,0 +1,28 @@
+[[
+  {
+    "name": "redis.command",
+    "service": "redis",
+    "resource": "GET cheese",
+    "trace_id": 0,
+    "span_id": 1,
+    "parent_id": 0,
+    "type": "redis",
+    "meta": {
+      "out.host": "127.0.0.1",
+      "redis.raw_command": "GET cheese",
+      "runtime-id": "a76e23453dc9417bb8e4e691e0d9a567"
+    },
+    "metrics": {
+      "_dd.agent_psr": 1.0,
+      "_dd.measured": 1,
+      "_dd.top_level": 1,
+      "_dd.tracer_kr": 1.0,
+      "_sampling_priority_v1": 1,
+      "out.port": 6379,
+      "out.redis_db": 0,
+      "redis.args_length": 2,
+      "system.pid": 179
+    },
+    "duration": 1973000,
+    "start": 1638885382734225000
+  }]]