Skip to content

Commit

Permalink
Disable test when running under GCStress (#96508)
Browse files Browse the repository at this point in the history
* Rewrite HandleRef test
  • Loading branch information
AaronRobinsonMSFT authored Jan 5, 2024
1 parent bc81c55 commit 4838097
Showing 1 changed file with 84 additions and 59 deletions.
143 changes: 84 additions & 59 deletions src/tests/Interop/PInvoke/Miscellaneous/HandleRef/HandleRefTest.cs
Original file line number Diff line number Diff line change
@@ -1,88 +1,113 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.InteropServices;
using System;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using Xunit;

public class HandleRefTest
public unsafe class HandleRefTest
{
[DllImport(@"HandleRefNative", CallingConvention = CallingConvention.Winapi)]
[DllImport(@"HandleRefNative")]
private static extern int MarshalPointer_In(HandleRef pintValue, int stackGuard);

[DllImport(@"HandleRefNative", CallingConvention = CallingConvention.Winapi)]
[DllImport(@"HandleRefNative")]
private static extern int MarshalPointer_InOut(HandleRef pintValue, int stackGuard);

[DllImport(@"HandleRefNative", CallingConvention = CallingConvention.Winapi)]
[DllImport(@"HandleRefNative")]
private static extern int MarshalPointer_Out(HandleRef pintValue, int stackGuard);

[DllImport(@"HandleRefNative", CallingConvention = CallingConvention.Winapi)]
private static extern int TestNoGC(HandleRef pintValue, Action gcCallback);
[DllImport(@"HandleRefNative")]
private static extern int TestNoGC(HandleRef pintValue, void* gcCallback);

[DllImport(@"HandleRefNative")]
private static extern HandleRef InvalidMarshalPointer_Return();

// See matching values in HandleRefNative.cpp
const int intManaged = 1000;
const int intNative = 2000;
const int intReturn = 3000;
const int stackGuard = 5000;

[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/91388", typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.PlatformDoesNotSupportNativeTestAssets))]
public static void Validate_In()
{
int value = intManaged;
int* pInt = &value;
HandleRef hr = new HandleRef(new object(), (IntPtr)pInt);
Assert.Equal(intReturn, MarshalPointer_In(hr, stackGuard));
Assert.Equal(intManaged, value);
}

[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/91388", typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.PlatformDoesNotSupportNativeTestAssets))]
public static void Validate_InOut()
{
int value = intManaged;
int* pInt = &value;
HandleRef hr = new HandleRef(new object(), (IntPtr)pInt);
Assert.Equal(intReturn, MarshalPointer_InOut(hr, stackGuard));
Assert.Equal(intNative, value);
}

[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/91388", typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.PlatformDoesNotSupportNativeTestAssets))]
public static void Validate_Out()
{
int value = intManaged;
int* pInt = &value;
HandleRef hr = new HandleRef(new object(), (IntPtr)pInt);
Assert.Equal(intReturn, MarshalPointer_Out(hr, stackGuard));
Assert.Equal(intNative, value);
}

[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/91388", typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.PlatformDoesNotSupportNativeTestAssets))]
public static void Validate_InvalidReturn()
{
Assert.Throws<MarshalDirectiveException>(() => InvalidMarshalPointer_Return());
}

[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/91388", typeof(TestLibrary.PlatformDetection), nameof(TestLibrary.PlatformDetection.PlatformDoesNotSupportNativeTestAssets))]
public unsafe static int TestEntryPoint()
[SkipOnCoreClr("https://github.com/dotnet/runtime/issues/96364", RuntimeTestModes.GCStress3)]
[SkipOnCoreClr("https://github.com/dotnet/runtime/issues/96364", RuntimeTestModes.GCStressC)]
public static void Validate_NoGC()
{
try{
const int intManaged = 1000;
const int intNative = 2000;
const int intReturn = 3000;
const int stackGuard = 5000;

Console.WriteLine("MarshalPointer_In");
int int1 = intManaged;
int* int1Ptr = &int1;
HandleRef hr1 = new HandleRef(new Object(), (IntPtr)int1Ptr);
Assert.Equal(intReturn, MarshalPointer_In(hr1, stackGuard));
Assert.Equal(intManaged, int1);

Console.WriteLine("MarshalPointer_InOut");
int int2 = intManaged;
int* int2Ptr = &int2;
HandleRef hr2 = new HandleRef(new Object(), (IntPtr)int2Ptr);
Assert.Equal(intReturn, MarshalPointer_InOut(hr2, stackGuard));
Assert.Equal(intNative, int2);

Console.WriteLine("MarshalPointer_Out");
int int3 = intManaged;
int* int3Ptr = &int3;
HandleRef hr3 = new HandleRef(new Object(), (IntPtr)int3Ptr);
Assert.Equal(intReturn, MarshalPointer_Out(hr3, stackGuard));
Assert.Equal(intNative, int3);

// Note that this scenario will always pass in a debug build because all values
// stay rooted until the end of the method.
Console.WriteLine("TestNoGC");

int* int4Ptr = (int*)Marshal.AllocHGlobal(sizeof(int)); // We don't free this memory so we don't have to worry about a GC run between freeing and return (possible in a GCStress mode).
Console.WriteLine("2");
*int4Ptr = intManaged;
CollectableClass collectableClass = new CollectableClass(int4Ptr);
HandleRef hr4 = new HandleRef(collectableClass, (IntPtr)int4Ptr);
Action gcCallback = () => { Console.WriteLine("GC callback now"); GC.Collect(2, GCCollectionMode.Forced); GC.WaitForPendingFinalizers(); GC.Collect(2, GCCollectionMode.Forced); };
Assert.Equal(intReturn, TestNoGC(hr4, gcCallback));
Console.WriteLine("Native code finished");

Console.WriteLine("InvalidMarshalPointer_Return");
Assert.Throws<MarshalDirectiveException>(() => InvalidMarshalPointer_Return());

return 100;
} catch (Exception e){
Console.WriteLine($"Test Failure: {e}");
return 101;
HandleRef hr = CreateHandleRef();
Assert.Equal(intReturn, TestNoGC(hr, (delegate* unmanaged<void>)&Callback));

[UnmanagedCallersOnly]
static void Callback()
{
Console.WriteLine("GC Callback Begin");
GC.Collect(2, GCCollectionMode.Forced);
Console.WriteLine("GC Callback before WaitForPendingFinalizers()");
GC.WaitForPendingFinalizers();
Console.WriteLine("GC Callback after WaitForPendingFinalizers()");
GC.Collect(2, GCCollectionMode.Forced);
Console.WriteLine("GC Callback End");
}

[MethodImpl(MethodImplOptions.NoInlining)]
static HandleRef CreateHandleRef()
{
// We don't free this memory so we don't have to worry
// about a race with CollectableClass's finalizer.
int* pInt = (int*)NativeMemory.Alloc(sizeof(int));
*pInt = intManaged;
CollectableClass collectableClass = new(pInt);
return new HandleRef(collectableClass, (IntPtr)pInt);
}
}

/// <summary>
/// Class that will change a pointer passed to native code when this class gets finalized.
/// Native code can check whether the pointer changed during a P/Invoke
/// </summary>
unsafe class CollectableClass
class CollectableClass
{
int* PtrToChange;
public CollectableClass(int* ptrToChange)
Expand All @@ -92,8 +117,8 @@ public CollectableClass(int* ptrToChange)

~CollectableClass()
{
Console.WriteLine("CollectableClass collected");
*PtrToChange = Int32.MaxValue;
int* ptr = PtrToChange;
*ptr = Int32.MaxValue;
}
}
}

0 comments on commit 4838097

Please sign in to comment.