From a19f5a28b339e9bd8de9a15747981f32de147cdc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 14 Jul 2025 18:52:06 +0000 Subject: [PATCH 1/3] Initial plan From 1bc701be2c14349d19584921fa8d4aca0c6117a9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 14 Jul 2025 19:19:13 +0000 Subject: [PATCH 2/3] Add retry logic to PerformanceCounter test helpers to handle file access conflicts Co-authored-by: ericstj <8918108+ericstj@users.noreply.github.com> --- .../tests/Helpers.cs | 44 ++++++++++++++++++- .../tests/PerformanceCounterCategoryTests.cs | 10 ++--- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Diagnostics.PerformanceCounter/tests/Helpers.cs b/src/libraries/System.Diagnostics.PerformanceCounter/tests/Helpers.cs index 0e2407302a2ff7..e63b3058c021d8 100644 --- a/src/libraries/System.Diagnostics.PerformanceCounter/tests/Helpers.cs +++ b/src/libraries/System.Diagnostics.PerformanceCounter/tests/Helpers.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.IO; using System.Threading; +using System.ComponentModel; using Xunit; // Implementation is not robust with respect to modifying counter categories @@ -17,6 +18,36 @@ internal class Helpers public static bool CanWriteToPerfCounters { get => PlatformDetection.IsNotWindowsNanoServer; } public static bool CanReadNetPerfCounters { get => File.Exists(Environment.SystemDirectory + Path.DirectorySeparatorChar + "netfxperf.dll"); } + /// + /// Determines if an exception is retriable for performance counter operations. + /// Typically used for file access conflicts and transient system errors. + /// + private static bool IsRetriableException(Exception ex) + { + // Handle Win32Exception with specific error codes for file access conflicts + if (ex is Win32Exception win32Ex) + { + // ERROR_SHARING_VIOLATION (32) - The process cannot access the file because it is being used by another process + // ERROR_ACCESS_DENIED (5) - Access is denied + // ERROR_LOCK_VIOLATION (33) - The process cannot access the file because another process has locked a portion of the file + return win32Ex.NativeErrorCode == 32 || win32Ex.NativeErrorCode == 5 || win32Ex.NativeErrorCode == 33; + } + + // Handle IOException for file access issues + if (ex is IOException) + { + return true; + } + + // Handle UnauthorizedAccessException + if (ex is UnauthorizedAccessException) + { + return true; + } + + return false; + } + public static void CreateCategory(string categoryName, PerformanceCounterCategoryType categoryType) { string counterName = categoryName.Replace("_Category", "_Counter"); @@ -30,7 +61,12 @@ public static void CreateCategory(string categoryName, string counterName, Perfo // If the category already exists, delete it, then create it. DeleteCategory(categoryName); - PerformanceCounterCategory.Create(categoryName, "description", categoryType, counterName, "counter description"); + + // Retry the Create operation to handle file access conflicts + RetryHelper.Execute(() => + { + PerformanceCounterCategory.Create(categoryName, "description", categoryType, counterName, "counter description"); + }, maxAttempts: 10, retryWhen: IsRetriableException); VerifyPerformanceCounterCategoryCreated(categoryName); } @@ -53,7 +89,11 @@ public static void DeleteCategory(string categoryName) Assert.EndsWith("_Category", categoryName); if (PerformanceCounterCategory.Exists(categoryName)) { - PerformanceCounterCategory.Delete(categoryName); + // Retry the Delete operation to handle file access conflicts + RetryHelper.Execute(() => + { + PerformanceCounterCategory.Delete(categoryName); + }, maxAttempts: 10, retryWhen: IsRetriableException); } int tries = 0; diff --git a/src/libraries/System.Diagnostics.PerformanceCounter/tests/PerformanceCounterCategoryTests.cs b/src/libraries/System.Diagnostics.PerformanceCounter/tests/PerformanceCounterCategoryTests.cs index 2a164b91ef2aee..c2edf6658ff55d 100644 --- a/src/libraries/System.Diagnostics.PerformanceCounter/tests/PerformanceCounterCategoryTests.cs +++ b/src/libraries/System.Diagnostics.PerformanceCounter/tests/PerformanceCounterCategoryTests.cs @@ -87,7 +87,7 @@ public static void PerformanceCounterCategory_CategoryType_MultiInstance() PerformanceCounterCategory pcc = new PerformanceCounterCategory(categoryName); Assert.Equal(PerformanceCounterCategoryType.MultiInstance, Helpers.RetryOnAllPlatformsWithClosingResources(() => pcc.CategoryType)); - PerformanceCounterCategory.Delete(categoryName); + Helpers.DeleteCategory(categoryName); } [ConditionalFact(typeof(Helpers), nameof(Helpers.IsElevatedAndCanWriteAndReadNetPerfCounters))] @@ -100,7 +100,7 @@ public static void PerformanceCounterCategory_CategoryType_SingleInstance() PerformanceCounterCategory pcc = new PerformanceCounterCategory(categoryName); Assert.Equal(PerformanceCounterCategoryType.SingleInstance, Helpers.RetryOnAllPlatformsWithClosingResources(() => pcc.CategoryType)); - PerformanceCounterCategory.Delete(categoryName); + Helpers.DeleteCategory(categoryName); } #pragma warning disable 0618 // obsolete warning @@ -115,7 +115,7 @@ public static void PerformanceCounterCategory_Create_Obsolete() PerformanceCounterCategory.Create(categoryName, "category help", counterName, "counter help"); Assert.True(PerformanceCounterCategory.Exists(categoryName)); - PerformanceCounterCategory.Delete(categoryName); + Helpers.DeleteCategory(categoryName); } [ConditionalFact(typeof(Helpers), nameof(Helpers.IsElevatedAndCanWriteToPerfCounters))] @@ -132,7 +132,7 @@ public static void PerformanceCounterCategory_Create_Obsolete_CCD() PerformanceCounterCategory.Create(categoryName, "category help", ccdc); Assert.True(PerformanceCounterCategory.Exists(categoryName)); - PerformanceCounterCategory.Delete(categoryName); + Helpers.DeleteCategory(categoryName); } #pragma warning restore 0618 @@ -232,7 +232,7 @@ public static void PerformanceCounterCategory_GetCounters() PerformanceCounter[] counters = pcc.GetCounters(); Assert.True(counters.Length > 0); - PerformanceCounterCategory.Delete(categoryName); + Helpers.DeleteCategory(categoryName); } [Fact] From 3f1d1621ff47d35af5e2a552cd22d469867aa7c2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 14 Jul 2025 20:50:05 +0000 Subject: [PATCH 3/3] Address code review feedback: wrap PerformanceCounterCategory.Create calls with RetryHelper and use Helpers.DeleteCategory Co-authored-by: ericstj <8918108+ericstj@users.noreply.github.com> --- .../tests/Helpers.cs | 2 +- .../tests/PerformanceCounterCategoryTests.cs | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Diagnostics.PerformanceCounter/tests/Helpers.cs b/src/libraries/System.Diagnostics.PerformanceCounter/tests/Helpers.cs index e63b3058c021d8..1110cd6fb775e8 100644 --- a/src/libraries/System.Diagnostics.PerformanceCounter/tests/Helpers.cs +++ b/src/libraries/System.Diagnostics.PerformanceCounter/tests/Helpers.cs @@ -22,7 +22,7 @@ internal class Helpers /// Determines if an exception is retriable for performance counter operations. /// Typically used for file access conflicts and transient system errors. /// - private static bool IsRetriableException(Exception ex) + internal static bool IsRetriableException(Exception ex) { // Handle Win32Exception with specific error codes for file access conflicts if (ex is Win32Exception win32Ex) diff --git a/src/libraries/System.Diagnostics.PerformanceCounter/tests/PerformanceCounterCategoryTests.cs b/src/libraries/System.Diagnostics.PerformanceCounter/tests/PerformanceCounterCategoryTests.cs index c2edf6658ff55d..dc6d61ad29f49e 100644 --- a/src/libraries/System.Diagnostics.PerformanceCounter/tests/PerformanceCounterCategoryTests.cs +++ b/src/libraries/System.Diagnostics.PerformanceCounter/tests/PerformanceCounterCategoryTests.cs @@ -112,7 +112,10 @@ public static void PerformanceCounterCategory_Create_Obsolete() Helpers.DeleteCategory(categoryName); - PerformanceCounterCategory.Create(categoryName, "category help", counterName, "counter help"); + RetryHelper.Execute(() => + { + PerformanceCounterCategory.Create(categoryName, "category help", counterName, "counter help"); + }, maxAttempts: 10, retryWhen: Helpers.IsRetriableException); Assert.True(PerformanceCounterCategory.Exists(categoryName)); Helpers.DeleteCategory(categoryName); @@ -129,7 +132,10 @@ public static void PerformanceCounterCategory_Create_Obsolete_CCD() Helpers.DeleteCategory(categoryName); - PerformanceCounterCategory.Create(categoryName, "category help", ccdc); + RetryHelper.Execute(() => + { + PerformanceCounterCategory.Create(categoryName, "category help", ccdc); + }, maxAttempts: 10, retryWhen: Helpers.IsRetriableException); Assert.True(PerformanceCounterCategory.Exists(categoryName)); Helpers.DeleteCategory(categoryName); @@ -209,7 +215,7 @@ public static void PerformanceCounterCategory_DeleteCategory() string categoryName = nameof(PerformanceCounterCategory_DeleteCategory) + "_Category"; Helpers.CreateCategory(categoryName, PerformanceCounterCategoryType.SingleInstance); - PerformanceCounterCategory.Delete(categoryName); + Helpers.DeleteCategory(categoryName); Assert.False(PerformanceCounterCategory.Exists(categoryName)); }