Skip to content

Commit

Permalink
Rev PInvoke tests
Browse files Browse the repository at this point in the history
  • Loading branch information
VSadov committed Nov 14, 2024
1 parent c94e12f commit 453cbce
Show file tree
Hide file tree
Showing 8 changed files with 384 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
project (ForeignThreadRevPInvokeUnhandledNative)

include_directories(${INC_PLATFORM_DIR})

if(CLR_CMAKE_HOST_OSX)
# Enable non-POSIX pthreads APIs, which by default are not included in the pthreads header
add_definitions(-D_DARWIN_C_SOURCE)
endif(CLR_CMAKE_HOST_OSX)

set(SOURCES ForeignThreadRevPInvokeUnhandledNative.cpp)

if(NOT CLR_CMAKE_HOST_WIN32)
add_compile_options(-pthread)
endif()

# add the executable
add_library (ForeignThreadRevPInvokeUnhandledNative SHARED ${SOURCES})

# add the install targets
install (TARGETS ForeignThreadRevPInvokeUnhandledNative DESTINATION bin)
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using Xunit;

public delegate void MyCallback();

public class PInvokeRevPInvokeUnhandled
{
[DllImport("ForeignThreadRevPInvokeUnhandled")]
public static extern void InvokeCallback(MyCallback callback);

[DllImport("ForeignThreadRevPInvokeUnhandled")]
public static extern void InvokeCallbackOnNewThread(MyCallback callback);

[ThreadStatic]
private static Exception lastEx;
private static bool expectUnhandledException = false;

private static bool Handler(Exception ex)
{
lastEx = ex;
return true;
}

private static void SetHandler()
{
System.Runtime.ExceptionServices.ExceptionHandling.SetUnhandledExceptionHandler(Handler);
}

// test-wide setup
static PInvokeRevPInvokeUnhandled()
{
AppDomain.CurrentDomain.UnhandledException += (_, _) =>
{
if (expectUnhandledException &&
lastEx == null)
{
Environment.Exit(100);
}
};

SetHandler();
}

public static void RunTest()
{
// sanity check, the handler should be working in a separate thread
Thread th = new Thread(() =>
{
try
{
lastEx = null;
throw new Exception("here is an unhandled exception1");
Assert.Fail();
}
finally
{
Assert.Equal("here is an unhandled exception1", lastEx.Message);
}
});

th.Start();
th.Join();

expectUnhandledException = true;
InvokeCallbackOnNewThread(() => {
try
{
lastEx = null;
throw new Exception("here is an unhandled exception2");
Assert.Fail();
}
finally
{
Assert.Null(lastEx);
}
});

Assert.Fail();
}

public static int Main()
{
RunTest();

// should not reach here
return 42;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Needed for CMakeProjectReference -->
<RequiresProcessIsolation>true</RequiresProcessIsolation>
<!-- Test requires EH-clean Main -->
<ReferenceXUnitWrapperGenerator>false</ReferenceXUnitWrapperGenerator>
<MonoAotIncompatible>true</MonoAotIncompatible>
</PropertyGroup>
<ItemGroup>
<Compile Include="ForeignThreadRevPInvokeUnhandled.cs" />
</ItemGroup>
<ItemGroup>
<CMakeProjectReference Include="CMakeLists.txt" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#include "stdio.h"
#include <stdlib.h>

#ifdef _WIN32
#pragma warning(push)
#pragma warning(disable:4265 4577)
#include <thread>
#pragma warning(pop)
#else // _WIN32
#include <pthread.h>
#endif // _WIN32

// Work around typedef redefinition: platformdefines.h defines error_t
// as unsigned while it's defined as int in errno.h.
#define error_t error_t_ignore
#include <platformdefines.h>
#undef error_t

typedef void (*PFNACTION1)();
extern "C" DLL_EXPORT void InvokeCallback(PFNACTION1 callback)
{
callback();
}

#ifndef _WIN32
void* InvokeCallbackUnix(void* callback)
{
InvokeCallback((PFNACTION1)callback);
return NULL;
}

#define AbortIfFail(st) if (st != 0) abort()

#endif // !_WIN32

extern "C" DLL_EXPORT void InvokeCallbackOnNewThread(PFNACTION1 callback)
{
#ifdef _WIN32
std::thread t1(InvokeCallback, callback);
t1.join();
#else // _WIN32
// For Unix, we need to use pthreads to create the thread so that we can set its stack size.
// We need to set the stack size due to the very small (80kB) default stack size on MUSL
// based Linux distros.
pthread_attr_t attr;
int st = pthread_attr_init(&attr);
AbortIfFail(st);

// set stack size to 1.5MB
st = pthread_attr_setstacksize(&attr, 0x180000);
AbortIfFail(st);

pthread_t t;
st = pthread_create(&t, &attr, InvokeCallbackUnix, (void*)callback);
AbortIfFail(st);

st = pthread_join(t, NULL);
AbortIfFail(st);
#endif // _WIN32
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
project (PInvokeRevPInvokeUnhandledNative)

include_directories(${INC_PLATFORM_DIR})

if(CLR_CMAKE_HOST_OSX)
# Enable non-POSIX pthreads APIs, which by default are not included in the pthreads header
add_definitions(-D_DARWIN_C_SOURCE)
endif(CLR_CMAKE_HOST_OSX)

set(SOURCES PInvokeRevPInvokeUnhandledNative.cpp)

if(NOT CLR_CMAKE_HOST_WIN32)
add_compile_options(-pthread)
endif()

# add the executable
add_library (PInvokeRevPInvokeUnhandledNative SHARED ${SOURCES})

# add the install targets
install (TARGETS PInvokeRevPInvokeUnhandledNative DESTINATION bin)
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using Xunit;

public delegate void MyCallback();

public class PInvokeRevPInvokeUnhandled
{
[DllImport("PInvokeRevPInvokeUnhandledNative")]
public static extern void InvokeCallback(MyCallback callback);

[DllImport("PInvokeRevPInvokeUnhandledNative")]
public static extern void InvokeCallbackOnNewThread(MyCallback callback);

[ThreadStatic]
private static Exception lastEx;
private static bool expectUnhandledException = false;

private static bool Handler(Exception ex)
{
lastEx = ex;
return true;
}

private static void SetHandler()
{
System.Runtime.ExceptionServices.ExceptionHandling.SetUnhandledExceptionHandler(Handler);
}

// test-wide setup
static PInvokeRevPInvokeUnhandled()
{
AppDomain.CurrentDomain.UnhandledException += (_, _) =>
{
if (expectUnhandledException &&
lastEx == null)
{
Environment.Exit(100);
}
};

SetHandler();
}

public static void RunTest()
{
// sanity check, the handler should be working in a separate thread
Thread th = new Thread(() =>
{
try
{
lastEx = null;
throw new Exception("here is an unhandled exception1");
Assert.Fail();
}
finally
{
Assert.Equal("here is an unhandled exception1", lastEx.Message);
}
});

th.Start();
th.Join();

expectUnhandledException = true;
InvokeCallback(() => {
try
{
lastEx = null;
throw new Exception("here is an unhandled exception2");
Assert.Fail();
}
finally
{
Assert.Null(lastEx);
}
});

Assert.Fail();
}

public static int Main()
{
RunTest();

// should not reach here
return 42;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Needed for CMakeProjectReference -->
<RequiresProcessIsolation>true</RequiresProcessIsolation>
<!-- Test requires EH-clean Main -->
<ReferenceXUnitWrapperGenerator>false</ReferenceXUnitWrapperGenerator>
<MonoAotIncompatible>true</MonoAotIncompatible>
</PropertyGroup>
<ItemGroup>
<Compile Include="PInvokeRevPInvokeUnhandled.cs" />
</ItemGroup>
<ItemGroup>
<CMakeProjectReference Include="CMakeLists.txt" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#include "stdio.h"
#include <stdlib.h>

#ifdef _WIN32
#pragma warning(push)
#pragma warning(disable:4265 4577)
#include <thread>
#pragma warning(pop)
#else // _WIN32
#include <pthread.h>
#endif // _WIN32

// Work around typedef redefinition: platformdefines.h defines error_t
// as unsigned while it's defined as int in errno.h.
#define error_t error_t_ignore
#include <platformdefines.h>
#undef error_t

typedef void (*PFNACTION1)();
extern "C" DLL_EXPORT void InvokeCallback(PFNACTION1 callback)
{
callback();
}

#ifndef _WIN32
void* InvokeCallbackUnix(void* callback)
{
InvokeCallback((PFNACTION1)callback);
return NULL;
}

#define AbortIfFail(st) if (st != 0) abort()

#endif // !_WIN32

extern "C" DLL_EXPORT void InvokeCallbackOnNewThread(PFNACTION1 callback)
{
#ifdef _WIN32
std::thread t1(InvokeCallback, callback);
t1.join();
#else // _WIN32
// For Unix, we need to use pthreads to create the thread so that we can set its stack size.
// We need to set the stack size due to the very small (80kB) default stack size on MUSL
// based Linux distros.
pthread_attr_t attr;
int st = pthread_attr_init(&attr);
AbortIfFail(st);

// set stack size to 1.5MB
st = pthread_attr_setstacksize(&attr, 0x180000);
AbortIfFail(st);

pthread_t t;
st = pthread_create(&t, &attr, InvokeCallbackUnix, (void*)callback);
AbortIfFail(st);

st = pthread_join(t, NULL);
AbortIfFail(st);
#endif // _WIN32
}

0 comments on commit 453cbce

Please sign in to comment.