From 88258987e0f1cfb7158238655977037bafc10572 Mon Sep 17 00:00:00 2001 From: Andrew Lock Date: Mon, 23 Dec 2024 10:19:20 +0000 Subject: [PATCH] Add support for ValueTask instrumentations for < NET Core 3.1 --- .../Continuations/IValueTaskDuckType.cs | 18 ++ .../Continuations/ValueTaskActivator.cs | 65 +++++ .../Continuations/ValueTaskActivator`1.cs | 94 ++++++++ .../ValueTaskContinuationGenerator.NetFx.cs | 205 ++++++++++++++++ .../ValueTaskContinuationGenerator`1.NetFx.cs | 224 ++++++++++++++++++ .../CallTarget/Handlers/EndMethodHandler`1.cs | 9 +- .../CallTarget/Handlers/ValueTaskHelper.cs | 44 ++++ .../CallTarget/ValueTaskActivatorTests.cs | 74 ++++++ ...alueTaskAsyncContinuationGeneratorTests.cs | 2 - .../ValueTaskContinuationGeneratorTests.cs | 2 - .../CallTarget/ValueTaskHelperTests.cs | 62 +++++ 11 files changed, 788 insertions(+), 11 deletions(-) create mode 100644 tracer/src/Datadog.Trace/ClrProfiler/CallTarget/Handlers/Continuations/IValueTaskDuckType.cs create mode 100644 tracer/src/Datadog.Trace/ClrProfiler/CallTarget/Handlers/Continuations/ValueTaskActivator.cs create mode 100644 tracer/src/Datadog.Trace/ClrProfiler/CallTarget/Handlers/Continuations/ValueTaskActivator`1.cs create mode 100644 tracer/src/Datadog.Trace/ClrProfiler/CallTarget/Handlers/Continuations/ValueTaskContinuationGenerator.NetFx.cs create mode 100644 tracer/src/Datadog.Trace/ClrProfiler/CallTarget/Handlers/Continuations/ValueTaskContinuationGenerator`1.NetFx.cs create mode 100644 tracer/src/Datadog.Trace/ClrProfiler/CallTarget/Handlers/ValueTaskHelper.cs create mode 100644 tracer/test/Datadog.Trace.Tests/CallTarget/ValueTaskActivatorTests.cs create mode 100644 tracer/test/Datadog.Trace.Tests/CallTarget/ValueTaskHelperTests.cs diff --git a/tracer/src/Datadog.Trace/ClrProfiler/CallTarget/Handlers/Continuations/IValueTaskDuckType.cs b/tracer/src/Datadog.Trace/ClrProfiler/CallTarget/Handlers/Continuations/IValueTaskDuckType.cs new file mode 100644 index 000000000000..9b2a890dc293 --- /dev/null +++ b/tracer/src/Datadog.Trace/ClrProfiler/CallTarget/Handlers/Continuations/IValueTaskDuckType.cs @@ -0,0 +1,18 @@ +// +// 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. +// + +#if !NETCOREAPP3_1_OR_GREATER +#nullable enable +using System.Threading.Tasks; + +namespace Datadog.Trace.ClrProfiler.CallTarget.Handlers.Continuations; + +internal interface IValueTaskDuckType +{ + bool IsCompletedSuccessfully { get; } + + Task AsTask(); +} +#endif diff --git a/tracer/src/Datadog.Trace/ClrProfiler/CallTarget/Handlers/Continuations/ValueTaskActivator.cs b/tracer/src/Datadog.Trace/ClrProfiler/CallTarget/Handlers/Continuations/ValueTaskActivator.cs new file mode 100644 index 000000000000..5f2d0b3772ab --- /dev/null +++ b/tracer/src/Datadog.Trace/ClrProfiler/CallTarget/Handlers/Continuations/ValueTaskActivator.cs @@ -0,0 +1,65 @@ +// +// 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. +// + +#if !NETCOREAPP3_1_OR_GREATER +#nullable enable + +using System; +using System.Reflection.Emit; +using System.Threading.Tasks; +using Datadog.Trace.DuckTyping; +using Datadog.Trace.Logging; +using Datadog.Trace.Util; + +namespace Datadog.Trace.ClrProfiler.CallTarget.Handlers.Continuations; + +internal static class ValueTaskActivator +{ + private static readonly Func Activator; + + static ValueTaskActivator() + { + try + { + Activator = CreateActivator(); + } + catch (Exception ex) + { + DatadogLogging.GetLoggerFor() + .Error(ex, "Error creating the custom activator for: {Type}", typeof(TValueTask).FullName); + + // Unfortunately this will box the ValueTask, but I think it's still the best we can do in this scenario + Activator = FallbackActivator; + } + } + + // Internal for testing + internal static Func CreateActivator() + { + var valueTaskType = typeof(TValueTask); + var ctor = valueTaskType.GetConstructor([typeof(Task)])!; + + var createValueTaskMethod = new DynamicMethod( + $"TypeActivator" + valueTaskType.Name, + returnType: valueTaskType, + parameterTypes: [typeof(Task)], + typeof(DuckType).Module, + true); + + var il = createValueTaskMethod.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Newobj, ctor); + il.Emit(OpCodes.Ret); + + return (Func)createValueTaskMethod.CreateDelegate(typeof(Func)); + } + + // Internal for testing + internal static TValueTask FallbackActivator(Task task) + => (TValueTask)System.Activator.CreateInstance(typeof(TValueTask), task)!; + + public static TValueTask CreateInstance(Task task) => Activator(task); +} +#endif diff --git a/tracer/src/Datadog.Trace/ClrProfiler/CallTarget/Handlers/Continuations/ValueTaskActivator`1.cs b/tracer/src/Datadog.Trace/ClrProfiler/CallTarget/Handlers/Continuations/ValueTaskActivator`1.cs new file mode 100644 index 000000000000..125dbbfa0dd1 --- /dev/null +++ b/tracer/src/Datadog.Trace/ClrProfiler/CallTarget/Handlers/Continuations/ValueTaskActivator`1.cs @@ -0,0 +1,94 @@ +// +// 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. +// + +#if !NETCOREAPP3_1_OR_GREATER +#nullable enable + +using System; +using System.Reflection.Emit; +using System.Threading.Tasks; +using Datadog.Trace.DuckTyping; +using Datadog.Trace.Logging; +using Datadog.Trace.Util; + +#pragma warning disable SA1649 // File name must match first type name + +namespace Datadog.Trace.ClrProfiler.CallTarget.Handlers.Continuations; + +internal static class ValueTaskActivator +{ + private static readonly Func, TValueTask> TaskActivator; + private static readonly Func ResultActivator; + + static ValueTaskActivator() + { + try + { + TaskActivator = CreateTaskActivator(); + ResultActivator = CreateResultActivator(); + } + catch (Exception ex) + { + DatadogLogging.GetLoggerFor() + .Error(ex, "Error creating the custom activator for: {Type}", typeof(TValueTask).FullName); + + // Unfortunately this will box the ValueTask, but I think it's still the best we can do in this scenario + TaskActivator = FallbackTaskActivator; + ResultActivator = FallbackResultActivator; + } + } + + // Internal for testing + internal static Func, TValueTask> CreateTaskActivator() + { + var valueTaskType = typeof(TValueTask); + var ctor = valueTaskType.GetConstructor([typeof(Task)])!; + + var createValueTaskMethod = new DynamicMethod( + $"TypeActivatorTask" + valueTaskType.Name, + returnType: valueTaskType, + parameterTypes: [typeof(Task)], + typeof(DuckType).Module, + true); + + var il = createValueTaskMethod.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Newobj, ctor); + il.Emit(OpCodes.Ret); + + return (Func, TValueTask>)createValueTaskMethod.CreateDelegate(typeof(Func, TValueTask>)); + } + + // Internal for testing + internal static Func CreateResultActivator() + { + var valueTaskType = typeof(TValueTask); + var ctor = valueTaskType.GetConstructor([typeof(TResult)])!; + + var createValueTaskMethod = new DynamicMethod( + $"TypeActivatorResult" + valueTaskType.Name, + returnType: valueTaskType, + parameterTypes: [typeof(TResult)], + typeof(DuckType).Module, + true); + + var il = createValueTaskMethod.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Newobj, ctor); + il.Emit(OpCodes.Ret); + + return (Func)createValueTaskMethod.CreateDelegate(typeof(Func)); + } + + // Internal for testing + internal static TValueTask FallbackTaskActivator(Task task) + => (TValueTask)Activator.CreateInstance(typeof(TValueTask), task)!; + + internal static TValueTask FallbackResultActivator(TResult task) + => (TValueTask)Activator.CreateInstance(typeof(TValueTask), task)!; + + public static TValueTask CreateInstance(Task task) => TaskActivator(task); +} +#endif diff --git a/tracer/src/Datadog.Trace/ClrProfiler/CallTarget/Handlers/Continuations/ValueTaskContinuationGenerator.NetFx.cs b/tracer/src/Datadog.Trace/ClrProfiler/CallTarget/Handlers/Continuations/ValueTaskContinuationGenerator.NetFx.cs new file mode 100644 index 000000000000..e844abee60ab --- /dev/null +++ b/tracer/src/Datadog.Trace/ClrProfiler/CallTarget/Handlers/Continuations/ValueTaskContinuationGenerator.NetFx.cs @@ -0,0 +1,205 @@ +// +// 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. +// +#nullable enable + +#if !NETCOREAPP3_1_OR_GREATER +using System; +using System.Threading.Tasks; +using Datadog.Trace.DuckTyping; +using Datadog.Trace.Vendors.Serilog.Events; + +namespace Datadog.Trace.ClrProfiler.CallTarget.Handlers.Continuations; + +internal class ValueTaskContinuationGenerator : ContinuationGenerator +{ + private static readonly CallbackHandler Resolver; + + static ValueTaskContinuationGenerator() + { + var result = IntegrationMapper.CreateAsyncEndMethodDelegate(typeof(TIntegration), typeof(TTarget), typeof(object)); + if (result.Method is not null) + { + if (result.Method.ReturnType == typeof(Task) || + (result.Method.ReturnType.IsGenericType && typeof(Task).IsAssignableFrom(result.Method.ReturnType))) + { + var asyncContinuation = (AsyncObjectContinuationMethodDelegate)result.Method.CreateDelegate(typeof(AsyncObjectContinuationMethodDelegate)); + Resolver = new AsyncCallbackHandler(asyncContinuation, result.PreserveContext); + } + else + { + var continuation = (ObjectContinuationMethodDelegate)result.Method.CreateDelegate(typeof(ObjectContinuationMethodDelegate)); + Resolver = new SyncCallbackHandler(continuation, result.PreserveContext); + } + } + else + { + Resolver = new NoOpCallbackHandler(); + } + + if (Log.IsEnabled(LogEventLevel.Debug)) + { + Log.Debug( + "== {TaskContinuationGenerator} using Resolver: {Resolver}", + $"TaskContinuationGenerator<{typeof(TIntegration).FullName}, {typeof(TTarget).FullName}, {typeof(TReturn).FullName}>", + Resolver.GetType().FullName); + } + } + + public override TReturn? SetContinuation(TTarget? instance, TReturn? returnValue, Exception? exception, in CallTargetState state) + { + return Resolver.ExecuteCallback(instance, returnValue, exception, in state); + } + + private class SyncCallbackHandler : CallbackHandler + { + private readonly ObjectContinuationMethodDelegate _continuation; + private readonly bool _preserveContext; + + public SyncCallbackHandler(ObjectContinuationMethodDelegate continuation, bool preserveContext) + { + _continuation = continuation; + _preserveContext = preserveContext; + } + + public override TReturn? ExecuteCallback(TTarget? instance, TReturn? returnValue, Exception? exception, in CallTargetState state) + { + if (exception != null || returnValue == null) + { + _continuation(instance, default, exception, in state); + return returnValue; + } + + var previousValueTask = returnValue.DuckCast(); + if (previousValueTask.IsCompletedSuccessfully) + { + // ok all good, just run synchronously + _continuation(instance, returnValue, exception, in state); + return returnValue; + } + + // uh oh, need to extract the task, await it, run the continuation + var task = previousValueTask.AsTask(); + var secondTask = ContinuationAction(task, instance, state); + // need to wrap the secondTask in a ValueTask to return it + return ValueTaskActivator.CreateInstance(secondTask); + } + + private async Task ContinuationAction(Task previousValueTask, TTarget? target, CallTargetState state) + { + try + { + await previousValueTask.ConfigureAwait(_preserveContext); + } + catch (Exception ex) + { + try + { + // * + // Calls the CallTarget integration continuation, exceptions here should never bubble up to the application + // * + _continuation(target, default, ex, in state); + } + catch (Exception contEx) + { + IntegrationOptions.LogException(contEx); + } + + throw; + } + + try + { + // * + // Calls the CallTarget integration continuation, exceptions here should never bubble up to the application + // * + _continuation(target, default, default, in state); + } + catch (Exception contEx) + { + IntegrationOptions.LogException(contEx); + } + } + } + + private class AsyncCallbackHandler : CallbackHandler + { + private readonly AsyncObjectContinuationMethodDelegate _asyncContinuation; + private readonly bool _preserveContext; + + public AsyncCallbackHandler(AsyncObjectContinuationMethodDelegate asyncContinuation, bool preserveContext) + { + _asyncContinuation = asyncContinuation; + _preserveContext = preserveContext; + } + + public override TReturn ExecuteCallback(TTarget? instance, TReturn? returnValue, Exception? exception, in CallTargetState state) + { + Task task; + if (returnValue == null) + { + task = Task.CompletedTask; + } + else + { + var previousValueTask = returnValue.DuckCast(); + if (previousValueTask.IsCompletedSuccessfully) + { + // ok all good, just run synchronously + task = Task.CompletedTask; + } + else + { + task = previousValueTask.AsTask(); + } + } + + var secondTask = ContinuationAction(task, instance, state, exception); + // need to wrap the secondTask in a ValueTask to return it + return ValueTaskActivator.CreateInstance(secondTask); + } + + private async Task ContinuationAction(Task previousValueTask, TTarget? target, CallTargetState state, Exception? exception) + { + if (exception != null) + { + await _asyncContinuation(target, default, exception, in state).ConfigureAwait(_preserveContext); + } + + try + { + await previousValueTask.ConfigureAwait(_preserveContext); + } + catch (Exception ex) + { + try + { + // * + // Calls the CallTarget integration continuation, exceptions here should never bubble up to the application + // * + await _asyncContinuation(target, default, ex, in state).ConfigureAwait(_preserveContext); + } + catch (Exception contEx) + { + IntegrationOptions.LogException(contEx); + } + + throw; + } + + try + { + // * + // Calls the CallTarget integration continuation, exceptions here should never bubble up to the application + // * + await _asyncContinuation(target, default, default, in state).ConfigureAwait(_preserveContext); + } + catch (Exception contEx) + { + IntegrationOptions.LogException(contEx); + } + } + } +} +#endif diff --git a/tracer/src/Datadog.Trace/ClrProfiler/CallTarget/Handlers/Continuations/ValueTaskContinuationGenerator`1.NetFx.cs b/tracer/src/Datadog.Trace/ClrProfiler/CallTarget/Handlers/Continuations/ValueTaskContinuationGenerator`1.NetFx.cs new file mode 100644 index 000000000000..51fa5520aab2 --- /dev/null +++ b/tracer/src/Datadog.Trace/ClrProfiler/CallTarget/Handlers/Continuations/ValueTaskContinuationGenerator`1.NetFx.cs @@ -0,0 +1,224 @@ +// +// 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. +// +#nullable enable + +#if !NETCOREAPP3_1_OR_GREATER +using System; +using System.Threading.Tasks; +using Datadog.Trace.DuckTyping; +using Datadog.Trace.Vendors.Serilog.Events; + +#pragma warning disable SA1649 // File name must match first type name + +namespace Datadog.Trace.ClrProfiler.CallTarget.Handlers.Continuations; + +internal class ValueTaskContinuationGenerator : ContinuationGenerator +{ + private static readonly CallbackHandler Resolver; + + static ValueTaskContinuationGenerator() + { + var result = IntegrationMapper.CreateAsyncEndMethodDelegate(typeof(TIntegration), typeof(TTarget), typeof(TResult)); + if (result.Method is not null) + { + if (result.Method.ReturnType == typeof(Task) || + (result.Method.ReturnType.IsGenericType && typeof(Task).IsAssignableFrom(result.Method.ReturnType))) + { + var asyncContinuation = (AsyncContinuationMethodDelegate)result.Method.CreateDelegate(typeof(AsyncContinuationMethodDelegate)); + Resolver = new AsyncCallbackHandler(asyncContinuation, result.PreserveContext); + } + else + { + var continuation = (ContinuationMethodDelegate)result.Method.CreateDelegate(typeof(ContinuationMethodDelegate)); + Resolver = new SyncCallbackHandler(continuation, result.PreserveContext); + } + } + else + { + Resolver = new NoOpCallbackHandler(); + } + + if (Log.IsEnabled(LogEventLevel.Debug)) + { + Log.Debug( + "== {TaskContinuationGenerator} using Resolver: {Resolver}", + $"TaskContinuationGenerator<{typeof(TIntegration).FullName}, {typeof(TTarget).FullName}, {typeof(TReturn).FullName}>", + Resolver.GetType().FullName); + } + } + + public override TReturn? SetContinuation(TTarget? instance, TReturn? returnValue, Exception? exception, in CallTargetState state) + { + return Resolver.ExecuteCallback(instance, returnValue, exception, in state); + } + + private class SyncCallbackHandler : CallbackHandler + { + private readonly ContinuationMethodDelegate _continuation; + private readonly bool _preserveContext; + + public SyncCallbackHandler(ContinuationMethodDelegate continuation, bool preserveContext) + { + _continuation = continuation; + _preserveContext = preserveContext; + } + + public override TReturn? ExecuteCallback(TTarget? instance, TReturn? returnValue, Exception? exception, in CallTargetState state) + { + if (exception != null || returnValue == null) + { + _continuation(instance, default, exception, in state); + return returnValue; + } + + var previousValueTask = returnValue.DuckCast(); + if (previousValueTask.IsCompletedSuccessfully) + { + // ok all good, just run synchronously + var unwrappedResult = previousValueTask.Result; + _continuation(instance, unwrappedResult, exception, in state); + return returnValue; + } + + // uh oh, need to extract the task, await it, run the continuation + var task = previousValueTask.AsTask(); + var secondTask = ContinuationAction(task, instance, state); + // need to wrap the Task in a ValueTask to return it + return ValueTaskActivator.CreateInstance(secondTask); + } + + private async Task ContinuationAction(Task previousValueTask, TTarget? target, CallTargetState state) + { + TResult? result = default; + try + { + result = await previousValueTask.ConfigureAwait(_preserveContext); + } + catch (Exception ex) + { + try + { + // * + // Calls the CallTarget integration continuation, exceptions here should never bubble up to the application + // * + _continuation(target, result, ex, in state); + } + catch (Exception contEx) + { + IntegrationOptions.LogException(contEx); + } + + throw; + } + + try + { + // * + // Calls the CallTarget integration continuation, exceptions here should never bubble up to the application + // * + return _continuation(target, result, null, in state); + } + catch (Exception contEx) + { + IntegrationOptions.LogException(contEx); + } + + return result; + } + } + + private class AsyncCallbackHandler : CallbackHandler + { + private readonly AsyncContinuationMethodDelegate _asyncContinuation; + private readonly bool _preserveContext; + + public AsyncCallbackHandler(AsyncContinuationMethodDelegate asyncContinuation, bool preserveContext) + { + _asyncContinuation = asyncContinuation; + _preserveContext = preserveContext; + } + + public override TReturn ExecuteCallback(TTarget? instance, TReturn? returnValue, Exception? exception, in CallTargetState state) + { + Task task; + if (returnValue == null) + { + task = Task.FromResult(default); + } + else + { + var previousValueTask = returnValue.DuckCast(); + if (previousValueTask.IsCompletedSuccessfully) + { + // ok all good, just run synchronously + task = Task.FromResult(previousValueTask.Result); + } + else + { + task = previousValueTask.AsTask(); + } + } + + var secondTask = ContinuationAction(task, instance, state, exception); + // need to wrap the Task in a ValueTask to return it + return ValueTaskActivator.CreateInstance(secondTask); + } + + private async Task ContinuationAction(Task previousValueTask, TTarget? target, CallTargetState state, Exception? exception) + { + if (exception != null) + { + return await _asyncContinuation(target, default, exception, in state).ConfigureAwait(_preserveContext); + } + + TResult? result = default; + try + { + result = await previousValueTask.ConfigureAwait(_preserveContext); + } + catch (Exception ex) + { + try + { + // * + // Calls the CallTarget integration continuation, exceptions here should never bubble up to the application + // * + await _asyncContinuation(target, result, ex, in state).ConfigureAwait(_preserveContext); + } + catch (Exception contEx) + { + IntegrationOptions.LogException(contEx); + } + + throw; + } + + try + { + // * + // Calls the CallTarget integration continuation, exceptions here should never bubble up to the application + // * + return await _asyncContinuation(target, result, null, in state).ConfigureAwait(_preserveContext); + } + catch (Exception contEx) + { + IntegrationOptions.LogException(contEx); + } + + return result; + } + } + +#pragma warning disable SA1201 // An interface should not follow a class + private interface IValueTaskOfTResultDuckType + { + bool IsCompletedSuccessfully { get; } + + TResult? Result { get; } + + Task AsTask(); + } +} +#endif diff --git a/tracer/src/Datadog.Trace/ClrProfiler/CallTarget/Handlers/EndMethodHandler`1.cs b/tracer/src/Datadog.Trace/ClrProfiler/CallTarget/Handlers/EndMethodHandler`1.cs index 891d3debd5d2..aec7e454b14c 100644 --- a/tracer/src/Datadog.Trace/ClrProfiler/CallTarget/Handlers/EndMethodHandler`1.cs +++ b/tracer/src/Datadog.Trace/ClrProfiler/CallTarget/Handlers/EndMethodHandler`1.cs @@ -35,19 +35,16 @@ static EndMethodHandler() if (returnType.IsGenericType) { - var genericReturnType = returnType.GetGenericTypeDefinition(); if (typeof(Task).IsAssignableFrom(returnType)) { // The type is a Task<> _continuationGenerator = (ContinuationGenerator?)Activator.CreateInstance(typeof(TaskContinuationGenerator<,,,>).MakeGenericType(typeof(TIntegration), typeof(TTarget), returnType, ContinuationsHelper.GetResultType(returnType))); } -#if NETCOREAPP3_1_OR_GREATER - else if (genericReturnType == typeof(ValueTask<>)) + else if (ValueTaskHelper.IsGenericValueTask(returnType)) { // The type is a ValueTask<> _continuationGenerator = (ContinuationGenerator?)Activator.CreateInstance(typeof(ValueTaskContinuationGenerator<,,,>).MakeGenericType(typeof(TIntegration), typeof(TTarget), returnType, ContinuationsHelper.GetResultType(returnType))); } -#endif } else { @@ -56,13 +53,11 @@ static EndMethodHandler() // The type is a Task _continuationGenerator = new TaskContinuationGenerator(); } -#if NETCOREAPP3_1_OR_GREATER - else if (returnType == typeof(ValueTask)) + else if (ValueTaskHelper.IsValueTask(returnType)) { // The type is a ValueTask _continuationGenerator = new ValueTaskContinuationGenerator(); } -#endif } } diff --git a/tracer/src/Datadog.Trace/ClrProfiler/CallTarget/Handlers/ValueTaskHelper.cs b/tracer/src/Datadog.Trace/ClrProfiler/CallTarget/Handlers/ValueTaskHelper.cs new file mode 100644 index 000000000000..bc958ab2e592 --- /dev/null +++ b/tracer/src/Datadog.Trace/ClrProfiler/CallTarget/Handlers/ValueTaskHelper.cs @@ -0,0 +1,44 @@ +// +// 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. +// + +#nullable enable + +using System; + +namespace Datadog.Trace.ClrProfiler.CallTarget.Handlers; + +internal static class ValueTaskHelper +{ +#if NETCOREAPP3_1_OR_GREATER + public static bool IsValueTask(Type? returnValue) + => returnValue == typeof(System.Threading.Tasks.ValueTask); + + public static bool IsGenericValueTask(Type? returnValue) + => returnValue?.GetGenericTypeDefinition() == typeof(System.Threading.Tasks.ValueTask<>); +#else + // ValueTask isn't part of the BCL for .NET FX or .NET Standard, and is instead + // provided by the System.Threading.Tasks.Extensions package. In .NET Core 2.1, ValueTask is available + // the core lib, even though we can't directly reference it, so we don't need to worry about assembly + // qualified references. In .NET FX (and .NET Standard generally) the type comes from a package. + // In that case we can't rely on Type.GetType() because, we need to provide the fully qualified assembly names, + // butt we also need support multiple versions of the package. So we fallback on the somewhat simple name check. + private static readonly Type? ValueTaskType = Type.GetType("System.Threading.Tasks.ValueTask"); + private static readonly Type? GenericValueTaskType = Type.GetType("System.Threading.Tasks.ValueTask`1"); + + public static bool IsValueTask(Type? returnValue) + => ValueTaskType is not null + ? returnValue == ValueTaskType + : returnValue?.FullName == "System.Threading.Tasks.ValueTask"; + + public static bool IsGenericValueTask(Type? returnValue) + { + var genericDefinition = returnValue?.GetGenericTypeDefinition(); + return GenericValueTaskType is not null + ? genericDefinition == GenericValueTaskType + : genericDefinition?.FullName == "System.Threading.Tasks.ValueTask`1"; + } +#endif + +} diff --git a/tracer/test/Datadog.Trace.Tests/CallTarget/ValueTaskActivatorTests.cs b/tracer/test/Datadog.Trace.Tests/CallTarget/ValueTaskActivatorTests.cs new file mode 100644 index 000000000000..264d6bfc25f7 --- /dev/null +++ b/tracer/test/Datadog.Trace.Tests/CallTarget/ValueTaskActivatorTests.cs @@ -0,0 +1,74 @@ +// +// 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. +// + +#if !NETCOREAPP3_1_OR_GREATER +using System.Threading.Tasks; +using Datadog.Trace.ClrProfiler.CallTarget.Handlers.Continuations; +using FluentAssertions; +using Xunit; + +namespace Datadog.Trace.Tests.CallTarget; + +public class ValueTaskActivatorTests +{ + [Fact] + public async Task NonGeneric_CreateTaskActivator_CanBeAwaited() + { + var activator = ValueTaskActivator.CreateActivator(); + var task = Task.CompletedTask; + + var instance = activator(task); + await instance; + } + + [Fact] + public async Task NonGeneric_FallbackActivator_CanBeAwaited() + { + var task = Task.CompletedTask; + var instance = ValueTaskActivator.FallbackActivator(task); + await instance; + } + + [Fact] + public async Task Generic_CreateTaskActivator_CanBeAwaited() + { + var activator = ValueTaskActivator, int>.CreateTaskActivator(); + var task = Task.FromResult(123); + + var instance = activator(task); + var result = await instance; + result.Should().Be(123); + } + + [Fact] + public async Task Generic_CreateResultActivator_CanBeAwaited() + { + var activator = ValueTaskActivator, int>.CreateResultActivator(); + var value = 123; + + var instance = activator(value); + var result = await instance; + result.Should().Be(123); + } + + [Fact] + public async Task Generic_FallbackTaskActivator_CanBeAwaited() + { + var task = Task.FromResult(123); + var instance = ValueTaskActivator, int>.FallbackTaskActivator(task); + var result = await instance; + result.Should().Be(123); + } + + [Fact] + public async Task Generic_FallbackResultActivator_CanBeAwaited() + { + var value = 123; + var instance = ValueTaskActivator, int>.FallbackResultActivator(value); + var result = await instance; + result.Should().Be(123); + } +} +#endif diff --git a/tracer/test/Datadog.Trace.Tests/CallTarget/ValueTaskAsyncContinuationGeneratorTests.cs b/tracer/test/Datadog.Trace.Tests/CallTarget/ValueTaskAsyncContinuationGeneratorTests.cs index 98e89baa53b7..2ca5bd767944 100644 --- a/tracer/test/Datadog.Trace.Tests/CallTarget/ValueTaskAsyncContinuationGeneratorTests.cs +++ b/tracer/test/Datadog.Trace.Tests/CallTarget/ValueTaskAsyncContinuationGeneratorTests.cs @@ -3,7 +3,6 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. // -#if NETCOREAPP3_1_OR_GREATER using System; using System.Threading; using System.Threading.Tasks; @@ -348,4 +347,3 @@ public static async Task OnAsyncMethodEnd(TTarget instance, str } } } -#endif diff --git a/tracer/test/Datadog.Trace.Tests/CallTarget/ValueTaskContinuationGeneratorTests.cs b/tracer/test/Datadog.Trace.Tests/CallTarget/ValueTaskContinuationGeneratorTests.cs index 6789418ec6da..5b5568ad863e 100644 --- a/tracer/test/Datadog.Trace.Tests/CallTarget/ValueTaskContinuationGeneratorTests.cs +++ b/tracer/test/Datadog.Trace.Tests/CallTarget/ValueTaskContinuationGeneratorTests.cs @@ -3,7 +3,6 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. // -#if NETCOREAPP3_1_OR_GREATER using System; using System.Threading; using System.Threading.Tasks; @@ -343,4 +342,3 @@ public static string OnAsyncMethodEnd(TTarget instance, string returnVa } } } -#endif diff --git a/tracer/test/Datadog.Trace.Tests/CallTarget/ValueTaskHelperTests.cs b/tracer/test/Datadog.Trace.Tests/CallTarget/ValueTaskHelperTests.cs new file mode 100644 index 000000000000..9d0f3adecd43 --- /dev/null +++ b/tracer/test/Datadog.Trace.Tests/CallTarget/ValueTaskHelperTests.cs @@ -0,0 +1,62 @@ +// +// 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. +// + +using System.Threading.Tasks; +using Datadog.Trace.ClrProfiler.CallTarget.Handlers; +using FluentAssertions; +using Xunit; + +namespace Datadog.Trace.Tests.CallTarget; + +public class ValueTaskHelperTests +{ + [Fact] + public void IsValueTask_WhenValueTask_ReturnsTrue() + { + var task = new ValueTask(Task.CompletedTask); + Helper.IsValueTask(task).Should().BeTrue(); + } + + [Fact] + public void IsValueTask_WhenGenericValueTask_ReturnsFalse() + { + var task = new ValueTask(Task.FromResult(true)); + Helper.IsValueTask(task).Should().BeFalse(); + } + + [Fact] + public void IsValueTask_WhenTask_ReturnsFalse() + { + var task = Task.FromResult(true); + Helper.IsValueTask(task).Should().BeFalse(); + } + + [Fact] + public void IsGenericValueTask_WhenGenericValueTask_ReturnsTrue() + { + var task = new ValueTask(Task.FromResult(true)); + Helper.IsGenericTask(task).Should().BeTrue(); + } + + [Fact] + public void IsGenericValueTask_WhenTask_ReturnsFalse() + { + var task = Task.FromResult(true); + Helper.IsGenericTask(task).Should().BeFalse(); + } + + private static class Helper + { + public static bool IsValueTask(T task) + { + return ValueTaskHelper.IsValueTask(typeof(T)); + } + + public static bool IsGenericTask(T task) + { + return ValueTaskHelper.IsGenericValueTask(typeof(T)); + } + } +}