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));
+ }
+ }
+}