-
Notifications
You must be signed in to change notification settings - Fork 772
Add soft shutdown #958
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add soft shutdown #958
Changes from 1 commit
d1928dc
f000e08
f882400
b7715ee
41ac665
c6dae9e
4e19a4f
657452e
91f64b9
b07b844
7db724e
2a88be4
2940973
d108913
cc9e7e5
1cb8e8c
653a263
b3e889b
04d6dfb
5150e61
65e209e
cad95da
0dee5da
00a0b32
77da6df
2039e69
fe5050d
593fb00
1ce83fc
fba616a
bdc0f72
49d98e8
433d0f6
9b4864b
1b466df
49130c4
76ba510
1ff21ac
0b01378
631bb43
bf3d9f8
da97502
e8b3160
80d4fa0
9874cd1
8da561b
9499c64
431d644
2b84394
5ade069
8dabed7
f4bb77a
670bd74
3cb56f1
3c9a83c
992c469
924b217
df84e29
8b51621
97c8c2a
39f47c8
aa63f0b
5b2f3d4
35cbe55
f23cae6
183f9d8
9d57a82
08fad26
bcfdcc7
66ab719
1428af3
8e3c028
b387e9e
f85999e
cb65af3
498fc8c
cc2219e
8c8d66e
4f00165
39e20e3
da7c150
02b1ada
a8840b2
dec7a74
e877b33
6d738bf
ff5edc3
5ac75ba
65cb22e
73865d4
1a75f51
9b6d140
d9d5562
9b62a61
4f0420e
4ab9f1c
32bcb3a
0077ea8
38ea0b6
06a656e
09f8281
7ec9a6c
802a43a
0fdf969
3a8c72d
b52bc01
bfbf2c3
883c4ce
7e0d56d
82034dc
4ba50a7
1ecdce8
b35f441
ce8ee90
4c4bcb0
f575bd3
d1799aa
d2408b9
fd2b662
2b7bcac
0af3504
ba1df6e
639ba1f
7e5ab52
8075f48
eb8cb8a
308f0f2
1ae0bfe
07aefe6
5387b05
b203674
19d1379
25a4064
1272b89
8c133e3
d9b21a5
c8dee53
598cb77
83e8dd5
6db3181
e38a363
b409a89
f5c24b0
0d6c645
fa89b48
80a7644
12c0206
6a3cfc8
98da1fa
d7d44e8
6aa75c5
2343f89
cc6b8e4
d5fcfa4
fa47957
ff956e4
c7b134c
97e61a5
0b9d2c1
178cbc8
3a17f36
3203457
8d00e4c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ | |
| 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 | ||
|
|
@@ -17,6 +18,12 @@ namespace Python.EmbeddingTest | |
| { | ||
| class TestDomainReload | ||
| { | ||
| abstract class CrossCaller : MarshalByRefObject | ||
| { | ||
| public abstract ValueType Execte(ValueType arg); | ||
| } | ||
|
|
||
|
|
||
| /// <summary> | ||
| /// Test that the python runtime can survive a C# domain reload without crashing. | ||
| /// | ||
|
|
@@ -53,71 +60,198 @@ public static void DomainReloadAndGC() | |
| { | ||
| Assert.IsFalse(PythonEngine.IsInitialized); | ||
| RunAssemblyAndUnload("test1"); | ||
| Assert.That(Runtime.Runtime.Py_IsInitialized() != 0, | ||
| Assert.That(PyRuntime.Py_IsInitialized() != 0, | ||
| "On soft-shutdown mode, Python runtime should still running"); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about a test for the regular shutdown mode? |
||
|
|
||
| RunAssemblyAndUnload("test2"); | ||
| Assert.That(Runtime.Runtime.Py_IsInitialized() != 0, | ||
| 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. | ||
| Runtime.Runtime.PyGILState_Ensure(); | ||
| Runtime.Runtime.Py_Finalize(); | ||
| PyRuntime.PyGILState_Ensure(); | ||
| PyRuntime.Py_Finalize(); | ||
| } | ||
| } | ||
|
|
||
| [Test] | ||
| public static void CrossDomainObject() | ||
| #region CrossDomainObject | ||
|
|
||
| class CrossDomianObjectStep1 : CrossCaller | ||
| { | ||
| IntPtr handle = IntPtr.Zero; | ||
| Type type = typeof(Proxy); | ||
| public override ValueType Execte(ValueType arg) | ||
|
amos402 marked this conversation as resolved.
Outdated
|
||
| { | ||
| AppDomain domain = CreateDomain("test_domain_reload"); | ||
| try | ||
| { | ||
| var theProxy = (Proxy)domain.CreateInstanceAndUnwrap( | ||
| type.Assembly.FullName, | ||
| type.FullName); | ||
| theProxy.Call("InitPython", ShutdownMode.Reload); | ||
| handle = (IntPtr)theProxy.Call("GetTestObject"); | ||
| theProxy.Call("ShutdownPython"); | ||
| 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; | ||
|
lostmsu marked this conversation as resolved.
Comment on lines
+86
to
+111
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a comment explaining the intent of this method or extract this code into a separate method with clear name. |
||
| } | ||
| } | ||
| } | ||
| finally | ||
| catch (Exception e) | ||
| { | ||
| AppDomain.Unload(domain); | ||
| Debug.WriteLine(e); | ||
| throw; | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
||
| class CrossDomianObjectStep2 : CrossCaller | ||
|
amos402 marked this conversation as resolved.
Outdated
|
||
| { | ||
| public override ValueType Execte(ValueType arg) | ||
| { | ||
| AppDomain domain = CreateDomain("test_domain_reload"); | ||
| // handle refering a clr object created in previous domain, | ||
| // it should had been deserialized and became callable agian. | ||
| IntPtr handle = (IntPtr)arg; | ||
| try | ||
| { | ||
| var theProxy = (Proxy)domain.CreateInstanceAndUnwrap( | ||
| type.Assembly.FullName, | ||
| type.FullName); | ||
| theProxy.Call("InitPython", ShutdownMode.Reload); | ||
| using (Py.GIL()) | ||
| { | ||
| IntPtr tp = Runtime.Runtime.PyObject_TYPE(handle); | ||
| IntPtr tp_clear = Marshal.ReadIntPtr(tp, TypeOffset.tp_clear); | ||
|
Comment on lines
+135
to
+136
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These two are not used anywhere. Looks like they are for debugging only.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah ha, I were trying to assert their slots, but I forgot to fill with it. |
||
|
|
||
| // handle refering a clr object created in previous domain, | ||
| // it should had been deserialized and became callable agian. | ||
| theProxy.Call("RunTestObject", handle); | ||
| theProxy.Call("ShutdownPythonCompletely"); | ||
| 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<Domain.MyClass>(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| Assert.AreEqual(clrObj.Property, 2); | ||
| Assert.AreEqual(clrObj.Field, 20); | ||
| } | ||
| } | ||
| } | ||
| finally | ||
| catch (Exception e) | ||
| { | ||
| AppDomain.Unload(domain); | ||
| Debug.WriteLine(e); | ||
| throw; | ||
| } | ||
| return 0; | ||
| } | ||
| if (PythonEngine.DefaultShutdownMode == ShutdownMode.Normal) | ||
| } | ||
|
|
||
| [Test] | ||
| public static void CrossDomainObject() | ||
| { | ||
| RunDomainReloadSteps<CrossDomianObjectStep1, CrossDomianObjectStep2>(); | ||
| } | ||
|
|
||
| #endregion | ||
|
|
||
| #region TestClassReference | ||
|
|
||
| class ReloadClassRefStep1 : CrossCaller | ||
| { | ||
| public override ValueType Execte(ValueType arg) | ||
| { | ||
| const string code = @" | ||
| from Python.EmbeddingTest.Domain import MyClass | ||
|
|
||
| def test_obj_call(): | ||
| obj = MyClass() | ||
| obj.Method() | ||
| obj.StaticMethod() | ||
| obj.Property = 1 | ||
| obj.Field = 10 | ||
|
|
||
| test_obj_call() | ||
| "; | ||
| const string name = "test_domain_reload_mod"; | ||
| using (Py.GIL()) | ||
| { | ||
| IntPtr module = PyRuntime.PyModule_New(name); | ||
| Assert.That(module != IntPtr.Zero); | ||
| IntPtr globals = PyRuntime.PyObject_GetAttrString(module, "__dict__"); | ||
| Assert.That(globals != IntPtr.Zero); | ||
| try | ||
| { | ||
| int res = PyRuntime.PyDict_SetItemString(globals, "__builtins__", | ||
| PyRuntime.PyEval_GetBuiltins()); | ||
| PythonException.ThrowIfIsNotZero(res); | ||
|
|
||
| PythonEngine.Exec(code, globals); | ||
| IntPtr modules = PyRuntime.PyImport_GetModuleDict(); | ||
| res = PyRuntime.PyDict_SetItemString(modules, name, modules); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps this was meant to be
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. BTW, it would be helpful to add comments with equivalent Python code like this: // modules[name] = module
res = PyRuntime.PyDict_SetItemString(modules, name, modules);Or just run this entire code block in Python itself.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It should be |
||
| PythonException.ThrowIfIsNotZero(res); | ||
| } | ||
| catch | ||
| { | ||
| PyRuntime.XDecref(module); | ||
|
lostmsu marked this conversation as resolved.
|
||
| throw; | ||
| } | ||
| finally | ||
| { | ||
| PyRuntime.XDecref(globals); | ||
| } | ||
| return module; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| class ReloadClassRefStep2 : CrossCaller | ||
| { | ||
| public override ValueType Execte(ValueType arg) | ||
| { | ||
| Assert.IsTrue(Runtime.Runtime.Py_IsInitialized() == 0); | ||
| var module = (IntPtr)arg; | ||
| using (Py.GIL()) | ||
| { | ||
| var test_obj_call = PyRuntime.PyObject_GetAttrString(module, "test_obj_call"); | ||
| PythonException.ThrowIfIsNull(test_obj_call); | ||
| var args = PyRuntime.PyTuple_New(0); | ||
| var res = PyRuntime.PyObject_CallObject(test_obj_call, args); | ||
| PythonException.ThrowIfIsNull(res); | ||
|
|
||
| PyRuntime.XDecref(args); | ||
| PyRuntime.XDecref(res); | ||
|
lostmsu marked this conversation as resolved.
|
||
| } | ||
| return 0; | ||
| } | ||
| } | ||
|
|
||
|
|
||
| [Test] | ||
| public void TestClassReference() | ||
| { | ||
| RunDomainReloadSteps<ReloadClassRefStep1, ReloadClassRefStep2>(); | ||
| } | ||
|
|
||
| #endregion | ||
|
|
||
| #region Tempary tests | ||
|
|
||
| // https://github.com/pythonnet/pythonnet/pull/1074#issuecomment-596139665 | ||
| [Test] | ||
| public void CrossReleaseBuiltinType() | ||
|
|
@@ -274,6 +408,58 @@ static Assembly ResolveAssembly(object sender, ResolveEventArgs args) | |
|
|
||
| return null; | ||
| } | ||
|
|
||
| static void RunDomainReloadSteps<T1, T2>() where T1 : CrossCaller where T2 : CrossCaller | ||
| { | ||
| ValueType arg = null; | ||
| Type type = typeof(Proxy); | ||
| { | ||
| AppDomain domain = CreateDomain("test_domain_reload"); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe add a number 1 or 2 to the domain name here and in the next |
||
| try | ||
| { | ||
| var theProxy = (Proxy)domain.CreateInstanceAndUnwrap( | ||
| type.Assembly.FullName, | ||
| type.FullName); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A helper method |
||
| theProxy.Call("InitPython", ShutdownMode.Reload); | ||
|
|
||
| var caller = (T1)domain.CreateInstanceAndUnwrap( | ||
| typeof(T1).Assembly.FullName, | ||
| typeof(T1).FullName); | ||
| arg = caller.Execte(arg); | ||
|
|
||
| theProxy.Call("ShutdownPython"); | ||
| } | ||
| finally | ||
| { | ||
| AppDomain.Unload(domain); | ||
| } | ||
| } | ||
|
|
||
| { | ||
| AppDomain domain = CreateDomain("test_domain_reload"); | ||
| try | ||
| { | ||
| var theProxy = (Proxy)domain.CreateInstanceAndUnwrap( | ||
| type.Assembly.FullName, | ||
| type.FullName); | ||
| theProxy.Call("InitPython", ShutdownMode.Reload); | ||
|
|
||
| var caller = (T2)domain.CreateInstanceAndUnwrap( | ||
| typeof(T2).Assembly.FullName, | ||
| typeof(T2).FullName); | ||
| caller.Execte(arg); | ||
| theProxy.Call("ShutdownPythonCompletely"); | ||
| } | ||
| finally | ||
| { | ||
| AppDomain.Unload(domain); | ||
| } | ||
| } | ||
| if (PythonEngine.DefaultShutdownMode == ShutdownMode.Normal) | ||
| { | ||
| Assert.IsTrue(PyRuntime.Py_IsInitialized() == 0); | ||
| } | ||
|
Comment on lines
+523
to
+526
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this belongs in the caller. |
||
| } | ||
| } | ||
|
|
||
|
|
||
|
|
@@ -358,88 +544,6 @@ public static void ShutdownPythonCompletely() | |
| PythonEngine.Shutdown(); | ||
| } | ||
|
|
||
| public static IntPtr GetTestObject() | ||
| { | ||
| try | ||
| { | ||
| 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 | ||
| Runtime.Runtime.XIncref(obj.Handle); | ||
| return obj.Handle; | ||
| } | ||
| } | ||
| } | ||
| catch (Exception e) | ||
| { | ||
| Debug.WriteLine(e); | ||
| throw; | ||
| } | ||
| } | ||
|
|
||
| public static void RunTestObject(IntPtr handle) | ||
| { | ||
| try | ||
| { | ||
| using (Py.GIL()) | ||
| { | ||
| IntPtr tp = Runtime.Runtime.PyObject_TYPE(handle); | ||
| IntPtr tp_clear = Marshal.ReadIntPtr(tp, TypeOffset.tp_clear); | ||
|
|
||
| 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<Domain.MyClass>(); | ||
| Assert.AreEqual(clrObj.Property, 2); | ||
| Assert.AreEqual(clrObj.Field, 20); | ||
| } | ||
| } | ||
| } | ||
| catch (Exception e) | ||
| { | ||
| Debug.WriteLine(e); | ||
| throw; | ||
| } | ||
| } | ||
|
|
||
| public static void ReleaseTestObject(IntPtr handle) | ||
| { | ||
| using (Py.GIL()) | ||
| { | ||
| Runtime.Runtime.XDecref(handle); | ||
| } | ||
| } | ||
|
|
||
| static void OnDomainUnload(object sender, EventArgs e) | ||
| { | ||
| Console.WriteLine(string.Format("[{0} in .NET] unloading", AppDomain.CurrentDomain.FriendlyName)); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.