Skip to content

Commit c3f7c78

Browse files
committed
Add GetNativeThreadID method in PythonEngine and different PyThreadState_SetAsyncExc calls for OS and Python version
1 parent d075db3 commit c3f7c78

File tree

4 files changed

+71
-30
lines changed

4 files changed

+71
-30
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
1010
### Added
1111

1212
- Ability to instantiate new .NET arrays using `Array[T](dim1, dim2, ...)` syntax
13-
- Add Interrupt method in PythonEngine
13+
- Add GetNativeThreadID and Interrupt methods in PythonEngine
1414

1515
### Changed
1616
- Drop support for Python 2, 3.4, and 3.5

src/embed_tests/TestInterrupt.cs

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11

22
using System;
3-
using System.Runtime.InteropServices;
43
using System.Threading;
54
using System.Threading.Tasks;
65

@@ -14,15 +13,6 @@ public class TestInterrupt
1413
{
1514
private IntPtr _threadState;
1615

17-
[DllImport("Kernel32", EntryPoint = "GetCurrentThreadId", ExactSpelling = true)]
18-
private static extern uint GetCurrentThreadId();
19-
20-
[DllImport("libc", EntryPoint = "pthread_self")]
21-
private static extern IntPtr pthread_selfLinux();
22-
23-
[DllImport("pthread", EntryPoint = "pthread_self", CallingConvention = CallingConvention.Cdecl)]
24-
private static extern ulong pthread_selfOSX();
25-
2616
[OneTimeSetUp]
2717
public void SetUp()
2818
{
@@ -44,21 +34,9 @@ public void InterruptTest()
4434
ulong nativeThreadId = 0;
4535
Task.Factory.StartNew(() =>
4636
{
47-
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
48-
{
49-
nativeThreadId = GetCurrentThreadId();
50-
}
51-
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
52-
{
53-
nativeThreadId = (ulong)pthread_selfLinux();
54-
}
55-
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
56-
{
57-
nativeThreadId = pthread_selfOSX();
58-
}
59-
6037
using (Py.GIL())
6138
{
39+
nativeThreadId = PythonEngine.GetNativeThreadID();
6240
runSimpleStringReturnValue = PythonEngine.RunSimpleString(@"
6341
import time
6442
@@ -75,7 +53,7 @@ import time
7553
Assert.AreEqual(1, interruptReturnValue);
7654
}
7755

78-
Thread.Sleep(300);
56+
Thread.Sleep(500);
7957

8058
Assert.AreEqual(-1, runSimpleStringReturnValue);
8159
}

src/runtime/pythonengine.cs

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ namespace Python.Runtime
1212
/// </summary>
1313
public class PythonEngine : IDisposable
1414
{
15+
[DllImport("Kernel32", EntryPoint = "GetCurrentThreadId", ExactSpelling = true)]
16+
private static extern uint GetCurrentThreadId();
17+
18+
[DllImport("libc", EntryPoint = "pthread_self")]
19+
private static extern IntPtr pthread_selfLinux();
20+
21+
[DllImport("pthread", EntryPoint = "pthread_self", CallingConvention = CallingConvention.Cdecl)]
22+
private static extern ulong pthread_selfOSX();
23+
1524
public static ShutdownMode ShutdownMode
1625
{
1726
get => Runtime.ShutdownMode;
@@ -567,14 +576,59 @@ public static void Exec(string code, IntPtr? globals = null, IntPtr? locals = nu
567576
}
568577
}
569578

579+
/// <summary>
580+
/// Gets the native thread ID.
581+
/// </summary>
582+
/// <returns>The native thread ID.</returns>
583+
public static ulong GetNativeThreadID()
584+
{
585+
if (Runtime.PyVersion >= new Version(3, 8))
586+
{
587+
dynamic threading = Py.Import("threading");
588+
return threading.get_native_id();
589+
}
590+
591+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
592+
{
593+
return GetCurrentThreadId();
594+
}
595+
596+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
597+
{
598+
return (ulong)pthread_selfLinux();
599+
}
600+
601+
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
602+
{
603+
return pthread_selfOSX();
604+
}
605+
606+
return 0;
607+
}
608+
570609
/// <summary>
571610
/// Interrupts the execution of a thread.
572611
/// </summary>
573-
/// <param name="nativeThreadId">The native thread id.</param>
612+
/// <param name="nativeThreadID">The native thread ID.</param>
574613
/// <returns>The number of thread states modified; this is normally one, but will be zero if the thread id isn’t found.</returns>
575-
public static int Interrupt(ulong nativeThreadId)
614+
public static int Interrupt(ulong nativeThreadID)
576615
{
577-
return Runtime.PyThreadState_SetAsyncExc(nativeThreadId, Exceptions.KeyboardInterrupt);
616+
if (Runtime.PyVersion >= new Version(3, 7))
617+
{
618+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
619+
{
620+
return Runtime.PyThreadState_SetAsyncExc37Windows(nativeThreadID, Exceptions.KeyboardInterrupt);
621+
}
622+
623+
return Runtime.PyThreadState_SetAsyncExc37NonWindows((UIntPtr)nativeThreadID, Exceptions.KeyboardInterrupt);
624+
}
625+
626+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
627+
{
628+
return Runtime.PyThreadState_SetAsyncExc36Windows((long)nativeThreadID, Exceptions.KeyboardInterrupt);
629+
}
630+
631+
return Runtime.PyThreadState_SetAsyncExc36NonWindows((IntPtr)nativeThreadID, Exceptions.KeyboardInterrupt);
578632
}
579633

580634
/// <summary>

src/runtime/runtime.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2143,8 +2143,17 @@ internal static void Py_CLEAR(ref IntPtr ob)
21432143
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
21442144
internal static extern int Py_AddPendingCall(IntPtr func, IntPtr arg);
21452145

2146-
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
2147-
internal static extern int PyThreadState_SetAsyncExc(ulong id, IntPtr exc);
2146+
[DllImport(_PythonDll, EntryPoint = "PyThreadState_SetAsyncExc", CallingConvention = CallingConvention.Cdecl)]
2147+
internal static extern int PyThreadState_SetAsyncExc37Windows(ulong id, IntPtr exc);
2148+
2149+
[DllImport(_PythonDll, EntryPoint = "PyThreadState_SetAsyncExc", CallingConvention = CallingConvention.Cdecl)]
2150+
internal static extern int PyThreadState_SetAsyncExc36Windows(long id, IntPtr exc);
2151+
2152+
[DllImport(_PythonDll, EntryPoint = "PyThreadState_SetAsyncExc", CallingConvention = CallingConvention.Cdecl)]
2153+
internal static extern int PyThreadState_SetAsyncExc37NonWindows(UIntPtr id, IntPtr exc);
2154+
2155+
[DllImport(_PythonDll, EntryPoint = "PyThreadState_SetAsyncExc", CallingConvention = CallingConvention.Cdecl)]
2156+
internal static extern int PyThreadState_SetAsyncExc36NonWindows(IntPtr id, IntPtr exc);
21482157

21492158
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
21502159
internal static extern int Py_MakePendingCalls();

0 commit comments

Comments
 (0)