From b605ed4c307cf6aacd609094ba76e7e4b99c161e Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Thu, 6 Aug 2026 22:39:41 -0400 Subject: [PATCH] Add Runtime Async profiler API coverage Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/tests/profiler/native/CMakeLists.txt | 2 + src/tests/profiler/native/classfactory.cpp | 10 + .../runtimeasyncapisprofiler.cpp | 188 +++++++++++++ .../runtimeasyncapisprofiler.h | 39 +++ .../runtimeasynctypesprofiler.cpp | 252 ++++++++++++++++++ .../runtimeasynctypesprofiler.h | 53 ++++ .../runtimeasyncapis/runtimeasyncapis.cs | 53 ++++ .../runtimeasyncapis/runtimeasyncapis.csproj | 15 ++ .../runtimeasynctypes/runtimeasynctypes.cs | 64 +++++ .../runtimeasynctypes.csproj | 15 ++ 10 files changed, 691 insertions(+) create mode 100644 src/tests/profiler/native/runtimeasyncapis/runtimeasyncapisprofiler.cpp create mode 100644 src/tests/profiler/native/runtimeasyncapis/runtimeasyncapisprofiler.h create mode 100644 src/tests/profiler/native/runtimeasynctypes/runtimeasynctypesprofiler.cpp create mode 100644 src/tests/profiler/native/runtimeasynctypes/runtimeasynctypesprofiler.h create mode 100644 src/tests/profiler/runtimeasyncapis/runtimeasyncapis.cs create mode 100644 src/tests/profiler/runtimeasyncapis/runtimeasyncapis.csproj create mode 100644 src/tests/profiler/runtimeasynctypes/runtimeasynctypes.cs create mode 100644 src/tests/profiler/runtimeasynctypes/runtimeasynctypes.csproj diff --git a/src/tests/profiler/native/CMakeLists.txt b/src/tests/profiler/native/CMakeLists.txt index dbc2c0d3bc21f9..2e8c36450ec2b1 100644 --- a/src/tests/profiler/native/CMakeLists.txt +++ b/src/tests/profiler/native/CMakeLists.txt @@ -30,6 +30,8 @@ set(SOURCES rejitprofiler/ilrewriter.cpp rejitprofiler/sigparse.cpp releaseondetach/releaseondetach.cpp + runtimeasyncapis/runtimeasyncapisprofiler.cpp + runtimeasynctypes/runtimeasynctypesprofiler.cpp transitions/transitions.cpp profiler.def profiler.cpp diff --git a/src/tests/profiler/native/classfactory.cpp b/src/tests/profiler/native/classfactory.cpp index dace6277d6f030..22622a36f8cfe2 100644 --- a/src/tests/profiler/native/classfactory.cpp +++ b/src/tests/profiler/native/classfactory.cpp @@ -20,6 +20,8 @@ #include "nullprofiler/nullprofiler.h" #include "rejitprofiler/rejitprofiler.h" #include "releaseondetach/releaseondetach.h" +#include "runtimeasyncapis/runtimeasyncapisprofiler.h" +#include "runtimeasynctypes/runtimeasynctypesprofiler.h" #include "transitions/transitions.h" #include "multiple/multiple.h" #include "inlining/inlining.h" @@ -127,6 +129,14 @@ HRESULT STDMETHODCALLTYPE ClassFactory::CreateInstance(IUnknown *pUnkOuter, REFI { profiler = new ReleaseOnDetach(); } + else if (clsid == RuntimeAsyncApisProfiler::GetClsid()) + { + profiler = new RuntimeAsyncApisProfiler(); + } + else if (clsid == RuntimeAsyncTypesProfiler::GetClsid()) + { + profiler = new RuntimeAsyncTypesProfiler(); + } else if (clsid == Transitions::GetClsid()) { profiler = new Transitions(); diff --git a/src/tests/profiler/native/runtimeasyncapis/runtimeasyncapisprofiler.cpp b/src/tests/profiler/native/runtimeasyncapis/runtimeasyncapisprofiler.cpp new file mode 100644 index 00000000000000..f316832f29846e --- /dev/null +++ b/src/tests/profiler/native/runtimeasyncapis/runtimeasyncapisprofiler.cpp @@ -0,0 +1,188 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "runtimeasyncapisprofiler.h" + +RuntimeAsyncApisProfiler::RuntimeAsyncApisProfiler() + : Profiler(), + _failures(0), + _target(0), + _jitStarts(0), + _jitFinishes(0), + _ipRoundTrips(0), + _ilMappings(0), + _exceptionsThrown(0), + _targetSearches(0), + _targetUnwinds(0), + _catchers(0) +{ +} + +GUID RuntimeAsyncApisProfiler::GetClsid() +{ + // {9A20F5D8-47B1-4E63-82CD-1F7690AB34E2} + GUID clsid = {0x9a20f5d8, 0x47b1, 0x4e63, {0x82, 0xcd, 0x1f, 0x76, 0x90, 0xab, 0x34, 0xe2}}; + return clsid; +} + +HRESULT RuntimeAsyncApisProfiler::Initialize(IUnknown* pCorProfilerInfoUnk) +{ + HRESULT hr = Profiler::Initialize(pCorProfilerInfoUnk); + if (FAILED(hr)) + { + return hr; + } + + return pCorProfilerInfo->SetEventMask2( + COR_PRF_MONITOR_JIT_COMPILATION | COR_PRF_MONITOR_EXCEPTIONS, 0); +} + +bool RuntimeAsyncApisProfiler::IsTarget(FunctionID functionId) +{ + if (GetFunctionIDName(functionId) != WCHAR("Work")) + { + return false; + } + + std::wstring moduleName = GetModuleIDName(GetModuleId(functionId)).ToWString(); + return moduleName.find(L"runtimeasyncapis") != std::wstring::npos; +} + +HRESULT RuntimeAsyncApisProfiler::JITCompilationStarted(FunctionID functionId, BOOL fIsSafeToBlock) +{ + if (IsTarget(functionId)) + { + _jitStarts++; + } + return S_OK; +} + +HRESULT RuntimeAsyncApisProfiler::JITCompilationFinished( + FunctionID functionId, HRESULT hrStatus, BOOL fIsSafeToBlock) +{ + if (!IsTarget(functionId)) + { + return S_OK; + } + + _target = functionId; + if (FAILED(hrStatus)) + { + _failures++; + return S_OK; + } + + _jitFinishes++; + + COR_PRF_CODE_INFO codeInfos[16]; + ULONG32 codeInfoCount = 0; + HRESULT hr = pCorProfilerInfo->GetCodeInfo2( + functionId, static_cast(sizeof(codeInfos) / sizeof(codeInfos[0])), + &codeInfoCount, codeInfos); + if (FAILED(hr) || codeInfoCount == 0) + { + _failures++; + } + else + { + FunctionID roundTrip = 0; + hr = pCorProfilerInfo->GetFunctionFromIP( + reinterpret_cast(codeInfos[0].startAddress), &roundTrip); + if (SUCCEEDED(hr) && roundTrip == functionId) + { + _ipRoundTrips++; + } + else + { + _failures++; + } + } + + COR_DEBUG_IL_TO_NATIVE_MAP mappings[512]; + ULONG32 mappingCount = 0; + hr = pCorProfilerInfo->GetILToNativeMapping( + functionId, static_cast(sizeof(mappings) / sizeof(mappings[0])), + &mappingCount, mappings); + if (SUCCEEDED(hr) && mappingCount > 0) + { + _ilMappings++; + } + else + { + _failures++; + } + + return S_OK; +} + +HRESULT RuntimeAsyncApisProfiler::ExceptionThrown(ObjectID thrownObjectId) +{ + _exceptionsThrown++; + return S_OK; +} + +HRESULT RuntimeAsyncApisProfiler::ExceptionSearchFunctionEnter(FunctionID functionId) +{ + if (functionId == _target.load()) + { + _targetSearches++; + } + return S_OK; +} + +HRESULT RuntimeAsyncApisProfiler::ExceptionUnwindFunctionEnter(FunctionID functionId) +{ + if (functionId == _target.load()) + { + _targetUnwinds++; + } + return S_OK; +} + +HRESULT RuntimeAsyncApisProfiler::ExceptionCatcherEnter(FunctionID functionId, ObjectID objectId) +{ + _catchers++; + return S_OK; +} + +HRESULT RuntimeAsyncApisProfiler::Shutdown() +{ + HRESULT hr = Profiler::Shutdown(); + + printf("RuntimeAsyncApisProfiler: failures=%d jit=%d/%d ip=%d il=%d " + "thrown=%d searches=%d unwinds=%d catchers=%d\n", + _failures.load(), _jitStarts.load(), _jitFinishes.load(), + _ipRoundTrips.load(), _ilMappings.load(), _exceptionsThrown.load(), + _targetSearches.load(), _targetUnwinds.load(), _catchers.load()); + + bool passed = + SUCCEEDED(hr) && + _failures == 0 && + _target != 0 && + _jitStarts > 0 && + _jitFinishes > 0 && + _ipRoundTrips > 0 && + _ilMappings > 0 && + _exceptionsThrown > 0 && + (_targetSearches > 0 || _targetUnwinds > 0) && + _catchers > 0; + + printf(passed ? "PROFILER TEST PASSES\n" : "PROFILER TEST FAILED\n"); + fflush(stdout); + return hr; +} + +ModuleID RuntimeAsyncApisProfiler::GetModuleId(FunctionID functionId) +{ + ClassID classId = 0; + ModuleID moduleId = 0; + mdToken token = 0; + ULONG32 typeArgumentCount = 0; + ClassID typeArguments[SHORT_LENGTH]; + COR_PRF_FRAME_INFO frameInfo = 0; + + HRESULT hr = pCorProfilerInfo->GetFunctionInfo2( + functionId, frameInfo, &classId, &moduleId, &token, + SHORT_LENGTH, &typeArgumentCount, typeArguments); + return SUCCEEDED(hr) ? moduleId : 0; +} diff --git a/src/tests/profiler/native/runtimeasyncapis/runtimeasyncapisprofiler.h b/src/tests/profiler/native/runtimeasyncapis/runtimeasyncapisprofiler.h new file mode 100644 index 00000000000000..0f23ecd5853e77 --- /dev/null +++ b/src/tests/profiler/native/runtimeasyncapis/runtimeasyncapisprofiler.h @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#pragma once + +#include "../profiler.h" + +class RuntimeAsyncApisProfiler : public Profiler +{ +public: + RuntimeAsyncApisProfiler(); + + static GUID GetClsid(); + HRESULT STDMETHODCALLTYPE Initialize(IUnknown* pCorProfilerInfoUnk) override; + HRESULT STDMETHODCALLTYPE Shutdown() override; + HRESULT STDMETHODCALLTYPE JITCompilationStarted( + FunctionID functionId, BOOL fIsSafeToBlock) override; + HRESULT STDMETHODCALLTYPE JITCompilationFinished( + FunctionID functionId, HRESULT hrStatus, BOOL fIsSafeToBlock) override; + HRESULT STDMETHODCALLTYPE ExceptionThrown(ObjectID thrownObjectId) override; + HRESULT STDMETHODCALLTYPE ExceptionSearchFunctionEnter(FunctionID functionId) override; + HRESULT STDMETHODCALLTYPE ExceptionUnwindFunctionEnter(FunctionID functionId) override; + HRESULT STDMETHODCALLTYPE ExceptionCatcherEnter(FunctionID functionId, ObjectID objectId) override; + +private: + bool IsTarget(FunctionID functionId); + ModuleID GetModuleId(FunctionID functionId); + + std::atomic _failures; + std::atomic _target; + std::atomic _jitStarts; + std::atomic _jitFinishes; + std::atomic _ipRoundTrips; + std::atomic _ilMappings; + std::atomic _exceptionsThrown; + std::atomic _targetSearches; + std::atomic _targetUnwinds; + std::atomic _catchers; +}; diff --git a/src/tests/profiler/native/runtimeasynctypes/runtimeasynctypesprofiler.cpp b/src/tests/profiler/native/runtimeasynctypes/runtimeasynctypesprofiler.cpp new file mode 100644 index 00000000000000..6c8e0eaacf641e --- /dev/null +++ b/src/tests/profiler/native/runtimeasynctypes/runtimeasynctypesprofiler.cpp @@ -0,0 +1,252 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "runtimeasynctypesprofiler.h" + +RuntimeAsyncTypesProfiler::RuntimeAsyncTypesProfiler() + : Profiler(), + _failures(0), + _classLoadStarts(0), + _classLoadFinishes(0), + _classLoadPairFailures(0), + _arrayClassLoads(0), + _continuationClassLoads(0), + _gcStarts(0), + _classInfo1Incomplete(0), + _classInfo2Incomplete(0), + _classLayoutIncomplete(0), + _continuationContractFailures(0), + _nilTokenLeaks(0), + _continuationObjects(0), + _continuationArrayEdges(0) +{ +} + +GUID RuntimeAsyncTypesProfiler::GetClsid() +{ + // {7F4E1A63-92C5-4D81-AB37-560EC294F108} + GUID clsid = {0x7f4e1a63, 0x92c5, 0x4d81, {0xab, 0x37, 0x56, 0x0e, 0xc2, 0x94, 0xf1, 0x08}}; + return clsid; +} + +HRESULT RuntimeAsyncTypesProfiler::Initialize(IUnknown* pCorProfilerInfoUnk) +{ + HRESULT hr = Profiler::Initialize(pCorProfilerInfoUnk); + if (FAILED(hr)) + { + return hr; + } + + return pCorProfilerInfo->SetEventMask2(COR_PRF_MONITOR_CLASS_LOADS | COR_PRF_MONITOR_GC, 0); +} + +HRESULT RuntimeAsyncTypesProfiler::ClassLoadStarted(ClassID classId) +{ + _classLoadStarts++; + std::lock_guard lock(_stateLock); + _classLoadsInProgress.insert(classId); + return S_OK; +} + +HRESULT RuntimeAsyncTypesProfiler::ClassLoadFinished(ClassID classId, HRESULT hrStatus) +{ + _classLoadFinishes++; + + { + std::lock_guard lock(_stateLock); + if (_classLoadsInProgress.erase(classId) == 0) + { + _classLoadPairFailures++; + } + } + + if (FAILED(hrStatus)) + { + _failures++; + return S_OK; + } + + CorElementType baseElementType = ELEMENT_TYPE_END; + ClassID baseClassId = 0; + ULONG rank = 0; + HRESULT hr = pCorProfilerInfo->IsArrayClass(classId, &baseElementType, &baseClassId, &rank); + if (hr == S_OK) + { + _arrayClassLoads++; + } + else if (hr == S_FALSE) + { + ModuleID moduleId = 0; + mdTypeDef token = 0; + ClassID parentClassId = 0; + ULONG32 typeArgumentCount = 0; + hr = pCorProfilerInfo->GetClassIDInfo2( + classId, &moduleId, &token, &parentClassId, 0, &typeArgumentCount, nullptr); + if (hr == CORPROF_E_DATAINCOMPLETE) + { + _continuationClassLoads++; + } + } + else + { + _failures++; + } + + return S_OK; +} + +bool RuntimeAsyncTypesProfiler::CheckContinuationApis(ClassID classId) +{ + ModuleID moduleId = 0; + mdTypeDef token = 0; + ClassID parentClassId = 0; + ULONG32 typeArgumentCount = 0; + + HRESULT info2 = pCorProfilerInfo->GetClassIDInfo2( + classId, &moduleId, &token, &parentClassId, 0, &typeArgumentCount, nullptr); + if (info2 == CORPROF_E_DATAINCOMPLETE) + { + _classInfo2Incomplete++; + } + bool info2ReturnedNilToken = info2 == S_OK && IsNilToken(token); + + moduleId = 0; + token = 0; + HRESULT info1 = pCorProfilerInfo->GetClassIDInfo(classId, &moduleId, &token); + if (info1 == CORPROF_E_DATAINCOMPLETE) + { + _classInfo1Incomplete++; + } + bool info1ReturnedNilToken = info1 == S_OK && IsNilToken(token); + + COR_FIELD_OFFSET fieldOffsets[64]; + ULONG fieldCount = 0; + ULONG classSize = 0; + HRESULT layout = pCorProfilerInfo->GetClassLayout( + classId, + fieldOffsets, + static_cast(sizeof(fieldOffsets) / sizeof(fieldOffsets[0])), + &fieldCount, + &classSize); + if (layout == CORPROF_E_DATAINCOMPLETE) + { + _classLayoutIncomplete++; + } + + bool metadataLess = info1 == CORPROF_E_DATAINCOMPLETE || + info2 == CORPROF_E_DATAINCOMPLETE || + layout == CORPROF_E_DATAINCOMPLETE; + if (metadataLess && + (info1 != CORPROF_E_DATAINCOMPLETE || + info2 != CORPROF_E_DATAINCOMPLETE || + layout != CORPROF_E_DATAINCOMPLETE)) + { + _continuationContractFailures++; + } + if (metadataLess && (info1ReturnedNilToken || info2ReturnedNilToken)) + { + _nilTokenLeaks++; + } + + return metadataLess; +} + +HRESULT RuntimeAsyncTypesProfiler::GarbageCollectionStarted( + int cGenerations, BOOL generationCollected[], COR_PRF_GC_REASON reason) +{ + _gcStarts++; + std::lock_guard lock(_stateLock); + _objectClasses.clear(); + _continuationEdges.clear(); + return S_OK; +} + +HRESULT RuntimeAsyncTypesProfiler::ObjectReferences( + ObjectID objectId, ClassID classId, ULONG cObjectRefs, ObjectID objectRefIds[]) +{ + bool metadataLess = CheckContinuationApis(classId); + + std::lock_guard lock(_stateLock); + _objectClasses[objectId] = classId; + if (metadataLess) + { + _continuationObjects++; + for (ULONG i = 0; i < cObjectRefs; i++) + { + if (objectRefIds[i] != 0) + { + _continuationEdges.emplace_back(objectId, objectRefIds[i]); + } + } + } + + return S_OK; +} + +void RuntimeAsyncTypesProfiler::AnalyzeObjectGraph() +{ + int arrayEdges = 0; + std::lock_guard lock(_stateLock); + for (const auto& edge : _continuationEdges) + { + auto target = _objectClasses.find(edge.second); + if (target == _objectClasses.end()) + { + continue; + } + + CorElementType baseElementType = ELEMENT_TYPE_END; + ClassID baseClassId = 0; + ULONG rank = 0; + if (pCorProfilerInfo->IsArrayClass( + target->second, &baseElementType, &baseClassId, &rank) == S_OK && + rank == 1) + { + arrayEdges++; + } + } + + _continuationArrayEdges += arrayEdges; +} + +HRESULT RuntimeAsyncTypesProfiler::GarbageCollectionFinished() +{ + AnalyzeObjectGraph(); + return S_OK; +} + +HRESULT RuntimeAsyncTypesProfiler::Shutdown() +{ + HRESULT hr = Profiler::Shutdown(); + + printf("RuntimeAsyncTypesProfiler: failures=%d classLoads=%d/%d pairFailures=%d " + "arrays=%d continuations=%d info1=%d info2=%d layout=%d contractFailures=%d " + "nilTokens=%d gcStarts=%d continuationObjects=%d arrayEdges=%d\n", + _failures.load(), + _classLoadStarts.load(), _classLoadFinishes.load(), _classLoadPairFailures.load(), + _arrayClassLoads.load(), _continuationClassLoads.load(), + _classInfo1Incomplete.load(), _classInfo2Incomplete.load(), + _classLayoutIncomplete.load(), _continuationContractFailures.load(), + _nilTokenLeaks.load(), _gcStarts.load(), _continuationObjects.load(), + _continuationArrayEdges.load()); + + bool passed = + SUCCEEDED(hr) && + _failures == 0 && + _classLoadStarts == _classLoadFinishes && + _classLoadPairFailures == 0 && + _arrayClassLoads > 0 && + _continuationClassLoads > 0 && + _gcStarts > 0 && + _classInfo1Incomplete > 0 && + _classInfo2Incomplete > 0 && + _classLayoutIncomplete > 0 && + _continuationContractFailures == 0 && + _nilTokenLeaks == 0 && + _continuationObjects > 0 && + _continuationArrayEdges > 0; + + printf(passed ? "PROFILER TEST PASSES\n" : "PROFILER TEST FAILED\n"); + fflush(stdout); + return hr; +} diff --git a/src/tests/profiler/native/runtimeasynctypes/runtimeasynctypesprofiler.h b/src/tests/profiler/native/runtimeasynctypes/runtimeasynctypesprofiler.h new file mode 100644 index 00000000000000..15cbe3ac6f209f --- /dev/null +++ b/src/tests/profiler/native/runtimeasynctypes/runtimeasynctypesprofiler.h @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#pragma once + +#include "../profiler.h" + +#include +#include +#include +#include +#include + +class RuntimeAsyncTypesProfiler : public Profiler +{ +public: + RuntimeAsyncTypesProfiler(); + + static GUID GetClsid(); + HRESULT STDMETHODCALLTYPE Initialize(IUnknown* pCorProfilerInfoUnk) override; + HRESULT STDMETHODCALLTYPE Shutdown() override; + HRESULT STDMETHODCALLTYPE ClassLoadStarted(ClassID classId) override; + HRESULT STDMETHODCALLTYPE ClassLoadFinished(ClassID classId, HRESULT hrStatus) override; + HRESULT STDMETHODCALLTYPE GarbageCollectionStarted( + int cGenerations, BOOL generationCollected[], COR_PRF_GC_REASON reason) override; + HRESULT STDMETHODCALLTYPE GarbageCollectionFinished() override; + HRESULT STDMETHODCALLTYPE ObjectReferences( + ObjectID objectId, ClassID classId, ULONG cObjectRefs, ObjectID objectRefIds[]) override; + +private: + bool CheckContinuationApis(ClassID classId); + void AnalyzeObjectGraph(); + + std::atomic _failures; + std::atomic _classLoadStarts; + std::atomic _classLoadFinishes; + std::atomic _classLoadPairFailures; + std::atomic _arrayClassLoads; + std::atomic _continuationClassLoads; + std::atomic _gcStarts; + std::atomic _classInfo1Incomplete; + std::atomic _classInfo2Incomplete; + std::atomic _classLayoutIncomplete; + std::atomic _continuationContractFailures; + std::atomic _nilTokenLeaks; + std::atomic _continuationObjects; + std::atomic _continuationArrayEdges; + + std::mutex _stateLock; + std::unordered_set _classLoadsInProgress; + std::unordered_map _objectClasses; + std::vector> _continuationEdges; +}; diff --git a/src/tests/profiler/runtimeasyncapis/runtimeasyncapis.cs b/src/tests/profiler/runtimeasyncapis/runtimeasyncapis.cs new file mode 100644 index 00000000000000..bb4112fd8d40e6 --- /dev/null +++ b/src/tests/profiler/runtimeasyncapis/runtimeasyncapis.cs @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Profiler.Tests; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading.Tasks; + +internal static class RuntimeAsyncApis +{ + private static readonly Guid ProfilerGuid = new("9A20F5D8-47B1-4E63-82CD-1F7690AB34E2"); + + public static int Main(string[] args) + { + if (args.Length > 0 && args[0].Equals("RunTest", StringComparison.OrdinalIgnoreCase)) + { + return RunTest(); + } + + return ProfilerTestRunner.Run( + profileePath: Assembly.GetExecutingAssembly().Location, + testName: "RuntimeAsyncApis", + profilerClsid: ProfilerGuid, + profileeOptions: ProfileeOptions.OptimizationSensitive, + envVars: new Dictionary + { + ["DOTNET_RuntimeAsync"] = "1" + }); + } + + private static async Task Work() + { + await Task.Yield(); + throw new InvalidOperationException("Runtime Async profiler exception"); + } + + private static async Task RunAsync() + { + try + { + await Work(); + } + catch (InvalidOperationException) + { + return 100; + } + + return 1; + } + + private static int RunTest() => RunAsync().GetAwaiter().GetResult(); +} diff --git a/src/tests/profiler/runtimeasyncapis/runtimeasyncapis.csproj b/src/tests/profiler/runtimeasyncapis/runtimeasyncapis.csproj new file mode 100644 index 00000000000000..d849e39cb9ba04 --- /dev/null +++ b/src/tests/profiler/runtimeasyncapis/runtimeasyncapis.csproj @@ -0,0 +1,15 @@ + + + .NETCoreApp + exe + true + runtime-async=on + true + + + + + + + + diff --git a/src/tests/profiler/runtimeasynctypes/runtimeasynctypes.cs b/src/tests/profiler/runtimeasynctypes/runtimeasynctypes.cs new file mode 100644 index 00000000000000..ddfa7fd550ead2 --- /dev/null +++ b/src/tests/profiler/runtimeasynctypes/runtimeasynctypes.cs @@ -0,0 +1,64 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Profiler.Tests; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading.Tasks; + +internal static class RuntimeAsyncTypes +{ + private struct Payload + { + public int Value; + } + + private static readonly Guid ProfilerGuid = new("7F4E1A63-92C5-4D81-AB37-560EC294F108"); + private static readonly TaskCompletionSource Gate = + new(TaskCreationOptions.RunContinuationsAsynchronously); + + public static int Main(string[] args) + { + if (args.Length > 0 && args[0].Equals("RunTest", StringComparison.OrdinalIgnoreCase)) + { + return RunTest(); + } + + return ProfilerTestRunner.Run( + profileePath: Assembly.GetExecutingAssembly().Location, + testName: "RuntimeAsyncTypes", + profilerClsid: ProfilerGuid, + envVars: new Dictionary + { + ["DOTNET_RuntimeAsync"] = "1" + }); + } + + private static async Task Suspended(Payload[] payload) + { + await Gate.Task; + return payload[0].Value; + } + + private static int RunTest() + { + const int ContinuationCount = 32; + Task[] suspended = new Task[ContinuationCount]; + for (int i = 0; i < suspended.Length; i++) + { + Payload[] payload = new Payload[16]; + payload[0].Value = i; + suspended[i] = Suspended(payload); + } + + for (int i = 0; i < 10; i++) + { + GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, blocking: true); + } + + Gate.SetResult(0); + Task.WaitAll(suspended); + return 100; + } +} diff --git a/src/tests/profiler/runtimeasynctypes/runtimeasynctypes.csproj b/src/tests/profiler/runtimeasynctypes/runtimeasynctypes.csproj new file mode 100644 index 00000000000000..d849e39cb9ba04 --- /dev/null +++ b/src/tests/profiler/runtimeasynctypes/runtimeasynctypes.csproj @@ -0,0 +1,15 @@ + + + .NETCoreApp + exe + true + runtime-async=on + true + + + + + + + +