Skip to content

Commit

Permalink
Fix Isolated Azure Functions performance when using ASP.NET Core In…
Browse files Browse the repository at this point in the history
…tegration (#6567)

## Summary of changes

This fixes an application performance issue when using Isolated Azure
Functions with the `ASP.NET Core` Integration caused by us modifying a
`static` `TypedData` object when requests were being proxied via HTTP.


https://learn.microsoft.com/en-us/azure/azure-functions/dotnet-isolated-process-guide?tabs=hostbuilder%2Cwindows#aspnet-core-integration

## Reason for change

When using the `ASP.NET Core` integration with an Isolated Azure
Functions application (`ConfigureFunctionsWebApplication`) under load
the worker `ASP.NET Core` application would start to timeout on requests
or hang waiting for responses from the function application.

Ultimately it appears that within `GrpcMessageConversionExtensions` a
`static` instance of the `TypedData` that we used for injection was
added specifically when the requests were being proxied via HTTP
requests. Under load we'd end up modifying this shared object with our
propagated headers which would ultimately cause requests to start timing
out as we shouldn't have been modifying those headers.

## Implementation details

We now check for when the requests are being proxied and if so we
generate a new, non-`static` `TypedData` instance that we can safely
inject headers into.

Additionally, injection into these will now correctly adhere to whether
or not the Functions automatic instrumentation is enabled or not.

I was able to reliably reproduce the issue prior to the fix and haven't
seen the issue post-fix.

## Test coverage

- Added new Azure Functions Sample projects (and tests):
- `Samples.AzureFunctions.V4Isolated.AspNetCore.SdkV1` tests `ASP.NET
Core` integration with V1 of the NuGets
- `Samples.AzureFunctions.V4Isolated.AspNetCore.Sdk` tests `ASP.NET
Core` integration with V2 of the NuGets
- Dependabot is now enabled for these sample projects as Azure Functions
fell outside of our automated version range testing
- Load tested each version of the Azure Functions model locally both pre
and post fix to validate that the fix worked.
- Manually tested and checked traces / spans emitted from both `ASP.NET
Core` and `gRPC` Function models.

## Other details
<!-- Fixes #{issue} -->

Fixes #6494 

Noted some potential improvements in the signal-to-noise ratio of the
traces / spans produced by the Functions integration that we'll bring up
as future improvements.

<!-- ⚠️ Note: where possible, please obtain 2 approvals prior to
merging. Unless CODEOWNERS specifies otherwise, for external teams it is
typically best to have one review from a team member, and one review
from apm-dotnet. Trivial changes do not require 2 reviews. -->

---------

Co-authored-by: Andrew Lock <[email protected]>
  • Loading branch information
bouwkast and andrewlock authored Jan 20, 2025
1 parent 3375c75 commit 7e973a6
Show file tree
Hide file tree
Showing 28 changed files with 1,680 additions and 75 deletions.
35 changes: 15 additions & 20 deletions Datadog.Trace.Samples.g.sln
Original file line number Diff line number Diff line change
Expand Up @@ -129,22 +129,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Generated", "Generated", "{
tracer\build\supported_calltargets.g.json = tracer\build\supported_calltargets.g.json
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tracer", "tracer", "{82FFBC1A-6B13-4C0A-896A-90306AE4828F}"
ProjectSection(SolutionItems) = preProject
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{07D12F26-2583-4C6F-AFBB-AA30FF339FC6}"
ProjectSection(SolutionItems) = preProject
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test-applications", "test-applications", "{F8C637E1-1F4F-4E3B-9E34-AAD61097C3F8}"
ProjectSection(SolutionItems) = preProject
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "azure-functions", "azure-functions", "{FE9F14E0-8DFF-413B-BB9E-49CEA4115A5D}"
ProjectSection(SolutionItems) = preProject
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples.ExampleLibrary", "tracer\test\test-applications\integrations\dependency-libs\Samples.ExampleLibrary\Samples.ExampleLibrary.csproj", "{FDB5C8D0-018D-4FF9-9680-C6A5078F819B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Samples.ExampleLibraryTracer", "tracer\test\test-applications\integrations\dependency-libs\Samples.ExampleLibraryTracer\Samples.ExampleLibraryTracer.csproj", "{4B243CF1-4269-45C6-A238-1A9BFA58B8CC}"
Expand Down Expand Up @@ -445,6 +429,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AssemblyLoadContextResolve"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.AzureFunctions.V4Isolated.SdkV1", "tracer\test\test-applications\azure-functions\Samples.AzureFunctions.V4Isolated.SdkV1\Samples.AzureFunctions.V4Isolated.SdkV1.csproj", "{18767A3E-9ADC-485C-A8C7-50660D5B579D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.AzureFunctions.V4Isolated.AspNetCore.SdkV1", "tracer\test\test-applications\azure-functions\Samples.AzureFunctions.V4Isolated.AspNetCore.SdkV1\Samples.AzureFunctions.V4Isolated.AspNetCore.SdkV1.csproj", "{5D2C6B9C-FCE2-4E46-B4ED-BC3B11CFBB3C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.AzureFunctions.V4Isolated.AspNetCore", "tracer\test\test-applications\azure-functions\Samples.AzureFunctions.V4Isolated.AspNetCore\Samples.AzureFunctions.V4Isolated.AspNetCore.csproj", "{0F8EAB52-0C5B-4F60-92C5-42FAC21F4E77}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -1043,6 +1031,14 @@ Global
{18767A3E-9ADC-485C-A8C7-50660D5B579D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{18767A3E-9ADC-485C-A8C7-50660D5B579D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{18767A3E-9ADC-485C-A8C7-50660D5B579D}.Release|Any CPU.Build.0 = Release|Any CPU
{5D2C6B9C-FCE2-4E46-B4ED-BC3B11CFBB3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5D2C6B9C-FCE2-4E46-B4ED-BC3B11CFBB3C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5D2C6B9C-FCE2-4E46-B4ED-BC3B11CFBB3C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5D2C6B9C-FCE2-4E46-B4ED-BC3B11CFBB3C}.Release|Any CPU.Build.0 = Release|Any CPU
{0F8EAB52-0C5B-4F60-92C5-42FAC21F4E77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0F8EAB52-0C5B-4F60-92C5-42FAC21F4E77}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0F8EAB52-0C5B-4F60-92C5-42FAC21F4E77}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0F8EAB52-0C5B-4F60-92C5-42FAC21F4E77}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{9518425A-36A5-4B8F-B0B8-6137DB88441D} = {8CEC2042-F11C-49F5-A674-2355793B600A}
Expand All @@ -1063,9 +1059,6 @@ Global
{16427BFB-B4C6-46A9-A290-8EA51FF73FEA} = {9518425A-36A5-4B8F-B0B8-6137DB88441D}
{0884B566-D22E-498C-BAA9-26D50ABCAE3A} = {16427BFB-B4C6-46A9-A290-8EA51FF73FEA}
{E1B0F72C-991A-409D-9266-DE5ED1BD940E} = {A0C5FBBB-CFB2-4FB9-B8F0-55676E9DCF06}
{07D12F26-2583-4C6F-AFBB-AA30FF339FC6} = {82FFBC1A-6B13-4C0A-896A-90306AE4828F}
{F8C637E1-1F4F-4E3B-9E34-AAD61097C3F8} = {07D12F26-2583-4C6F-AFBB-AA30FF339FC6}
{FE9F14E0-8DFF-413B-BB9E-49CEA4115A5D} = {F8C637E1-1F4F-4E3B-9E34-AAD61097C3F8}
{FDB5C8D0-018D-4FF9-9680-C6A5078F819B} = {8683D82A-2BBE-4199-9C36-C59F48804F90}
{4B243CF1-4269-45C6-A238-1A9BFA58B8CC} = {8683D82A-2BBE-4199-9C36-C59F48804F90}
{086FF8A0-9CEE-470A-9751-78B0F1340649} = {BAF8F246-3645-42AD-B1D0-0F7EAFBAB34A}
Expand Down Expand Up @@ -1215,6 +1208,8 @@ Global
{2CA0D70C-DFC1-458A-871B-328AB6E87E3A} = {BAF8F246-3645-42AD-B1D0-0F7EAFBAB34A}
{D6155F26-8245-4B66-8944-79C3DF9F9DA3} = {BAF8F246-3645-42AD-B1D0-0F7EAFBAB34A}
{8B1AF6A7-DD41-4347-B637-90C23D69B50E} = {498A300E-D036-49B7-A43D-821D1CAF11A5}
{18767A3E-9ADC-485C-A8C7-50660D5B579D} = {FE9F14E0-8DFF-413B-BB9E-49CEA4115A5D}
{18767A3E-9ADC-485C-A8C7-50660D5B579D} = {C4C1E313-C7C1-4490-AECE-0DD0062380A4}
{5D2C6B9C-FCE2-4E46-B4ED-BC3B11CFBB3C} = {C4C1E313-C7C1-4490-AECE-0DD0062380A4}
{0F8EAB52-0C5B-4F60-92C5-42FAC21F4E77} = {C4C1E313-C7C1-4490-AECE-0DD0062380A4}
EndGlobalSection
EndGlobal
31 changes: 19 additions & 12 deletions Datadog.Trace.sln
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Microsoft Visual Studio Solution File, Format Version 12.00

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.1.31903.286
MinimumVisualStudioVersion = 15.0.26124.0
Expand Down Expand Up @@ -596,15 +597,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AssemblyLoadContextResolve"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.AspNet.MultipleAppsInDomain", "tracer\test\test-applications\aspnet\Samples.AspNet.MultipleAppsInDomain\Samples.AspNet.MultipleAppsInDomain.csproj", "{A82EB6F8-D8D0-4763-B252-08CA3F39D153}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tracer", "tracer", "{82FFBC1A-6B13-4C0A-896A-90306AE4828F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{07D12F26-2583-4C6F-AFBB-AA30FF339FC6}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test-applications", "test-applications", "{F8C637E1-1F4F-4E3B-9E34-AAD61097C3F8}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.AzureFunctions.V4Isolated.SdkV1", "tracer\test\test-applications\azure-functions\Samples.AzureFunctions.V4Isolated.SdkV1\Samples.AzureFunctions.V4Isolated.SdkV1.csproj", "{18767A3E-9ADC-485C-A8C7-50660D5B579D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "azure-functions", "azure-functions", "{FE9F14E0-8DFF-413B-BB9E-49CEA4115A5D}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.AzureFunctions.V4Isolated.AspNetCore.SdkV1", "tracer\test\test-applications\azure-functions\Samples.AzureFunctions.V4Isolated.AspNetCore.SdkV1\Samples.AzureFunctions.V4Isolated.AspNetCore.SdkV1.csproj", "{5D2C6B9C-FCE2-4E46-B4ED-BC3B11CFBB3C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.AzureFunctions.V4Isolated.SdkV1", "tracer\test\test-applications\azure-functions\Samples.AzureFunctions.V4Isolated.SdkV1\Samples.AzureFunctions.V4Isolated.SdkV1.csproj", "{18767A3E-9ADC-485C-A8C7-50660D5B579D}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Samples.AzureFunctions.V4Isolated.AspNetCore", "tracer\test\test-applications\azure-functions\Samples.AzureFunctions.V4Isolated.AspNetCore\Samples.AzureFunctions.V4Isolated.AspNetCore.csproj", "{0F8EAB52-0C5B-4F60-92C5-42FAC21F4E77}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -1442,6 +1439,14 @@ Global
{18767A3E-9ADC-485C-A8C7-50660D5B579D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{18767A3E-9ADC-485C-A8C7-50660D5B579D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{18767A3E-9ADC-485C-A8C7-50660D5B579D}.Release|Any CPU.Build.0 = Release|Any CPU
{5D2C6B9C-FCE2-4E46-B4ED-BC3B11CFBB3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5D2C6B9C-FCE2-4E46-B4ED-BC3B11CFBB3C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5D2C6B9C-FCE2-4E46-B4ED-BC3B11CFBB3C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5D2C6B9C-FCE2-4E46-B4ED-BC3B11CFBB3C}.Release|Any CPU.Build.0 = Release|Any CPU
{0F8EAB52-0C5B-4F60-92C5-42FAC21F4E77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0F8EAB52-0C5B-4F60-92C5-42FAC21F4E77}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0F8EAB52-0C5B-4F60-92C5-42FAC21F4E77}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0F8EAB52-0C5B-4F60-92C5-42FAC21F4E77}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1674,10 +1679,9 @@ Global
{D6155F26-8245-4B66-8944-79C3DF9F9DA3} = {BAF8F246-3645-42AD-B1D0-0F7EAFBAB34A}
{8B1AF6A7-DD41-4347-B637-90C23D69B50E} = {498A300E-D036-49B7-A43D-821D1CAF11A5}
{A82EB6F8-D8D0-4763-B252-08CA3F39D153} = {AFA0AB23-64F0-4AC1-9050-6CE8FE06F580}
{07D12F26-2583-4C6F-AFBB-AA30FF339FC6} = {82FFBC1A-6B13-4C0A-896A-90306AE4828F}
{F8C637E1-1F4F-4E3B-9E34-AAD61097C3F8} = {07D12F26-2583-4C6F-AFBB-AA30FF339FC6}
{FE9F14E0-8DFF-413B-BB9E-49CEA4115A5D} = {F8C637E1-1F4F-4E3B-9E34-AAD61097C3F8}
{18767A3E-9ADC-485C-A8C7-50660D5B579D} = {FE9F14E0-8DFF-413B-BB9E-49CEA4115A5D}
{18767A3E-9ADC-485C-A8C7-50660D5B579D} = {C4C1E313-C7C1-4490-AECE-0DD0062380A4}
{5D2C6B9C-FCE2-4E46-B4ED-BC3B11CFBB3C} = {C4C1E313-C7C1-4490-AECE-0DD0062380A4}
{0F8EAB52-0C5B-4F60-92C5-42FAC21F4E77} = {C4C1E313-C7C1-4490-AECE-0DD0062380A4}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {160A1D00-1F5B-40F8-A155-621B4459D78F}
Expand All @@ -1692,7 +1696,9 @@ Global
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{0e036453-2c80-4fc9-a517-771f0071734b}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{0f0f7d45-0e13-42b0-a158-8f303bbe8358}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{0f1d9fb5-4415-40f1-b7b0-6dd5a3bab0c4}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{0f8eab52-0c5b-4f60-92c5-42fac21f4e77}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{10619ba2-aed1-482a-8570-bb7c7b83dddc}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{18767a3e-9adc-485c-a8c7-50660d5b579d}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{18a6904a-5afd-4816-ac3f-9f5e433720b5}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{1a5e9f40-f3a5-4b59-9898-3dcd65c459c3}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{1b3e6bee-f7ab-433e-a1d9-e8be3782419b}*SharedItemsImports = 5
Expand Down Expand Up @@ -1733,6 +1739,7 @@ Global
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{56de0d44-e9e5-48da-baea-2934b1e28d4e}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{5a806f4b-39e7-4f38-b36f-f5cfc4f8760a}*SharedItemsImports = 13
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{5c2829c2-ed0d-414c-b5a0-2bfdca07b493}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{5d2c6b9c-fce2-4e46-b4ed-bc3b11cfbb3c}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{5e290fa1-e87b-4782-b977-eb5fa6c96efe}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{5ee6b6eb-b768-47ec-882b-8dcaca2b1360}*SharedItemsImports = 5
tracer\test\test-applications\Samples.Shared\Samples.Shared.projitems*{600953c4-bd8f-4a4b-a275-6d6f9ef48342}*SharedItemsImports = 5
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,26 +280,6 @@ _ when type.StartsWith("eventGrid", StringComparison.OrdinalIgnoreCase) => "Even
return scope;
}

internal static void OverridePropagatedContext<TTarget, TTypeData>(Tracer tracer, TTypeData typedData, string? useNullableHeadersCapability)
where TTypeData : ITypedData
{
if (tracer.Settings.IsIntegrationEnabled(IntegrationId)
&& tracer.ActiveScope is Scope { Span: { OperationName: OperationName } span })
{
// The HTTP request represented by TypedData is a duplicate of the original incoming
// request that was received by func.exe. This is used to create a span representing
// the request from the client. The typed data is then sent by the GRPC connection
// to the functions app and is used to invoke the actual function. We intercept that
// in the functions app and use it to create a span representing the actual work of the app.
// In order for the span hierarchy/parenting to work correctly, we need to replace the parentID
// in the GRPC http request representation, which is what we're doing here by overwriting all
// the existing datadog headers
var useNullableHeaders = !string.IsNullOrEmpty(useNullableHeadersCapability);
var context = new PropagationContext(span.Context, Baggage.Current);
tracer.TracerManager.SpanContextPropagator.Inject(context, new RpcHttpHeadersCollection<TTarget>(typedData.Http, useNullableHeaders));
}
}

private static PropagationContext ExtractPropagatedContextFromHttp<T>(T context, string? bindingName)
where T : IFunctionContext
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
using System;
using System.ComponentModel;
using Datadog.Trace.ClrProfiler.CallTarget;
using Datadog.Trace.DuckTyping;
using Datadog.Trace.Propagators;
using Microsoft.AspNetCore.Http;

namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.Azure.Functions;
Expand All @@ -31,17 +33,62 @@ namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.Azure.Functions;
public class GrpcMessageConversionExtensionsToRpcHttpIntegration
{
internal static CallTargetState OnMethodBegin<TTarget, TLogger, TGrpcCapabilities>(TTarget nullInstance, HttpRequest request, TLogger logger, TGrpcCapabilities capabilities)
where TGrpcCapabilities : IGrpcCapabilities
{
var capability = capabilities.GetCapabilityState("UseNullableValueDictionaryForHttp");
return new CallTargetState(scope: null, state: capability);
return new CallTargetState(scope: null, state: capabilities);
}

internal static TReturn OnAsyncMethodEnd<TTarget, TReturn>(TTarget nullInstance, TReturn returnValue, Exception exception, in CallTargetState state)
where TReturn : ITypedData
internal static TReturn OnAsyncMethodEnd<TTarget, TReturn>(TTarget nullInstance, TReturn returnValue, Exception? exception, in CallTargetState state)
// We can't do this now, as we need to get and return the _real_ underlying type
// where TReturn : ITypedData
{
AzureFunctionsCommon.OverridePropagatedContext<TTarget, TReturn>(Tracer.Instance, returnValue, state.State as string);
return returnValue;
var capabilities = state.State.DuckCast<IGrpcCapabilities>();
if (capabilities is null || returnValue is null)
{
// Something went wrong, this shouldn't happen
return returnValue;
}

var tracer = Tracer.Instance;
if (!tracer.Settings.IsIntegrationEnabled(AzureFunctionsCommon.IntegrationId)
|| tracer.ActiveScope is not Scope { Span: { OperationName: AzureFunctionsCommon.OperationName } span })
{
return returnValue;
}

// The HTTP request represented by TypedData is essentially a duplicate of the original incoming
// request that was received by func.exe. This is used to create a span representing
// the request from the client. The typed data is then sent by the GRPC connection
// to the functions app and is used to invoke the actual function. We intercept that
// in the functions app and use it to create a span representing the actual work of the app.
// In order for the span hierarchy/parenting to work correctly, we need to replace the parentID
// in the GRPC http request representation, which is what we're doing here by overwriting all
// the existing datadog headers
//
// However, when using the AspNetCore integration, things work a bit differently. The
// func.exe app instead primarily _proxies_ the HTTP request (if it is an HTTP trigger) to the functions
// app, instead of sending the bulk of the context as a gRPC message. This means that we _shouldn't_ inject the
// context into the gRPC request, because the context is already present in the incoming HTTP request, and is
// used preferentially.
//
// What's more, in the case of HTTP triggers with proxying enabled, the TypedData returnValue returned from
// this is method is a shared object, so mutating it can cause issues with other parts of the system.
// See https://github.com/Azure/azure-functions-host/blob/420a4686802612857cae35cefea2b685283507c9/src/WebJobs.Script.Grpc/MessageExtensions/GrpcMessageConversionExtensions.cs#L104-L126

var isHttpProxying = !string.IsNullOrEmpty(capabilities.GetCapabilityState("HttpUri"));
var requiresRouteParameters = !string.IsNullOrEmpty(capabilities.GetCapabilityState("RequiresRouteParameters"));
var useNullableHeaders = !string.IsNullOrEmpty(capabilities.GetCapabilityState("UseNullableValueDictionaryForHttp"));

// When proxying, this method returns a singleton value that we must not update, so we create a new one
// If we're not proxying, we can safely inject the context into the provided gRPC request
var typedData = isHttpProxying && !requiresRouteParameters
? TypedDataHelper<TReturn>.CreateTypedData()
: returnValue.DuckCast<ITypedData>();

var context = new PropagationContext(span.Context, Baggage.Current);
tracer.TracerManager.SpanContextPropagator.Inject(context, new RpcHttpHeadersCollection<TTarget>(typedData.Http, useNullableHeaders));

// Get the "real" value back out, whether it's the one we were provided or the new one we created
return (TReturn)typedData.Instance!;
}
}
#endif
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// <copyright file="RpcHttpStruct.cs" company="Datadog">
// <copyright file="IRpcHttp.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>
Expand All @@ -13,15 +13,17 @@ namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.Azure.Functions;

/// <summary>
/// Duck type for RpcHttp
/// This can't be a [DuckCopy] struct, because we set the Http property on TypedData to an instance of RpcHttp
/// and a [DuckCopy] struct is _purely_ for extracting properites
/// </summary>
[DuckCopy]
internal struct RpcHttpStruct
internal interface IRpcHttp
{
/// <summary>
/// An IDictionary&lt;string, NullableString&gt;
/// Gets an IDictionary&lt;string, NullableString&gt;
/// </summary>
public IDictionary NullableHeaders;
public IDictionary<string, string> Headers;
public IDictionary NullableHeaders { get; }

public IDictionary<string, string> Headers { get; }
}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>

using Datadog.Trace.DuckTyping;

#if !NETFRAMEWORK
#nullable enable

Expand All @@ -13,8 +15,9 @@ namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.Azure.Functions;
/// Interface because used in integration definition
/// https://github.com/Azure/azure-functions-host/blob/8ceb05a89a4337f07264d4991545538a3e8b58a0/src/WebJobs.Script.Grpc/azure-functions-language-worker-protobuf/src/proto/FunctionRpc.proto#L443
/// </summary>
internal interface ITypedData
///
internal interface ITypedData : IDuckType
{
public RpcHttpStruct Http { get; }
public IRpcHttp Http { get; set; }
}
#endif
Loading

0 comments on commit 7e973a6

Please sign in to comment.