Skip to content

Commit 37f6383

Browse files
authored
Fix DuckTypeFieldIsReadonlyException in RabbitMQ v7+ auto-instrumentation (#8006)
## Summary of changes Fixes a `DuckTypeFieldIsReadonlyException` happening in the RabbitMQ v7+ instrumentation when `BasicPublish` is called with `CachedString`s. ## Reason for change https://github.com/rabbitmq/rabbitmq-dotnet-client/blob/94147996821f6a77bed0e77fd1886e75b08a31f9/projects/RabbitMQ.Client/CachedString.cs#L16 is defined as `readonly` Our DuckType had was a `get/set` property. When calling `BasicPublish` with `CachedString` we'd get the following error: ``` 2025-12-23 09:52:24.105 -05:00 [ERR] Exception occurred when calling the CallTarget integration continuation. System.TypeInitializationException: The type initializer for 'Datadog.Trace.ClrProfiler.CallTarget.Handlers.BeginMethodHandler`8' threw an exception.  ---> Datadog.Trace.ClrProfiler.CallTarget.CallTargetInvokerException: The field 'Value' is marked as readonly, you should remove the setter from the base type class or interface.  ---> Datadog.Trace.DuckTyping.DuckTypeFieldIsReadonlyException: The field 'Value' is marked as readonly, you should remove the setter from the base type class or interface.    at Datadog.Trace.DuckTyping.DuckTypeFieldIsReadonlyException.Throw(FieldInfo field) in C:\Users\steven.bouwkamp\source\repos\dd-trace-dotnet2\tracer\src\Datadog.Trace\DuckTyping\DuckTypeExceptions.cs:line 171    at Datadog.Trace.DuckTyping.DuckType.CreateProperties(TypeBuilder proxyTypeBuilder, Type proxyDefinitionType, Type targetType, FieldInfo instanceField) in C:\Users\steven.bouwkamp\source\repos\dd-trace-dotnet2\tracer\src\Datadog.Trace\DuckTyping\DuckType.cs:line 774    at Datadog.Trace.DuckTyping.DuckType.CreateProxyType(Type proxyDefinitionType, Type targetType, Boolean dryRun) in C:\Users\steven.bouwkamp\source\repos\dd-trace-dotnet2\tracer\src\Datadog.Trace\DuckTyping\DuckType.cs:line 215 --- End of stack trace from previous location ---    at Datadog.Trace.ClrProfiler.CallTarget.Handlers.IntegrationMapper.CreateBeginMethodDelegate(Type integrationType, Type targetType, Type[] argumentsTypes) in C:\Users\steven.bouwkamp\source\repos\dd-trace-dotnet2\tracer\src\Datadog.Trace\ClrProfiler\CallTarget\Handlers\IntegrationMapper.cs:line 142    at Datadog.Trace.ClrProfiler.CallTarget.Handlers.BeginMethodHandler`8..cctor() in C:\Users\steven.bouwkamp\source\repos\dd-trace-dotnet2\tracer\src\Datadog.Trace\ClrProfiler\CallTarget\Handlers\BeginMethodHandler`6.cs:line 28    --- End of inner exception stack trace ---    at Datadog.Trace.ClrProfiler.CallTarget.Handlers.BeginMethodHandler`8..cctor() in C:\Users\steven.bouwkamp\source\repos\dd-trace-dotnet2\tracer\src\Datadog.Trace\ClrProfiler\CallTarget\Handlers\BeginMethodHandler`6.cs:line 35    --- End of inner exception stack trace ---    at RabbitMQ.Client.Impl.Channel.BasicPublishAsync[TProperties](CachedString exchange, CachedString routingKey, Boolean mandatory, TProperties basicProperties, ReadOnlyMemory`1 body, CancellationToken cancellationToken)  { MachineName: ".", Process: "[68600 Samples.RabbitMQ]", AppDomain: "[1 Samples.RabbitMQ]", AssemblyLoadContext: "\"\" Datadog.Trace.ClrProfiler.Managed.Loader.ManagedProfilerAssemblyLoadContext #2", TracerVersion: "3.35.0.0" } ``` ## Implementation details Removed the `set`. ## Test coverage So, I updated the sample application to make a call with `CachedString`s on V7+ _instead_ of the normal `string` calls. So no new spans, we would expect the spans to be the same and they are. Without this fix (removing the `set`) the tests fail as the snapshots are different for V7. I didn't think it was super valuable to update the sample application to call both versions of `BasicPublish` for V7 (one with `CachedString` and one without) as `BasicPublishAsyncCachedStringsIntegration` just calls `return BasicPublishAsyncIntegration.OnMethodBegin` so 🤷 ## Other details <!-- Fixes #{issue} --> Fixes [this error identified by Error Tracking](https://app.datadoghq.com/error-tracking/issue/b5bb096c-6bd4-11f0-adde-da7ad0900002) <!-- ⚠️ 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. MergeQueue is NOT enabled in this repository. If you have write access to the repo, the PR has 1-2 approvals (see above), and all of the required checks have passed, you can use the Squash and Merge button to merge the PR. If you don't have write access, or you need help, reach out in the #apm-dotnet channel in Slack. -->
1 parent 4eb6bef commit 37f6383

File tree

2 files changed

+24
-2
lines changed

2 files changed

+24
-2
lines changed

tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/RabbitMQ/BasicPublishAsyncCachedStringsIntegration.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ internal static CallTargetState OnMethodBegin<TTarget, TExchange, TRoutingKey, T
5555
internal interface ICachedStringProxy : IDuckType
5656
{
5757
/// <summary>
58-
/// Gets or sets a value of System.String
58+
/// Gets the value of System.String
5959
/// </summary>
6060
[DuckField(Name = "Value")]
61-
string Value { get; set; }
61+
string Value { get; }
6262
}

tracer/test/test-applications/integrations/Samples.RabbitMQ/Program.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,19 @@ private static async Task PublishAndGet(IRabbitChannel channel, string consumerT
135135
string message = $"{messagePrefix} - Message";
136136
var body = Encoding.UTF8.GetBytes(message);
137137

138+
#if RABBITMQ_7_0
139+
// Use CachedString parameters to trigger BasicPublishAsyncCachedStringsIntegration
140+
var cachedExchange = new CachedString(publishExchangeName);
141+
var cachedRoutingKey = new CachedString(publishRoutingKey);
142+
await channel.BasicPublishAsync(
143+
exchange: cachedExchange,
144+
routingKey: cachedRoutingKey,
145+
mandatory: false,
146+
basicProperties: properties,
147+
body: body);
148+
#else
138149
await Helper.BasicPublishAsync(channel, publishExchangeName, publishRoutingKey, body, properties);
150+
#endif
139151

140152
Console.WriteLine($"BasicPublish - Sent message: {message}");
141153
}
@@ -180,9 +192,19 @@ private static async Task Send(IConnection connection, string consumerType)
180192
string message = $"Send - Message #{i}";
181193
var body = Encoding.UTF8.GetBytes(message);
182194

195+
#if RABBITMQ_7_0
196+
// Use CachedString parameters to trigger BasicPublishAsyncCachedStringsIntegration
197+
var cachedExchange = new CachedString("");
198+
var cachedRoutingKey = new CachedString("hello");
199+
await channel.BasicPublishAsync(
200+
exchange: cachedExchange,
201+
routingKey: cachedRoutingKey,
202+
body: body);
203+
#else
183204
await Helper.BasicPublishAsync (channel, exchange: "",
184205
routingKey: "hello",
185206
body: body);
207+
#endif
186208
Console.WriteLine("[Send] - [x] Sent \"{0}\"", message);
187209

188210

0 commit comments

Comments
 (0)