Skip to content

Fix exception in Kafka on .NET Framework#8366

Merged
bouwkast merged 4 commits into
masterfrom
steven/kafka-fix
Mar 30, 2026
Merged

Fix exception in Kafka on .NET Framework#8366
bouwkast merged 4 commits into
masterfrom
steven/kafka-fix

Conversation

@bouwkast

@bouwkast bouwkast commented Mar 24, 2026

Copy link
Copy Markdown
Collaborator

Summary of changes

Ran Kafka locally as I was attempting to port it to Testcontainers and was actually get exceptions / errors on it, this resolves them (or at least it seems to resolve them). Also noticed them in Error Tracking, but only a single instance, but I'm not 100% sure if it was the same, Error Tracking is linked in the linked Jira Ticket.

Reason for change

Previously this was using IsCompletedSuccessfully and then doing a cast and enumeration of all properties and was failing with the following error as IsCompletedSuccessfully isn't available on .NET Framework:

2026-03-24 15:43:11.768 -04:00 [WRN] Error extracting cluster_id from Kafka metadata Datadog.Trace.DuckTyping.DuckTypePropertyOrFieldNotFoundException: The property or field 'IsCompletedSuccessfully' for the proxy property 'IsCompletedSuccessfully' was not found in the instance of type 'System.Threading.Tasks.Task`1[[Confluent.Kafka.Admin.DescribeClusterResult, Confluent.Kafka, Version=2.6.1.0, Culture=neutral, PublicKeyToken=12c514ca49093d1e]]'.
   at Datadog.Trace.DuckTyping.DuckTypePropertyOrFieldNotFoundException.Throw(String name, String duckAttributeName, Type type)
   at Datadog.Trace.DuckTyping.DuckType.CreateProperties(TypeBuilder proxyTypeBuilder, Type proxyDefinitionType, Type targetType, FieldInfo instanceField)
   at Datadog.Trace.DuckTyping.DuckType.CreateProxyType(Type proxyDefinitionType, Type targetType, Boolean dryRun)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Datadog.Trace.DuckTyping.DuckType.CreateTypeResult.ThrowOnError[T](Object instance)
   at Datadog.Trace.DuckTyping.DuckType.CreateTypeResult.CreateInstance[T](Object instance)
   at Datadog.Trace.DuckTyping.DuckType.CreateCache`1.Create(Object instance)
   at Datadog.Trace.ClrProfiler.AutoInstrumentation.Kafka.KafkaHelper.DescribeClusterWithTimeout(IAdminClient adminClient, Type describeClusterOptionsType)
   at Datadog.Trace.ClrProfiler.AutoInstrumentation.Kafka.KafkaHelper.GetClusterId(String bootstrapServers, Object clientInstance)
 { MachineName: ".", Process: "[59476 Samples.Kafka]", AppDomain: "[1 Samples.Kafka.exe]", TracerVersion: "3.41.0.0" }

Implementation details

Remove IsCompletedSuccessfully from IDuckTypeTask as it isn't there on .NET Framework, attempted initially to go with TaskStatus but this caused failures for xUnit/MS Test integration tests as those appear to be using ValueTask, which doesn't have it.

Removing IsCompletedSuccessfully required some more changes to KafkaHelper

Test coverage

Applied the fix, re-ran didn't see again, however, the Error Tracking one may be a different bug as it appears that it is on .NET 9.0 😕

Edit: yes appears to be a different error that came up during development that was addressed

Other details

Fixes https://datadoghq.atlassian.net/browse/APMLP-1146

@bouwkast
bouwkast requested review from a team as code owners March 24, 2026 20:49
@bouwkast bouwkast added the AI Generated Largely based on code generated by an AI or LLM. This label is the same across all dd-trace-* repos label Mar 24, 2026

@robcarlan-datadog robcarlan-datadog left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@pr-commenter

pr-commenter Bot commented Mar 24, 2026

Copy link
Copy Markdown

Benchmarks

Benchmark execution time: 2026-03-27 19:55:43

Comparing candidate commit dcc705d in PR branch steven/kafka-fix with baseline commit 5ff69b3 in branch master.

Found 4 performance improvements and 16 performance regressions! Performance is the same for 250 metrics, 18 unstable metrics.

Explanation

This is an A/B test comparing a candidate commit's performance against that of a baseline commit. Performance changes are noted in the tables below as:

  • 🟩 = significantly better candidate vs. baseline
  • 🟥 = significantly worse candidate vs. baseline

We compute a confidence interval (CI) over the relative difference of means between metrics from the candidate and baseline commits, considering the baseline as the reference.

If the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD), the change is considered significant.

Feel free to reach out to #apm-benchmarking-platform on Slack if you have any questions.

More details about the CI and significant changes

You can imagine this CI as a range of values that is likely to contain the true difference of means between the candidate and baseline commits.

CIs of the difference of means are often centered around 0%, because often changes are not that big:

---------------------------------(------|---^--------)-------------------------------->
                              -0.6%    0%  0.3%     +1.2%
                                 |          |        |
         lower bound of the CI --'          |        |
sample mean (center of the CI) -------------'        |
         upper bound of the CI ----------------------'

As described above, a change is considered significant if the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD).

For instance, for an execution time metric, this confidence interval indicates a significantly worse performance:

----------------------------------------|---------|---(---------^---------)---------->
                                       0%        1%  1.3%      2.2%      3.1%
                                                  |   |         |         |
       significant impact threshold --------------'   |         |         |
                      lower bound of CI --------------'         |         |
       sample mean (center of the CI) --------------------------'         |
                      upper bound of CI ----------------------------------'

scenario:Benchmarks.Trace.AgentWriterBenchmark.WriteAndFlushEnrichedTraces net6.0

  • 🟥 execution_time [+78.587ms; +78.670ms] or [+63.036%; +63.103%]

scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.AllCycleSimpleBody net6.0

  • 🟩 execution_time [-20.690ms; -14.228ms] or [-9.641%; -6.630%]

scenario:Benchmarks.Trace.AspNetCoreBenchmark.SendRequest net6.0

  • 🟥 execution_time [+105.609ms; +106.802ms] or [+113.549%; +114.831%]

scenario:Benchmarks.Trace.CIVisibilityProtocolWriterBenchmark.WriteAndFlushEnrichedTraces net472

  • 🟥 execution_time [+14.142ms; +20.777ms] or [+7.301%; +10.726%]
  • 🟥 throughput [-113.161op/s; -77.973op/s] or [-9.781%; -6.740%]

scenario:Benchmarks.Trace.CIVisibilityProtocolWriterBenchmark.WriteAndFlushEnrichedTraces net6.0

  • 🟥 throughput [-226.301op/s; -188.819op/s] or [-13.667%; -11.403%]

scenario:Benchmarks.Trace.CharSliceBenchmark.OptimizedCharSlice net6.0

  • 🟥 execution_time [+145.618µs; +152.542µs] or [+10.483%; +10.981%]
  • 🟥 throughput [-71.517op/s; -68.044op/s] or [-9.934%; -9.452%]

scenario:Benchmarks.Trace.CharSliceBenchmark.OptimizedCharSlice netcoreapp3.1

  • 🟥 throughput [-196.415op/s; -146.544op/s] or [-37.266%; -27.804%]

scenario:Benchmarks.Trace.DbCommandBenchmark.ExecuteNonQuery net6.0

  • 🟥 execution_time [+17.122ms; +20.889ms] or [+8.802%; +10.739%]

scenario:Benchmarks.Trace.ElasticsearchBenchmark.CallElasticsearchAsync netcoreapp3.1

  • 🟥 throughput [-33425.588op/s; -26156.088op/s] or [-6.935%; -5.427%]

scenario:Benchmarks.Trace.GraphQLBenchmark.ExecuteAsync netcoreapp3.1

  • 🟩 throughput [+27409.856op/s; +36567.168op/s] or [+6.792%; +9.061%]

scenario:Benchmarks.Trace.Iast.StringAspectsBenchmark.StringConcatAspectBenchmark netcoreapp3.1

  • 🟥 allocated_mem [+60.135KB; +60.180KB] or [+21.672%; +21.688%]

scenario:Benchmarks.Trace.Log4netBenchmark.EnrichedLog netcoreapp3.1

  • 🟥 execution_time [+13.552ms; +15.050ms] or [+7.251%; +8.052%]

scenario:Benchmarks.Trace.NLogBenchmark.EnrichedLog net6.0

  • 🟥 execution_time [+22.799ms; +25.448ms] or [+12.219%; +13.638%]

scenario:Benchmarks.Trace.SingleSpanAspNetCoreBenchmark.SingleSpanAspNetCore netcoreapp3.1

  • 🟩 throughput [+15227706.182op/s; +16147001.755op/s] or [+6.752%; +7.160%]

scenario:Benchmarks.Trace.SpanBenchmark.StartFinishScope netcoreapp3.1

  • 🟥 execution_time [+10.857ms; +15.696ms] or [+5.620%; +8.124%]

scenario:Benchmarks.Trace.SpanBenchmark.StartFinishSpan net6.0

  • 🟥 execution_time [+14.669ms; +20.161ms] or [+7.366%; +10.124%]

scenario:Benchmarks.Trace.SpanBenchmark.StartFinishTwoScopes net6.0

  • 🟩 execution_time [-21.938ms; -16.445ms] or [-10.078%; -7.555%]

scenario:Benchmarks.Trace.TraceAnnotationsBenchmark.RunOnMethodBegin net6.0

  • 🟥 execution_time [+10.643ms; +15.949ms] or [+5.367%; +8.043%]

{
// Restore the original synchronization context.
SynchronizationContext.SetSynchronizationContext(originalContext);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is literally

public static void SafeWait(this Task task)
{
if (task.IsCompleted)
{
return;
}
var originalContext = SynchronizationContext.Current;
try
{
// Set the synchronization context to null to avoid deadlocks.
SynchronizationContext.SetSynchronizationContext(null);
// Wait synchronously for the task to complete.
task.GetAwaiter().GetResult();
}
finally
{
// Restore the original synchronization context.
SynchronizationContext.SetSynchronizationContext(originalContext);
}
}

please use the extension method.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately it wasn't quite possible 😅 #7702 (comment)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think these changes were necessary if the IDuckType fix in 7278249 is good enough

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I've tried again dcc705d

@tonyredondo tonyredondo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why aren't we fixing IDuckTypeTask instead?

@bouwkast

Copy link
Copy Markdown
Collaborator Author

Why aren't we fixing IDuckTypeTask instead?

@tonyredondo Thanks, I modified IDuckTypeTask to TaskStatus Status { get; } I think that is the fix

@andrewlock andrewlock left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch + fix 👍

@bouwkast

Copy link
Copy Markdown
Collaborator Author

Why aren't we fixing IDuckTypeTask instead?

@tonyredondo Thanks, I modified IDuckTypeTask to TaskStatus Status { get; } I think that is the fix

Well this is now failing the xunit instrumentation tests 😭

This reverts commit 7278249.
This broke the xunit / MS Test tests and I don't
see a clean way to enable both Kafka and them
@dd-trace-dotnet-ci-bot

Copy link
Copy Markdown

Execution-Time Benchmarks Report ⏱️

Execution-time results for samples comparing This PR (8366) and master.

✅ No regressions detected - check the details below

Full Metrics Comparison

FakeDbCommand

Metric Master (Mean ± 95% CI) Current (Mean ± 95% CI) Change Status
.NET Framework 4.8 - Baseline
duration72.56 ± (72.63 - 73.02) ms71.94 ± (71.90 - 72.34) ms-0.9%
.NET Framework 4.8 - Bailout
duration76.19 ± (76.05 - 76.35) ms76.00 ± (75.90 - 76.19) ms-0.2%
.NET Framework 4.8 - CallTarget+Inlining+NGEN
duration1075.19 ± (1077.10 - 1083.89) ms1073.99 ± (1075.63 - 1083.55) ms-0.1%
.NET Core 3.1 - Baseline
process.internal_duration_ms22.41 ± (22.37 - 22.45) ms22.24 ± (22.20 - 22.28) ms-0.8%
process.time_to_main_ms84.13 ± (83.92 - 84.33) ms83.42 ± (83.24 - 83.60) ms-0.8%
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed10.90 ± (10.89 - 10.90) MB10.91 ± (10.91 - 10.91) MB+0.1%✅⬆️
runtime.dotnet.threads.count12 ± (12 - 12)12 ± (12 - 12)+0.0%
.NET Core 3.1 - Bailout
process.internal_duration_ms22.26 ± (22.22 - 22.29) ms22.23 ± (22.18 - 22.27) ms-0.1%
process.time_to_main_ms84.67 ± (84.47 - 84.87) ms85.26 ± (85.06 - 85.46) ms+0.7%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed10.93 ± (10.93 - 10.94) MB10.95 ± (10.94 - 10.95) MB+0.1%✅⬆️
runtime.dotnet.threads.count13 ± (13 - 13)13 ± (13 - 13)+0.0%
.NET Core 3.1 - CallTarget+Inlining+NGEN
process.internal_duration_ms224.96 ± (223.74 - 226.17) ms225.43 ± (224.02 - 226.84) ms+0.2%✅⬆️
process.time_to_main_ms536.75 ± (535.53 - 537.96) ms535.08 ± (533.57 - 536.59) ms-0.3%
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed48.10 ± (48.07 - 48.14) MB48.25 ± (48.22 - 48.28) MB+0.3%✅⬆️
runtime.dotnet.threads.count28 ± (28 - 28)28 ± (28 - 28)+0.0%✅⬆️
.NET 6 - Baseline
process.internal_duration_ms21.08 ± (21.04 - 21.11) ms21.19 ± (21.16 - 21.23) ms+0.6%✅⬆️
process.time_to_main_ms72.31 ± (72.15 - 72.46) ms72.75 ± (72.58 - 72.92) ms+0.6%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed10.63 ± (10.62 - 10.63) MB10.64 ± (10.63 - 10.64) MB+0.1%✅⬆️
runtime.dotnet.threads.count10 ± (10 - 10)10 ± (10 - 10)+0.0%
.NET 6 - Bailout
process.internal_duration_ms20.96 ± (20.93 - 20.99) ms21.01 ± (20.97 - 21.05) ms+0.2%✅⬆️
process.time_to_main_ms73.34 ± (73.18 - 73.50) ms73.57 ± (73.39 - 73.74) ms+0.3%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed10.74 ± (10.74 - 10.74) MB10.74 ± (10.74 - 10.75) MB+0.1%✅⬆️
runtime.dotnet.threads.count11 ± (11 - 11)11 ± (11 - 11)+0.0%
.NET 6 - CallTarget+Inlining+NGEN
process.internal_duration_ms213.08 ± (211.82 - 214.34) ms213.26 ± (212.06 - 214.47) ms+0.1%✅⬆️
process.time_to_main_ms533.30 ± (531.92 - 534.67) ms535.08 ± (533.78 - 536.38) ms+0.3%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed50.09 ± (50.05 - 50.12) MB50.13 ± (50.10 - 50.16) MB+0.1%✅⬆️
runtime.dotnet.threads.count29 ± (29 - 29)29 ± (29 - 29)+0.1%✅⬆️
.NET 8 - Baseline
process.internal_duration_ms19.19 ± (19.16 - 19.22) ms19.22 ± (19.18 - 19.25) ms+0.1%✅⬆️
process.time_to_main_ms71.60 ± (71.44 - 71.75) ms71.71 ± (71.56 - 71.85) ms+0.2%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed7.68 ± (7.67 - 7.68) MB7.71 ± (7.70 - 7.71) MB+0.4%✅⬆️
runtime.dotnet.threads.count10 ± (10 - 10)10 ± (10 - 10)+0.0%
.NET 8 - Bailout
process.internal_duration_ms19.15 ± (19.12 - 19.18) ms19.35 ± (19.31 - 19.39) ms+1.0%✅⬆️
process.time_to_main_ms72.24 ± (72.14 - 72.35) ms73.24 ± (73.06 - 73.42) ms+1.4%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed7.73 ± (7.73 - 7.74) MB7.75 ± (7.75 - 7.76) MB+0.3%✅⬆️
runtime.dotnet.threads.count11 ± (11 - 11)11 ± (11 - 11)+0.0%
.NET 8 - CallTarget+Inlining+NGEN
process.internal_duration_ms159.45 ± (158.71 - 160.19) ms160.76 ± (159.91 - 161.61) ms+0.8%✅⬆️
process.time_to_main_ms491.21 ± (490.28 - 492.14) ms494.43 ± (493.40 - 495.47) ms+0.7%✅⬆️
runtime.dotnet.exceptions.count0 ± (0 - 0)0 ± (0 - 0)+0.0%
runtime.dotnet.mem.committed36.97 ± (36.95 - 36.99) MB36.99 ± (36.96 - 37.01) MB+0.1%✅⬆️
runtime.dotnet.threads.count28 ± (28 - 28)28 ± (28 - 28)+0.2%✅⬆️

HttpMessageHandler

Metric Master (Mean ± 95% CI) Current (Mean ± 95% CI) Change Status
.NET Framework 4.8 - Baseline
duration215.20 ± (214.98 - 216.05) ms222.16 ± (221.79 - 223.43) ms+3.2%✅⬆️
.NET Framework 4.8 - Bailout
duration220.43 ± (220.27 - 221.33) ms226.28 ± (225.52 - 227.12) ms+2.7%✅⬆️
.NET Framework 4.8 - CallTarget+Inlining+NGEN
duration1249.02 ± (1247.78 - 1253.68) ms1270.18 ± (1269.65 - 1277.12) ms+1.7%✅⬆️
.NET Core 3.1 - Baseline
process.internal_duration_ms212.12 ± (211.48 - 212.76) ms217.94 ± (217.01 - 218.88) ms+2.7%✅⬆️
process.time_to_main_ms90.75 ± (90.48 - 91.02) ms92.55 ± (92.20 - 92.91) ms+2.0%✅⬆️
runtime.dotnet.exceptions.count3 ± (3 - 3)3 ± (3 - 3)+0.0%
runtime.dotnet.mem.committed15.86 ± (15.84 - 15.87) MB15.77 ± (15.75 - 15.79) MB-0.5%
runtime.dotnet.threads.count20 ± (20 - 20)20 ± (20 - 20)+0.1%✅⬆️
.NET Core 3.1 - Bailout
process.internal_duration_ms210.85 ± (210.23 - 211.46) ms216.92 ± (215.97 - 217.87) ms+2.9%✅⬆️
process.time_to_main_ms92.05 ± (91.79 - 92.31) ms93.33 ± (93.02 - 93.64) ms+1.4%✅⬆️
runtime.dotnet.exceptions.count3 ± (3 - 3)3 ± (3 - 3)+0.0%
runtime.dotnet.mem.committed15.98 ± (15.97 - 16.00) MB15.86 ± (15.84 - 15.88) MB-0.8%
runtime.dotnet.threads.count21 ± (21 - 21)21 ± (21 - 21)+0.2%✅⬆️
.NET Core 3.1 - CallTarget+Inlining+NGEN
process.internal_duration_ms424.16 ± (422.69 - 425.63) ms425.68 ± (424.09 - 427.26) ms+0.4%✅⬆️
process.time_to_main_ms568.33 ± (566.99 - 569.67) ms571.18 ± (569.45 - 572.91) ms+0.5%✅⬆️
runtime.dotnet.exceptions.count3 ± (3 - 3)3 ± (3 - 3)+0.0%
runtime.dotnet.mem.committed59.11 ± (59.07 - 59.15) MB59.16 ± (59.12 - 59.21) MB+0.1%✅⬆️
runtime.dotnet.threads.count30 ± (30 - 30)30 ± (30 - 30)+0.3%✅⬆️
.NET 6 - Baseline
process.internal_duration_ms219.26 ± (218.37 - 220.14) ms224.27 ± (223.14 - 225.40) ms+2.3%✅⬆️
process.time_to_main_ms79.55 ± (79.33 - 79.76) ms80.42 ± (80.14 - 80.70) ms+1.1%✅⬆️
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed16.15 ± (16.13 - 16.16) MB16.09 ± (16.08 - 16.11) MB-0.3%
runtime.dotnet.threads.count20 ± (19 - 20)20 ± (20 - 20)+0.4%✅⬆️
.NET 6 - Bailout
process.internal_duration_ms218.28 ± (217.57 - 218.98) ms223.89 ± (222.70 - 225.08) ms+2.6%✅⬆️
process.time_to_main_ms80.72 ± (80.51 - 80.92) ms81.29 ± (81.01 - 81.56) ms+0.7%✅⬆️
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed16.13 ± (16.12 - 16.15) MB16.12 ± (16.10 - 16.13) MB-0.1%
runtime.dotnet.threads.count21 ± (21 - 21)21 ± (21 - 21)+0.1%✅⬆️
.NET 6 - CallTarget+Inlining+NGEN
process.internal_duration_ms452.55 ± (450.78 - 454.32) ms471.47 ± (464.84 - 478.10) ms+4.2%✅⬆️
process.time_to_main_ms575.04 ± (573.66 - 576.42) ms577.98 ± (576.39 - 579.56) ms+0.5%✅⬆️
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed60.60 ± (60.52 - 60.68) MB60.45 ± (60.35 - 60.54) MB-0.3%
runtime.dotnet.threads.count31 ± (31 - 31)31 ± (31 - 31)-0.2%
.NET 8 - Baseline
process.internal_duration_ms221.30 ± (220.22 - 222.38) ms216.23 ± (215.08 - 217.38) ms-2.3%
process.time_to_main_ms78.81 ± (78.57 - 79.05) ms78.39 ± (78.06 - 78.71) ms-0.5%
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed11.47 ± (11.45 - 11.48) MB11.52 ± (11.50 - 11.54) MB+0.4%✅⬆️
runtime.dotnet.threads.count19 ± (19 - 19)19 ± (19 - 19)-0.0%
.NET 8 - Bailout
process.internal_duration_ms221.14 ± (220.01 - 222.28) ms220.33 ± (219.27 - 221.39) ms-0.4%
process.time_to_main_ms80.19 ± (79.95 - 80.42) ms79.95 ± (79.72 - 80.19) ms-0.3%
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed11.51 ± (11.49 - 11.53) MB11.54 ± (11.52 - 11.56) MB+0.2%✅⬆️
runtime.dotnet.threads.count20 ± (20 - 20)20 ± (20 - 20)+0.3%✅⬆️
.NET 8 - CallTarget+Inlining+NGEN
process.internal_duration_ms465.05 ± (460.41 - 469.70) ms468.56 ± (463.84 - 473.28) ms+0.8%✅⬆️
process.time_to_main_ms531.15 ± (530.11 - 532.20) ms533.24 ± (531.92 - 534.57) ms+0.4%✅⬆️
runtime.dotnet.exceptions.count4 ± (4 - 4)4 ± (4 - 4)+0.0%
runtime.dotnet.mem.committed50.63 ± (50.53 - 50.72) MB50.63 ± (50.52 - 50.73) MB+0.0%✅⬆️
runtime.dotnet.threads.count30 ± (30 - 30)30 ± (30 - 30)+0.0%✅⬆️
Comparison explanation

Execution-time benchmarks measure the whole time it takes to execute a program, and are intended to measure the one-off costs. Cases where the execution time results for the PR are worse than latest master results are highlighted in **red**. The following thresholds were used for comparing the execution times:

  • Welch test with statistical test for significance of 5%
  • Only results indicating a difference greater than 5% and 5 ms are considered.

Note that these results are based on a single point-in-time result for each branch. For full results, see the dashboard.

Graphs show the p99 interval based on the mean and StdDev of the test run, as well as the mean value of the run (shown as a diamond below the graph).

Duration charts
FakeDbCommand (.NET Framework 4.8)
gantt
    title Execution time (ms) FakeDbCommand (.NET Framework 4.8)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8366) - mean (72ms)  : 69, 76
    master - mean (73ms)  : 70, 76

    section Bailout
    This PR (8366) - mean (76ms)  : 75, 77
    master - mean (76ms)  : 75, 78

    section CallTarget+Inlining+NGEN
    This PR (8366) - mean (1,080ms)  : 1021, 1138
    master - mean (1,080ms)  : 1032, 1129

Loading
FakeDbCommand (.NET Core 3.1)
gantt
    title Execution time (ms) FakeDbCommand (.NET Core 3.1)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8366) - mean (112ms)  : 109, 116
    master - mean (113ms)  : 109, 118

    section Bailout
    This PR (8366) - mean (114ms)  : 112, 117
    master - mean (113ms)  : 110, 117

    section CallTarget+Inlining+NGEN
    This PR (8366) - mean (796ms)  : 778, 815
    master - mean (800ms)  : 782, 817

Loading
FakeDbCommand (.NET 6)
gantt
    title Execution time (ms) FakeDbCommand (.NET 6)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8366) - mean (100ms)  : 97, 103
    master - mean (99ms)  : 96, 103

    section Bailout
    This PR (8366) - mean (101ms)  : 98, 103
    master - mean (100ms)  : 97, 103

    section CallTarget+Inlining+NGEN
    This PR (8366) - mean (787ms)  : 769, 806
    master - mean (784ms)  : 765, 802

Loading
FakeDbCommand (.NET 8)
gantt
    title Execution time (ms) FakeDbCommand (.NET 8)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8366) - mean (99ms)  : 95, 102
    master - mean (99ms)  : 96, 101

    section Bailout
    This PR (8366) - mean (100ms)  : 97, 103
    master - mean (99ms)  : 97, 101

    section CallTarget+Inlining+NGEN
    This PR (8366) - mean (696ms)  : 671, 720
    master - mean (692ms)  : 668, 715

Loading
HttpMessageHandler (.NET Framework 4.8)
gantt
    title Execution time (ms) HttpMessageHandler (.NET Framework 4.8)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8366) - mean (223ms)  : 211, 234
    master - mean (216ms)  : 208, 223

    section Bailout
    This PR (8366) - mean (226ms)  : 214, 239
    master - mean (221ms)  : 213, 229

    section CallTarget+Inlining+NGEN
    This PR (8366) - mean (1,273ms)  : 1219, 1328
    master - mean (1,251ms)  : 1208, 1294

Loading
HttpMessageHandler (.NET Core 3.1)
gantt
    title Execution time (ms) HttpMessageHandler (.NET Core 3.1)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8366) - mean (321ms)  : 305, 337
    master - mean (312ms)  : 298, 326

    section Bailout
    This PR (8366) - mean (320ms)  : 300, 340
    master - mean (313ms)  : 301, 324

    section CallTarget+Inlining+NGEN
    This PR (8366) - mean (1,037ms)  : 1010, 1063
    master - mean (1,033ms)  : 1004, 1062

Loading
HttpMessageHandler (.NET 6)
gantt
    title Execution time (ms) HttpMessageHandler (.NET 6)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8366) - mean (315ms)  : 295, 334
    master - mean (309ms)  : 291, 326

    section Bailout
    This PR (8366) - mean (315ms)  : 293, 338
    master - mean (309ms)  : 295, 323

    section CallTarget+Inlining+NGEN
    This PR (8366) - mean (1,087ms)  : 984, 1189
    master - mean (1,068ms)  : 1020, 1116

Loading
HttpMessageHandler (.NET 8)
gantt
    title Execution time (ms) HttpMessageHandler (.NET 8)
    dateFormat  x
    axisFormat %Q
    todayMarker off
    section Baseline
    This PR (8366) - mean (306ms)  : 291, 321
    master - mean (312ms)  : 293, 330

    section Bailout
    This PR (8366) - mean (312ms)  : 290, 334
    master - mean (312ms)  : 292, 332

    section CallTarget+Inlining+NGEN
    This PR (8366) - mean (1,039ms)  : 976, 1103
    master - mean (1,034ms)  : 955, 1114

Loading

Not supported in .NET Framework
@bouwkast

Copy link
Copy Markdown
Collaborator Author

Re-requesting reviews as the change has changed

@andrewlock

Copy link
Copy Markdown
Member

Why aren't we fixing IDuckTypeTask instead?

@tonyredondo Thanks, I modified IDuckTypeTask to TaskStatus Status { get; } I think that is the fix

Well this is now failing the xunit instrumentation tests 😭

but... :why:? That seems like there's a bug in the xunit instrumentation tests then though 😟 In general, t would definitely be preferable to be able to check if the task has completed successfully synchronously, as that avoids the whole dangerousness of sync-over-async entirely, though I think in practical terms it's very unlikely to be the path that's taken here... but we probably want it to be an option in the future :/

@andrewlock

Copy link
Copy Markdown
Member

Why aren't we fixing IDuckTypeTask instead?

@tonyredondo Thanks, I modified IDuckTypeTask to TaskStatus Status { get; } I think that is the fix

Well this is now failing the xunit instrumentation tests 😭

but... :why:? That seems like there's a bug in the xunit instrumentation tests then though 😟 In general, t would definitely be preferable to be able to check if the task has completed successfully synchronously, as that avoids the whole dangerousness of sync-over-async entirely, though I think in practical terms it's very unlikely to be the path that's taken here... but we probably want it to be an option in the future :/

OK, I know why - it's because that type is used to DuckType both Task and ValueTask 🙄 Which is presumably why it only fails some of the xunit integration tests... Logs

2026-03-25 16:52:14.544 +00:00 [ERR] Exception occurred when calling the CallTarget integration continuation. Datadog.Trace.DuckTyping.DuckTypePropertyOrFieldNotFoundException: The property or field 'Status' for the proxy property 'Status' was not found in the instance of type 'System.Threading.Tasks.ValueTask`1[[Xunit.v3.RunSummary, xunit.v3.core, Version=2.0.3.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c]]'.
   at Datadog.Trace.DuckTyping.DuckTypePropertyOrFieldNotFoundException.Throw(String name, String duckAttributeName, Type type) in /project/tracer/src/Datadog.Trace/DuckTyping/DuckTypeExceptions.cs:line 189
   at Datadog.Trace.DuckTyping.DuckType.CreateProperties(TypeBuilder proxyTypeBuilder, Type proxyDefinitionType, Type targetType, FieldInfo instanceField) in /project/tracer/src/Datadog.Trace/DuckTyping/DuckType.cs:line 689
   at Datadog.Trace.DuckTyping.DuckType.CreateProxyType(Type proxyDefinitionType, Type targetType, Boolean dryRun) in /project/tracer/src/Datadog.Trace/DuckTyping/DuckType.cs:line 215
--- End of stack trace from previous location ---
   at xunit_v3_core__8D05B1BB7A6FDB6C.Xunit_v3_XunitTestMethodRunner.Datadog_Trace_ClrProfiler_AutoInstrumentation_Testing_XUnit_V3_IXunitTestMethodRunnerV3_24.RunTestCase(Object testCase, Object)
   at Datadog.Trace.ClrProfiler.AutoInstrumentation.Testing.XUnit.V3.XUnitTestMethodRunnerBaseRunTestCaseV3Integration.OnAsyncMethodEnd[TTarget,TReturn](TTarget instance, TReturn returnValue, Exception exception, CallTargetState state) in /project/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/Testing/XUnit/V3/XUnitTestMethodRunnerBaseRunTestCaseV3Integration.cs:line 175
   at Datadog.Trace.ClrProfiler.CallTarget.Handlers.Continuations.ValueTaskContinuationGenerator`4.AsyncCallbackHandler.ContinuationAction(ValueTask`1 previousValueTask, TTarget target, CallTargetState state, Exception exception) in /project/tracer/src/Datadog.Trace/ClrProfiler/CallTarget/Handlers/Continuations/ValueTaskContinuationGenerator`1.cs:line 170
 { MachineName: ".", Process: "[25866 dotnet]", AppDomain: "[1 Samples.XUnitTestsRetriesV3]", AssemblyLoadContext: "\"\" Datadog.Trace.ClrProfiler.Managed.Loader.ManagedProfilerAssemblyLoadContext #1", TracerVersion: "3.41.0.0" }

So it looks like:

  • IsCompletedSuccessfully - available on ValueTask and Task but not on .NET Framework
  • Status - available on Task but not ValueTask
  • ValueTask isn't on .NET Framework

So the main problem here is essentially that we're mixing our duck types, trying to use the same duck type for multiple targets etc. My feelings are:

  1. What you have here right now fixes the immediate bug, so I think we should merge it
  2. We should probably split the ValueTask and Task duck type implementations
  • Update this one to always use Task, and re-introduce Status for sync completion
  • Update xunit to use a different ValueTask duck type

@bouwkast

Copy link
Copy Markdown
Collaborator Author

Why aren't we fixing IDuckTypeTask instead?

@tonyredondo Thanks, I modified IDuckTypeTask to TaskStatus Status { get; } I think that is the fix

Well this is now failing the xunit instrumentation tests 😭

but... :why:? That seems like there's a bug in the xunit instrumentation tests then though 😟 In general, t would definitely be preferable to be able to check if the task has completed successfully synchronously, as that avoids the whole dangerousness of sync-over-async entirely, though I think in practical terms it's very unlikely to be the path that's taken here... but we probably want it to be an option in the future :/

OK, I know why - it's because that type is used to DuckType both Task and ValueTask 🙄 Which is presumably why it only fails some of the xunit integration tests... Logs

2026-03-25 16:52:14.544 +00:00 [ERR] Exception occurred when calling the CallTarget integration continuation. Datadog.Trace.DuckTyping.DuckTypePropertyOrFieldNotFoundException: The property or field 'Status' for the proxy property 'Status' was not found in the instance of type 'System.Threading.Tasks.ValueTask`1[[Xunit.v3.RunSummary, xunit.v3.core, Version=2.0.3.0, Culture=neutral, PublicKeyToken=8d05b1bb7a6fdb6c]]'.
   at Datadog.Trace.DuckTyping.DuckTypePropertyOrFieldNotFoundException.Throw(String name, String duckAttributeName, Type type) in /project/tracer/src/Datadog.Trace/DuckTyping/DuckTypeExceptions.cs:line 189
   at Datadog.Trace.DuckTyping.DuckType.CreateProperties(TypeBuilder proxyTypeBuilder, Type proxyDefinitionType, Type targetType, FieldInfo instanceField) in /project/tracer/src/Datadog.Trace/DuckTyping/DuckType.cs:line 689
   at Datadog.Trace.DuckTyping.DuckType.CreateProxyType(Type proxyDefinitionType, Type targetType, Boolean dryRun) in /project/tracer/src/Datadog.Trace/DuckTyping/DuckType.cs:line 215
--- End of stack trace from previous location ---
   at xunit_v3_core__8D05B1BB7A6FDB6C.Xunit_v3_XunitTestMethodRunner.Datadog_Trace_ClrProfiler_AutoInstrumentation_Testing_XUnit_V3_IXunitTestMethodRunnerV3_24.RunTestCase(Object testCase, Object)
   at Datadog.Trace.ClrProfiler.AutoInstrumentation.Testing.XUnit.V3.XUnitTestMethodRunnerBaseRunTestCaseV3Integration.OnAsyncMethodEnd[TTarget,TReturn](TTarget instance, TReturn returnValue, Exception exception, CallTargetState state) in /project/tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/Testing/XUnit/V3/XUnitTestMethodRunnerBaseRunTestCaseV3Integration.cs:line 175
   at Datadog.Trace.ClrProfiler.CallTarget.Handlers.Continuations.ValueTaskContinuationGenerator`4.AsyncCallbackHandler.ContinuationAction(ValueTask`1 previousValueTask, TTarget target, CallTargetState state, Exception exception) in /project/tracer/src/Datadog.Trace/ClrProfiler/CallTarget/Handlers/Continuations/ValueTaskContinuationGenerator`1.cs:line 170
 { MachineName: ".", Process: "[25866 dotnet]", AppDomain: "[1 Samples.XUnitTestsRetriesV3]", AssemblyLoadContext: "\"\" Datadog.Trace.ClrProfiler.Managed.Loader.ManagedProfilerAssemblyLoadContext #1", TracerVersion: "3.41.0.0" }

So it looks like:

  • IsCompletedSuccessfully - available on ValueTask and Task but not on .NET Framework
  • Status - available on Task but not ValueTask
  • ValueTask isn't on .NET Framework

So the main problem here is essentially that we're mixing our duck types, trying to use the same duck type for multiple targets etc. My feelings are:

  1. What you have here right now fixes the immediate bug, so I think we should merge it
  2. We should probably split the ValueTask and Task duck type implementations
  • Update this one to always use Task, and re-introduce Status for sync completion
  • Update xunit to use a different ValueTask duck type

Yep, that is the issue that we can't do both Task/ValueTask in the same DuckType so I'll merge this and then follow up with that

@bouwkast
bouwkast merged commit f51d779 into master Mar 30, 2026
139 checks passed
@bouwkast
bouwkast deleted the steven/kafka-fix branch March 30, 2026 18:58
@github-actions github-actions Bot added this to the vNext-v3 milestone Mar 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI Generated Largely based on code generated by an AI or LLM. This label is the same across all dd-trace-* repos area:integrations

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants