Skip to content

Commit dec1909

Browse files
ZStriker19wantsui
authored andcommitted
fix(tracing): add flag for aiohttp that disables potential for memory leak (#12081)
This PR adds the environment variable ``DD_AIOHTTP_CLIENT_DISABLE_STREAM_TIMING_FOR_MEM_LEAK`` to address a potential memory leak in the aiohttp integration. When set to true, this flag may cause streamed response span timing to be inaccurate. The flag defaults to false. When this was merged #7551 we essentially added support for getting the correct timing of streamed responses, however it also caused us to sometimes never close spans, which lead to a memory leak. When set to true, this flag essentially reverts that behavior. - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting) (cherry picked from commit 17c60dd)
1 parent aaaa95b commit dec1909

File tree

5 files changed

+86
-17
lines changed

5 files changed

+86
-17
lines changed

ddtrace/contrib/aiohttp/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@
3636
3737
Default: ``False``
3838
39+
.. py:data:: ddtrace.config.aiohttp['disable_stream_timing_for_mem_leak']
40+
41+
Whether or not to to address a potential memory leak in the aiohttp integration.
42+
When set to ``True``, this flag may cause streamed response span timing to be inaccurate.
43+
44+
Default: ``False``
45+
3946
4047
Server
4148
******

ddtrace/contrib/internal/aiohttp/middlewares.py

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -63,19 +63,39 @@ async def attach_context(request):
6363
if (config._analytics_enabled and analytics_enabled is not False) or analytics_enabled is True:
6464
request_span.set_tag(_ANALYTICS_SAMPLE_RATE_KEY, app[CONFIG_KEY].get("analytics_sample_rate", True))
6565

66-
# attach the context and the root span to the request; the Context
67-
# may be freely used by the application code
68-
request[REQUEST_CONTEXT_KEY] = request_span.context
69-
request[REQUEST_SPAN_KEY] = request_span
70-
request[REQUEST_CONFIG_KEY] = app[CONFIG_KEY]
71-
try:
72-
response = await handler(request)
73-
if isinstance(response, web.StreamResponse):
74-
request.task.add_done_callback(lambda _: finish_request_span(request, response))
75-
return response
76-
except Exception:
77-
request_span.set_traceback()
78-
raise
66+
with core.context_with_data(
67+
"aiohttp.request",
68+
span_name=schematize_url_operation("aiohttp.request", protocol="http", direction=SpanDirection.INBOUND),
69+
span_type=SpanTypes.WEB,
70+
service=service,
71+
tags={},
72+
tracer=tracer,
73+
distributed_headers=request.headers,
74+
distributed_headers_config=config.aiohttp,
75+
distributed_headers_config_override=app[CONFIG_KEY]["distributed_tracing_enabled"],
76+
headers_case_sensitive=True,
77+
analytics_enabled=analytics_enabled,
78+
analytics_sample_rate=app[CONFIG_KEY].get("analytics_sample_rate", True),
79+
) as ctx:
80+
req_span = ctx.span
81+
82+
ctx.set_item("req_span", req_span)
83+
core.dispatch("web.request.start", (ctx, config.aiohttp))
84+
85+
# attach the context and the root span to the request; the Context
86+
# may be freely used by the application code
87+
request[REQUEST_CONTEXT_KEY] = req_span.context
88+
request[REQUEST_SPAN_KEY] = req_span
89+
request[REQUEST_CONFIG_KEY] = app[CONFIG_KEY]
90+
try:
91+
response = await handler(request)
92+
if not config.aiohttp["disable_stream_timing_for_mem_leak"]:
93+
if isinstance(response, web.StreamResponse):
94+
request.task.add_done_callback(lambda _: finish_request_span(request, response))
95+
return response
96+
except Exception:
97+
req_span.set_traceback()
98+
raise
7999

80100
return attach_context
81101

@@ -142,9 +162,13 @@ async def on_prepare(request, response):
142162
the trace middleware execution.
143163
"""
144164
# NB isinstance is not appropriate here because StreamResponse is a parent of the other
145-
# aiohttp response types
146-
if type(response) is web.StreamResponse and not response.task.done():
147-
return
165+
# aiohttp response types. However in some cases this can also lead to missing the closing of
166+
# spans, leading to a memory leak, which is why we have this flag.
167+
# todo: this is a temporary fix for a memory leak in aiohttp. We should find a way to
168+
# consistently close spans with the correct timing.
169+
if not config.aiohttp["disable_stream_timing_for_mem_leak"]:
170+
if type(response) is web.StreamResponse and not response.task.done():
171+
return
148172
finish_request_span(request, response)
149173

150174

ddtrace/contrib/internal/aiohttp/patch.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from ddtrace.internal.utils import get_argument_value
2323
from ddtrace.internal.utils.formats import asbool
2424
from ddtrace.propagation.http import HTTPPropagator
25+
from ddtrace.settings._core import get_config as _get_config
2526
from ddtrace.trace import Pin
2627

2728

@@ -31,7 +32,12 @@
3132
# Server config
3233
config._add(
3334
"aiohttp",
34-
dict(distributed_tracing=True),
35+
dict(
36+
distributed_tracing=True,
37+
disable_stream_timing_for_mem_leak=asbool(
38+
_get_config("DD_AIOHTTP_CLIENT_DISABLE_STREAM_TIMING_FOR_MEM_LEAK", default=False)
39+
),
40+
),
3541
)
3642

3743
config._add(
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
3+
fixes:
4+
- |
5+
aiohttp: Adds the environment variable ``DD_AIOHTTP_CLIENT_DISABLE_STREAM_TIMING_FOR_MEM_LEAK`` to address a potential memory leak in the aiohttp integration. When set to true, this flag may cause streamed response span timing to be inaccurate. The flag defaults to false.

tests/contrib/aiohttp/test_request.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,33 @@ async def test_full_request(patched_app_tracer, aiohttp_client, loop):
3131
assert "GET /" == request_span.resource
3232

3333

34+
async def test_full_request_w_mem_leak_prevention_flag(patched_app_tracer, aiohttp_client, loop):
35+
config.aiohttp.disable_stream_timing_for_mem_leak = True
36+
try:
37+
app, tracer = patched_app_tracer
38+
client = await aiohttp_client(app)
39+
# it should create a root span when there is a handler hit
40+
# with the proper tags
41+
request = await client.request("GET", "/")
42+
assert 200 == request.status
43+
await request.text()
44+
# the trace is created
45+
traces = tracer.pop_traces()
46+
assert 1 == len(traces)
47+
assert 1 == len(traces[0])
48+
request_span = traces[0][0]
49+
assert_is_measured(request_span)
50+
51+
# request
52+
assert "aiohttp-web" == request_span.service
53+
assert "aiohttp.request" == request_span.name
54+
assert "GET /" == request_span.resource
55+
except Exception:
56+
raise
57+
finally:
58+
config.aiohttp.disable_stream_timing_for_mem_leak = False
59+
60+
3461
async def test_stream_request(patched_app_tracer, aiohttp_client, loop):
3562
app, tracer = patched_app_tracer
3663
async with await aiohttp_client(app) as client:

0 commit comments

Comments
 (0)