FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

GitHub Viewer

using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.InteropServices; using NUnit.Framework; using Python.Runtime; using PyRuntime = Python.Runtime.Runtime; // // This test case is disabled on .NET Standard because it doesn't have all the // APIs we use. We could work around that, but .NET Core doesn't implement // domain creation, so it's not worth it. // // Unfortunately this means no continuous integration testing for this case. // #if NETFRAMEWORK namespace Python.EmbeddingTest { class TestDomainReload { abstract class CrossCaller : MarshalByRefObject { public abstract ValueType Execute(ValueType arg); } /// /// Test that the python runtime can survive a C# domain reload without crashing. /// /// At the time this test was written, there was a very annoying /// seemingly random crash bug when integrating pythonnet into Unity. /// /// The repro steps that David Lassonde, Viktoria Kovecses and /// Benoit Hudson eventually worked out: /// 1. Write a HelloWorld.cs script that uses Python.Runtime to access /// some C# data from python: C# calls python, which calls C#. /// 2. Execute the script (e.g. make it a MenuItem and click it). /// 3. Touch HelloWorld.cs on disk, forcing Unity to recompile scripts. /// 4. Wait several seconds for Unity to be done recompiling and /// reloading the C# domain. /// 5. Make python run the gc (e.g. by calling gc.collect()). /// /// The reason: /// A. In step 2, Python.Runtime registers a bunch of new types with /// their tp_traverse slot pointing to managed code, and allocates /// some objects of those types. /// B. In step 4, Unity unloads the C# domain. That frees the managed /// code. But at the time of the crash investigation, pythonnet /// leaked the python side of the objects allocated in step 1. /// C. In step 5, python sees some pythonnet objects in its gc list of /// potentially-leaked objects. It calls tp_traverse on those objects. /// But tp_traverse was freed in step 3 => CRASH. /// /// This test distills what's going on without needing Unity around (we'd see /// similar behaviour if we were using pythonnet on a .NET web server that did /// a hot reload). /// [Test] public static void DomainReloadAndGC() { Assert.IsFalse(PythonEngine.IsInitialized); RunAssemblyAndUnload("test1"); Assert.That(PyRuntime.Py_IsInitialized() != 0, "On soft-shutdown mode, Python runtime should still running"); RunAssemblyAndUnload("test2"); Assert.That(PyRuntime.Py_IsInitialized() != 0, "On soft-shutdown mode, Python runtime should still running"); if (PythonEngine.DefaultShutdownMode == ShutdownMode.Normal) { // The default mode is a normal mode, // it should shutdown the Python VM avoiding influence other tests. PyRuntime.PyGILState_Ensure(); PyRuntime.Py_Finalize(); } } #region CrossDomainObject class CrossDomainObjectStep1 : CrossCaller { public override ValueType Execute(ValueType arg) { try { // Create a C# user-defined object in Python. Asssing some values. Type type = typeof(Python.EmbeddingTest.Domain.MyClass); string code = string.Format(@" import clr clr.AddReference('{0}') from Python.EmbeddingTest.Domain import MyClass obj = MyClass() obj.Method() obj.StaticMethod() obj.Property = 1 obj.Field = 10 ", Assembly.GetExecutingAssembly().FullName); using (Py.GIL()) using (var scope = Py.CreateScope()) { scope.Exec(code); using (PyObject obj = scope.Get("obj")) { Debug.Assert(obj.AsManagedObject(type).GetType() == type); // We only needs its Python handle PyRuntime.XIncref(obj.Handle); return obj.Handle; } } } catch (Exception e) { Debug.WriteLine(e); throw; } } } class CrossDomainObjectStep2 : CrossCaller { public override ValueType Execute(ValueType arg) { // handle refering a clr object created in previous domain, // it should had been deserialized and became callable agian. IntPtr handle = (IntPtr)arg; try { using (Py.GIL()) { IntPtr tp = Runtime.Runtime.PyObject_TYPE(handle); IntPtr tp_clear = Marshal.ReadIntPtr(tp, TypeOffset.tp_clear); Assert.That(tp_clear, Is.Not.Null); using (PyObject obj = new PyObject(handle)) { obj.InvokeMethod("Method"); obj.InvokeMethod("StaticMethod"); using (var scope = Py.CreateScope()) { scope.Set("obj", obj); scope.Exec(@" obj.Method() obj.StaticMethod() obj.Property += 1 obj.Field += 10 "); } var clrObj = obj.As(); Assert.AreEqual(clrObj.Property, 2); Assert.AreEqual(clrObj.Field, 20); } } } catch (Exception e) { Debug.WriteLine(e); throw; } return 0; } } /// /// Create a C# custom object in a domain, in python code. /// Unload the domain, create a new domain. /// Make sure the C# custom object created in the previous domain has been re-created /// [Test] public static void CrossDomainObject() { RunDomainReloadSteps(); } #endregion #region Tempary tests // https://github.com/pythonnet/pythonnet/pull/1074#issuecomment-596139665 [Test] public void CrossReleaseBuiltinType() { void ExecTest() { try { PythonEngine.Initialize(); var numRef = CreateNumReference(); Assert.True(numRef.IsAlive); PythonEngine.Shutdown(); //

Back | FazBrowse Home | New Git URL