Skip to content

Commit e9aeb09

Browse files
committed
Rev PInvoke tests
1 parent 8bd32f2 commit e9aeb09

File tree

8 files changed

+384
-0
lines changed

8 files changed

+384
-0
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
project (ForeignThreadRevPInvokeUnhandledNative)
2+
3+
include_directories(${INC_PLATFORM_DIR})
4+
5+
if(CLR_CMAKE_HOST_OSX)
6+
# Enable non-POSIX pthreads APIs, which by default are not included in the pthreads header
7+
add_definitions(-D_DARWIN_C_SOURCE)
8+
endif(CLR_CMAKE_HOST_OSX)
9+
10+
set(SOURCES ForeignThreadRevPInvokeUnhandledNative.cpp)
11+
12+
if(NOT CLR_CMAKE_HOST_WIN32)
13+
add_compile_options(-pthread)
14+
endif()
15+
16+
# add the executable
17+
add_library (ForeignThreadRevPInvokeUnhandledNative SHARED ${SOURCES})
18+
19+
# add the install targets
20+
install (TARGETS ForeignThreadRevPInvokeUnhandledNative DESTINATION bin)
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using System.Runtime.InteropServices;
8+
using Xunit;
9+
10+
public delegate void MyCallback();
11+
12+
public class PInvokeRevPInvokeUnhandled
13+
{
14+
[DllImport("ForeignThreadRevPInvokeUnhandled")]
15+
public static extern void InvokeCallback(MyCallback callback);
16+
17+
[DllImport("ForeignThreadRevPInvokeUnhandled")]
18+
public static extern void InvokeCallbackOnNewThread(MyCallback callback);
19+
20+
[ThreadStatic]
21+
private static Exception lastEx;
22+
private static bool expectUnhandledException = false;
23+
24+
private static bool Handler(Exception ex)
25+
{
26+
lastEx = ex;
27+
return true;
28+
}
29+
30+
private static void SetHandler()
31+
{
32+
System.Runtime.ExceptionServices.ExceptionHandling.SetUnhandledExceptionHandler(Handler);
33+
}
34+
35+
// test-wide setup
36+
static PInvokeRevPInvokeUnhandled()
37+
{
38+
AppDomain.CurrentDomain.UnhandledException += (_, _) =>
39+
{
40+
if (expectUnhandledException &&
41+
lastEx == null)
42+
{
43+
Environment.Exit(100);
44+
}
45+
};
46+
47+
SetHandler();
48+
}
49+
50+
public static void RunTest()
51+
{
52+
// sanity check, the handler should be working in a separate thread
53+
Thread th = new Thread(() =>
54+
{
55+
try
56+
{
57+
lastEx = null;
58+
throw new Exception("here is an unhandled exception1");
59+
Assert.Fail();
60+
}
61+
finally
62+
{
63+
Assert.Equal("here is an unhandled exception1", lastEx.Message);
64+
}
65+
});
66+
67+
th.Start();
68+
th.Join();
69+
70+
expectUnhandledException = true;
71+
InvokeCallbackOnNewThread(() => {
72+
try
73+
{
74+
lastEx = null;
75+
throw new Exception("here is an unhandled exception2");
76+
Assert.Fail();
77+
}
78+
finally
79+
{
80+
Assert.Null(lastEx);
81+
}
82+
});
83+
84+
Assert.Fail();
85+
}
86+
87+
public static int Main()
88+
{
89+
RunTest();
90+
91+
// should not reach here
92+
return 42;
93+
}
94+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<!-- Needed for CMakeProjectReference -->
4+
<RequiresProcessIsolation>true</RequiresProcessIsolation>
5+
<!-- Test requires EH-clean Main -->
6+
<ReferenceXUnitWrapperGenerator>false</ReferenceXUnitWrapperGenerator>
7+
<MonoAotIncompatible>true</MonoAotIncompatible>
8+
</PropertyGroup>
9+
<ItemGroup>
10+
<Compile Include="ForeignThreadRevPInvokeUnhandled.cs" />
11+
</ItemGroup>
12+
<ItemGroup>
13+
<CMakeProjectReference Include="CMakeLists.txt" />
14+
</ItemGroup>
15+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#include "stdio.h"
5+
#include <stdlib.h>
6+
7+
#ifdef _WIN32
8+
#pragma warning(push)
9+
#pragma warning(disable:4265 4577)
10+
#include <thread>
11+
#pragma warning(pop)
12+
#else // _WIN32
13+
#include <pthread.h>
14+
#endif // _WIN32
15+
16+
// Work around typedef redefinition: platformdefines.h defines error_t
17+
// as unsigned while it's defined as int in errno.h.
18+
#define error_t error_t_ignore
19+
#include <platformdefines.h>
20+
#undef error_t
21+
22+
typedef void (*PFNACTION1)();
23+
extern "C" DLL_EXPORT void InvokeCallback(PFNACTION1 callback)
24+
{
25+
callback();
26+
}
27+
28+
#ifndef _WIN32
29+
void* InvokeCallbackUnix(void* callback)
30+
{
31+
InvokeCallback((PFNACTION1)callback);
32+
return NULL;
33+
}
34+
35+
#define AbortIfFail(st) if (st != 0) abort()
36+
37+
#endif // !_WIN32
38+
39+
extern "C" DLL_EXPORT void InvokeCallbackOnNewThread(PFNACTION1 callback)
40+
{
41+
#ifdef _WIN32
42+
std::thread t1(InvokeCallback, callback);
43+
t1.join();
44+
#else // _WIN32
45+
// For Unix, we need to use pthreads to create the thread so that we can set its stack size.
46+
// We need to set the stack size due to the very small (80kB) default stack size on MUSL
47+
// based Linux distros.
48+
pthread_attr_t attr;
49+
int st = pthread_attr_init(&attr);
50+
AbortIfFail(st);
51+
52+
// set stack size to 1.5MB
53+
st = pthread_attr_setstacksize(&attr, 0x180000);
54+
AbortIfFail(st);
55+
56+
pthread_t t;
57+
st = pthread_create(&t, &attr, InvokeCallbackUnix, (void*)callback);
58+
AbortIfFail(st);
59+
60+
st = pthread_join(t, NULL);
61+
AbortIfFail(st);
62+
#endif // _WIN32
63+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
project (PInvokeRevPInvokeUnhandledNative)
2+
3+
include_directories(${INC_PLATFORM_DIR})
4+
5+
if(CLR_CMAKE_HOST_OSX)
6+
# Enable non-POSIX pthreads APIs, which by default are not included in the pthreads header
7+
add_definitions(-D_DARWIN_C_SOURCE)
8+
endif(CLR_CMAKE_HOST_OSX)
9+
10+
set(SOURCES PInvokeRevPInvokeUnhandledNative.cpp)
11+
12+
if(NOT CLR_CMAKE_HOST_WIN32)
13+
add_compile_options(-pthread)
14+
endif()
15+
16+
# add the executable
17+
add_library (PInvokeRevPInvokeUnhandledNative SHARED ${SOURCES})
18+
19+
# add the install targets
20+
install (TARGETS PInvokeRevPInvokeUnhandledNative DESTINATION bin)
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using System.Runtime.InteropServices;
8+
using Xunit;
9+
10+
public delegate void MyCallback();
11+
12+
public class PInvokeRevPInvokeUnhandled
13+
{
14+
[DllImport("PInvokeRevPInvokeUnhandledNative")]
15+
public static extern void InvokeCallback(MyCallback callback);
16+
17+
[DllImport("PInvokeRevPInvokeUnhandledNative")]
18+
public static extern void InvokeCallbackOnNewThread(MyCallback callback);
19+
20+
[ThreadStatic]
21+
private static Exception lastEx;
22+
private static bool expectUnhandledException = false;
23+
24+
private static bool Handler(Exception ex)
25+
{
26+
lastEx = ex;
27+
return true;
28+
}
29+
30+
private static void SetHandler()
31+
{
32+
System.Runtime.ExceptionServices.ExceptionHandling.SetUnhandledExceptionHandler(Handler);
33+
}
34+
35+
// test-wide setup
36+
static PInvokeRevPInvokeUnhandled()
37+
{
38+
AppDomain.CurrentDomain.UnhandledException += (_, _) =>
39+
{
40+
if (expectUnhandledException &&
41+
lastEx == null)
42+
{
43+
Environment.Exit(100);
44+
}
45+
};
46+
47+
SetHandler();
48+
}
49+
50+
public static void RunTest()
51+
{
52+
// sanity check, the handler should be working in a separate thread
53+
Thread th = new Thread(() =>
54+
{
55+
try
56+
{
57+
lastEx = null;
58+
throw new Exception("here is an unhandled exception1");
59+
Assert.Fail();
60+
}
61+
finally
62+
{
63+
Assert.Equal("here is an unhandled exception1", lastEx.Message);
64+
}
65+
});
66+
67+
th.Start();
68+
th.Join();
69+
70+
expectUnhandledException = true;
71+
InvokeCallback(() => {
72+
try
73+
{
74+
lastEx = null;
75+
throw new Exception("here is an unhandled exception2");
76+
Assert.Fail();
77+
}
78+
finally
79+
{
80+
Assert.Null(lastEx);
81+
}
82+
});
83+
84+
Assert.Fail();
85+
}
86+
87+
public static int Main()
88+
{
89+
RunTest();
90+
91+
// should not reach here
92+
return 42;
93+
}
94+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<!-- Needed for CMakeProjectReference -->
4+
<RequiresProcessIsolation>true</RequiresProcessIsolation>
5+
<!-- Test requires EH-clean Main -->
6+
<ReferenceXUnitWrapperGenerator>false</ReferenceXUnitWrapperGenerator>
7+
<MonoAotIncompatible>true</MonoAotIncompatible>
8+
</PropertyGroup>
9+
<ItemGroup>
10+
<Compile Include="PInvokeRevPInvokeUnhandled.cs" />
11+
</ItemGroup>
12+
<ItemGroup>
13+
<CMakeProjectReference Include="CMakeLists.txt" />
14+
</ItemGroup>
15+
</Project>
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#include "stdio.h"
5+
#include <stdlib.h>
6+
7+
#ifdef _WIN32
8+
#pragma warning(push)
9+
#pragma warning(disable:4265 4577)
10+
#include <thread>
11+
#pragma warning(pop)
12+
#else // _WIN32
13+
#include <pthread.h>
14+
#endif // _WIN32
15+
16+
// Work around typedef redefinition: platformdefines.h defines error_t
17+
// as unsigned while it's defined as int in errno.h.
18+
#define error_t error_t_ignore
19+
#include <platformdefines.h>
20+
#undef error_t
21+
22+
typedef void (*PFNACTION1)();
23+
extern "C" DLL_EXPORT void InvokeCallback(PFNACTION1 callback)
24+
{
25+
callback();
26+
}
27+
28+
#ifndef _WIN32
29+
void* InvokeCallbackUnix(void* callback)
30+
{
31+
InvokeCallback((PFNACTION1)callback);
32+
return NULL;
33+
}
34+
35+
#define AbortIfFail(st) if (st != 0) abort()
36+
37+
#endif // !_WIN32
38+
39+
extern "C" DLL_EXPORT void InvokeCallbackOnNewThread(PFNACTION1 callback)
40+
{
41+
#ifdef _WIN32
42+
std::thread t1(InvokeCallback, callback);
43+
t1.join();
44+
#else // _WIN32
45+
// For Unix, we need to use pthreads to create the thread so that we can set its stack size.
46+
// We need to set the stack size due to the very small (80kB) default stack size on MUSL
47+
// based Linux distros.
48+
pthread_attr_t attr;
49+
int st = pthread_attr_init(&attr);
50+
AbortIfFail(st);
51+
52+
// set stack size to 1.5MB
53+
st = pthread_attr_setstacksize(&attr, 0x180000);
54+
AbortIfFail(st);
55+
56+
pthread_t t;
57+
st = pthread_create(&t, &attr, InvokeCallbackUnix, (void*)callback);
58+
AbortIfFail(st);
59+
60+
st = pthread_join(t, NULL);
61+
AbortIfFail(st);
62+
#endif // _WIN32
63+
}

0 commit comments

Comments
 (0)