diff --git a/AUTHORS.md b/AUTHORS.md index 167fd496c..6cfa216b1 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -66,10 +66,10 @@ - Ville M. Vainio ([@vivainio](https://github.com/vivainio)) - Virgil Dupras ([@hsoft](https://github.com/hsoft)) - Wenguang Yang ([@yagweb](https://github.com/yagweb)) -- William Sardar ([@williamsardar])(https://github.com/williamsardar) +- William Sardar ([@williamsardar](https://github.com/williamsardar)) - Xavier Dupré ([@sdpython](https://github.com/sdpython)) - Zane Purvis ([@zanedp](https://github.com/zanedp)) -- ([@amos402]https://github.com/amos402) +- ([@amos402](https://github.com/amos402)) - ([@bltribble](https://github.com/bltribble)) - ([@civilx64](https://github.com/civilx64)) - ([@GSPP](https://github.com/GSPP)) @@ -82,3 +82,4 @@ - ([@testrunner123](https://github.com/testrunner123)) - ([@DanBarzilian](https://github.com/DanBarzilian)) - ([@alxnull](https://github.com/alxnull)) +- ([@gpetrou](https://github.com/gpetrou)) diff --git a/CHANGELOG.md b/CHANGELOG.md index 147a716b5..375071841 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Ability to instantiate new .NET arrays using `Array[T](dim1, dim2, ...)` syntax - Python operator method will call C# operator method for supported binary and unary operators ([#1324][p1324]). +- Add GetPythonThreadID and Interrupt methods in PythonEngine ### Changed - Drop support for Python 2, 3.4, and 3.5 diff --git a/src/embed_tests/TestInterrupt.cs b/src/embed_tests/TestInterrupt.cs new file mode 100644 index 000000000..e075d1194 --- /dev/null +++ b/src/embed_tests/TestInterrupt.cs @@ -0,0 +1,63 @@ + +using System; +using System.Threading; +using System.Threading.Tasks; + +using NUnit.Framework; + +using Python.Runtime; + +namespace Python.EmbeddingTest +{ + public class TestInterrupt + { + private IntPtr _threadState; + + [OneTimeSetUp] + public void SetUp() + { + PythonEngine.Initialize(); + _threadState = PythonEngine.BeginAllowThreads(); + } + + [OneTimeTearDown] + public void Dispose() + { + PythonEngine.EndAllowThreads(_threadState); + PythonEngine.Shutdown(); + } + + [Test] + public void InterruptTest() + { + int runSimpleStringReturnValue = int.MinValue; + ulong pythonThreadID = ulong.MinValue; + Task.Factory.StartNew(() => + { + using (Py.GIL()) + { + pythonThreadID = PythonEngine.GetPythonThreadID(); + runSimpleStringReturnValue = PythonEngine.RunSimpleString(@" +import time + +while True: + time.sleep(0.2)"); + } + }); + + Thread.Sleep(200); + + Assert.AreNotEqual(ulong.MinValue, pythonThreadID); + + using (Py.GIL()) + { + int interruptReturnValue = PythonEngine.Interrupt(pythonThreadID); + Assert.AreEqual(1, interruptReturnValue); + } + + Thread.Sleep(300); + + Assert.AreEqual(-1, runSimpleStringReturnValue); + } + } +} diff --git a/src/runtime/pythonengine.cs b/src/runtime/pythonengine.cs index df6cf7101..781d345e7 100644 --- a/src/runtime/pythonengine.cs +++ b/src/runtime/pythonengine.cs @@ -567,6 +567,30 @@ public static void Exec(string code, IntPtr? globals = null, IntPtr? locals = nu } } + /// + /// Gets the Python thread ID. + /// + /// The Python thread ID. + public static ulong GetPythonThreadID() + { + dynamic threading = Py.Import("threading"); + return threading.InvokeMethod("get_ident"); + } + + /// + /// Interrupts the execution of a thread. + /// + /// The Python thread ID. + /// The number of thread states modified; this is normally one, but will be zero if the thread id isn’t found. + public static int Interrupt(ulong pythonThreadID) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return Runtime.PyThreadState_SetAsyncExcLLP64((uint)pythonThreadID, Exceptions.KeyboardInterrupt); + } + + return Runtime.PyThreadState_SetAsyncExcLP64(pythonThreadID, Exceptions.KeyboardInterrupt); + } /// /// RunString Method. Function has been deprecated and will be removed. diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 2454c038f..8197f027b 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -2143,6 +2143,12 @@ internal static void Py_CLEAR(ref IntPtr ob) [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int Py_AddPendingCall(IntPtr func, IntPtr arg); + [DllImport(_PythonDll, EntryPoint = "PyThreadState_SetAsyncExc", CallingConvention = CallingConvention.Cdecl)] + internal static extern int PyThreadState_SetAsyncExcLLP64(uint id, IntPtr exc); + + [DllImport(_PythonDll, EntryPoint = "PyThreadState_SetAsyncExc", CallingConvention = CallingConvention.Cdecl)] + internal static extern int PyThreadState_SetAsyncExcLP64(ulong id, IntPtr exc); + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern int Py_MakePendingCalls();