X Tutup
Skip to content
This repository was archived by the owner on Jul 22, 2023. It is now read-only.

Commit 80d4fa0

Browse files
committed
* Serialize CLRObject
* Domain reload test
1 parent e8b3160 commit 80d4fa0

File tree

11 files changed

+298
-87
lines changed

11 files changed

+298
-87
lines changed

src/embed_tests/DomainCode.cs

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
using Python.Runtime;
2+
using System;
3+
using System.Diagnostics;
4+
using System.Reflection;
5+
6+
//
7+
// The code we'll test. All that really matters is
8+
// using GIL { Python.Exec(pyScript); }
9+
// but the rest is useful for debugging.
10+
//
11+
// What matters in the python code is gc.collect and clr.AddReference.
12+
//
13+
// Note that the language version is 2.0, so no $"foo{bar}" syntax.
14+
//
15+
static class PythonRunner
16+
{
17+
static readonly Action<IntPtr> XIncref;
18+
static readonly Action<IntPtr> XDecref;
19+
20+
static PythonRunner()
21+
{
22+
const BindingFlags flags = BindingFlags.Static | BindingFlags.NonPublic;
23+
MethodInfo incMethod = typeof(Runtime).GetMethod("XIncref", flags);
24+
MethodInfo decMethod = typeof(Runtime).GetMethod("XDecref", flags);
25+
26+
XIncref = (Action<IntPtr>)Delegate.CreateDelegate(typeof(Action<IntPtr>), incMethod);
27+
XDecref = (Action<IntPtr>)Delegate.CreateDelegate(typeof(Action<IntPtr>), decMethod);
28+
}
29+
30+
public static void RunPython()
31+
{
32+
AppDomain.CurrentDomain.DomainUnload += OnDomainUnload;
33+
string name = AppDomain.CurrentDomain.FriendlyName;
34+
Console.WriteLine(string.Format("[{0} in .NET] In PythonRunner.RunPython", name));
35+
PythonEngine.Initialize(mode: ShutdownMode.Reload);
36+
using (Py.GIL())
37+
{
38+
try
39+
{
40+
var pyScript = string.Format("import clr\n"
41+
+ "print('[{0} in python] imported clr')\n"
42+
+ "clr.AddReference('System')\n"
43+
+ "print('[{0} in python] allocated a clr object')\n"
44+
+ "import gc\n"
45+
+ "gc.collect()\n"
46+
+ "print('[{0} in python] collected garbage')\n",
47+
name);
48+
PythonEngine.Exec(pyScript);
49+
}
50+
catch (Exception e)
51+
{
52+
Console.WriteLine(string.Format("[{0} in .NET] Caught exception: {1}", name, e));
53+
}
54+
}
55+
PythonEngine.BeginAllowThreads();
56+
}
57+
58+
59+
private static IntPtr _state;
60+
61+
public static void InitPython(ShutdownMode mode)
62+
{
63+
PythonEngine.Initialize(mode: mode);
64+
_state = PythonEngine.BeginAllowThreads();
65+
}
66+
67+
public static void ShutdownPython()
68+
{
69+
PythonEngine.EndAllowThreads(_state);
70+
PythonEngine.Shutdown();
71+
}
72+
73+
public static void ShutdownPythonCompletely()
74+
{
75+
PythonEngine.EndAllowThreads(_state);
76+
PythonEngine.ShutdownMode = ShutdownMode.Normal;
77+
PythonEngine.Shutdown();
78+
}
79+
80+
public static IntPtr GetTestObject()
81+
{
82+
try
83+
{
84+
Type type = typeof(Python.EmbeddingTest.Domain.MyClass);
85+
string code = string.Format(@"
86+
import clr
87+
clr.AddReference('{0}')
88+
89+
from Python.EmbeddingTest.Domain import MyClass
90+
obj = MyClass()
91+
obj.Method()
92+
obj.StaticMethod()
93+
", Assembly.GetExecutingAssembly().FullName);
94+
95+
using (Py.GIL())
96+
using (var scope = Py.CreateScope())
97+
{
98+
scope.Exec(code);
99+
using (PyObject obj = scope.Get("obj"))
100+
{
101+
Debug.Assert(obj.AsManagedObject(type).GetType() == type);
102+
// We only needs its Python handle
103+
XIncref(obj.Handle);
104+
return obj.Handle;
105+
}
106+
}
107+
}
108+
catch (Exception e)
109+
{
110+
Debug.WriteLine(e);
111+
throw;
112+
}
113+
}
114+
115+
public static void RunTestObject(IntPtr handle)
116+
{
117+
using (Py.GIL())
118+
{
119+
120+
using (PyObject obj = new PyObject(handle))
121+
{
122+
obj.InvokeMethod("Method");
123+
obj.InvokeMethod("StaticMethod");
124+
}
125+
}
126+
}
127+
128+
public static void ReleaseTestObject(IntPtr handle)
129+
{
130+
using (Py.GIL())
131+
{
132+
XDecref(handle);
133+
}
134+
}
135+
136+
static void OnDomainUnload(object sender, EventArgs e)
137+
{
138+
Console.WriteLine(string.Format("[{0} in .NET] unloading", AppDomain.CurrentDomain.FriendlyName));
139+
}
140+
}
141+
142+
143+
namespace Python.EmbeddingTest.Domain
144+
{
145+
[Serializable]
146+
public class MyClass
147+
{
148+
public void Method() { }
149+
public static void StaticMethod() { }
150+
}
151+
}

src/embed_tests/Python.EmbeddingTest.15.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,13 @@
6565
<PropertyGroup Condition="$(Configuration.Contains('Release'))">
6666
<DefineConstants Condition="'$(CustomDefineConstants)' == ''">$(DefineConstants)</DefineConstants>
6767
</PropertyGroup>
68+
<ItemGroup>
69+
<Compile Remove="DomainCode.cs" />
70+
</ItemGroup>
6871

6972
<ItemGroup>
7073
<None Include="..\pythonnet.snk" />
74+
<None Include="DomainCode.cs" />
7175
<None Remove="packages.config" />
7276
</ItemGroup>
7377

src/embed_tests/TestDomainReload.cs

Lines changed: 75 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
using System;
22
using System.CodeDom.Compiler;
3+
using System.Collections.Generic;
4+
using System.Diagnostics;
5+
using System.IO;
6+
using System.Linq;
37
using System.Reflection;
48
using NUnit.Framework;
59
using Python.Runtime;
@@ -68,46 +72,40 @@ public static void DomainReloadAndGC()
6872
PythonEngine.Shutdown();
6973
}
7074

71-
//
72-
// The code we'll test. All that really matters is
73-
// using GIL { Python.Exec(pyScript); }
74-
// but the rest is useful for debugging.
75-
//
76-
// What matters in the python code is gc.collect and clr.AddReference.
77-
//
78-
// Note that the language version is 2.0, so no $"foo{bar}" syntax.
79-
//
80-
const string TestCode = @"
81-
using Python.Runtime;
82-
using System;
83-
class PythonRunner {
84-
public static void RunPython() {
85-
AppDomain.CurrentDomain.DomainUnload += OnDomainUnload;
86-
string name = AppDomain.CurrentDomain.FriendlyName;
87-
Console.WriteLine(string.Format(""[{0} in .NET] In PythonRunner.RunPython"", name));
88-
PythonEngine.Initialize(mode: ShutdownMode.Reload);
89-
using (Py.GIL()) {
90-
try {
91-
var pyScript = string.Format(""import clr\n""
92-
+ ""print('[{0} in python] imported clr')\n""
93-
+ ""clr.AddReference('System')\n""
94-
+ ""print('[{0} in python] allocated a clr object')\n""
95-
+ ""import gc\n""
96-
+ ""gc.collect()\n""
97-
+ ""print('[{0} in python] collected garbage')\n"",
98-
name);
99-
PythonEngine.Exec(pyScript);
100-
} catch(Exception e) {
101-
Console.WriteLine(string.Format(""[{0} in .NET] Caught exception: {1}"", name, e));
102-
}
103-
}
104-
PythonEngine.BeginAllowThreads();
105-
}
106-
static void OnDomainUnload(object sender, EventArgs e) {
107-
System.Console.WriteLine(string.Format(""[{0} in .NET] unloading"", AppDomain.CurrentDomain.FriendlyName));
108-
}
109-
}";
75+
[Test]
76+
public static void CrossDomainObject()
77+
{
78+
IntPtr handle;
79+
Type type = typeof(Proxy);
80+
Assembly assembly = BuildAssembly("test_domain_reload");
81+
{
82+
AppDomain domain = CreateDomain("test_domain_reload");
83+
var theProxy = (Proxy)domain.CreateInstanceAndUnwrap(
84+
type.Assembly.FullName,
85+
type.FullName);
86+
theProxy.InitAssembly(assembly.Location);
87+
theProxy.Call("InitPython", ShutdownMode.Reload);
88+
handle = (IntPtr)theProxy.Call("GetTestObject");
89+
theProxy.Call("ShutdownPython");
90+
AppDomain.Unload(domain);
91+
}
11092

93+
{
94+
AppDomain domain = CreateDomain("test_domain_reload");
95+
var theProxy = (Proxy)domain.CreateInstanceAndUnwrap(
96+
type.Assembly.FullName,
97+
type.FullName);
98+
theProxy.InitAssembly(assembly.Location);
99+
theProxy.Call("InitPython", ShutdownMode.Reload);
100+
101+
// handle refering a clr object created in previous domain,
102+
// it should had been deserialized and became callable agian.
103+
theProxy.Call("RunTestObject", handle);
104+
theProxy.Call("ShutdownPythonCompletely");
105+
AppDomain.Unload(domain);
106+
}
107+
Assert.IsTrue(Runtime.Runtime.Py_IsInitialized() == 0);
108+
}
111109

112110
/// <summary>
113111
/// Build an assembly out of the source code above.
@@ -119,15 +117,19 @@ static void OnDomainUnload(object sender, EventArgs e) {
119117
static Assembly BuildAssembly(string assemblyName)
120118
{
121119
var provider = CodeDomProvider.CreateProvider("CSharp");
122-
123120
var compilerparams = new CompilerParameters();
124-
compilerparams.ReferencedAssemblies.Add("Python.Runtime.dll");
121+
var assemblies = from assembly in AppDomain.CurrentDomain.GetAssemblies()
122+
where !assembly.IsDynamic
123+
select assembly.Location;
124+
compilerparams.ReferencedAssemblies.AddRange(assemblies.ToArray());
125+
125126
compilerparams.GenerateExecutable = false;
126127
compilerparams.GenerateInMemory = false;
127-
compilerparams.IncludeDebugInformation = false;
128+
compilerparams.IncludeDebugInformation = true;
128129
compilerparams.OutputAssembly = assemblyName;
129130

130-
var results = provider.CompileAssemblyFromSource(compilerparams, TestCode);
131+
var dir = Path.GetDirectoryName(new StackTrace(true).GetFrame(0).GetFileName());
132+
var results = provider.CompileAssemblyFromFile(compilerparams, Path.Combine(dir, "DomainCode.cs"));
131133
if (results.Errors.HasErrors)
132134
{
133135
var errors = new System.Text.StringBuilder("Compiler Errors:\n");
@@ -168,6 +170,13 @@ public void RunPython()
168170

169171
Console.WriteLine("[Proxy] Leaving RunPython");
170172
}
173+
174+
public object Call(string methodName, params object[] args)
175+
{
176+
var pythonrunner = theAssembly.GetType("PythonRunner");
177+
var method = pythonrunner.GetMethod(methodName);
178+
return method.Invoke(null, args);
179+
}
171180
}
172181

173182
/// <summary>
@@ -178,26 +187,11 @@ static void RunAssemblyAndUnload(Assembly assembly, string assemblyName)
178187
{
179188
Console.WriteLine($"[Program.Main] === creating domain for assembly {assembly.FullName}");
180189

181-
// Create the domain. Make sure to set PrivateBinPath to a relative
182-
// path from the CWD (namely, 'bin').
183-
// See https://stackoverflow.com/questions/24760543/createinstanceandunwrap-in-another-domain
184-
var currentDomain = AppDomain.CurrentDomain;
185-
var domainsetup = new AppDomainSetup()
186-
{
187-
ApplicationBase = currentDomain.SetupInformation.ApplicationBase,
188-
ConfigurationFile = currentDomain.SetupInformation.ConfigurationFile,
189-
LoaderOptimization = LoaderOptimization.SingleDomain,
190-
PrivateBinPath = "."
191-
};
192-
var domain = AppDomain.CreateDomain(
193-
$"My Domain {assemblyName}",
194-
currentDomain.Evidence,
195-
domainsetup);
196-
190+
AppDomain domain = CreateDomain(assemblyName);
197191
// Create a Proxy object in the new domain, where we want the
198192
// assembly (and Python .NET) to reside
199-
Type type = typeof(Proxy);
200193
System.IO.Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory);
194+
Type type = typeof(Proxy);
201195
var theProxy = (Proxy)domain.CreateInstanceAndUnwrap(
202196
type.Assembly.FullName,
203197
type.FullName);
@@ -214,13 +208,34 @@ static void RunAssemblyAndUnload(Assembly assembly, string assemblyName)
214208
try
215209
{
216210
Console.WriteLine($"[Program.Main] The Proxy object is valid ({theProxy}). Unexpected domain unload behavior");
211+
Assert.Fail($"{theProxy} should be invlaid now");
217212
}
218213
catch (AppDomainUnloadedException)
219214
{
220215
Console.WriteLine("[Program.Main] The Proxy object is not valid anymore, domain unload complete.");
221216
}
222217
}
223218

219+
private static AppDomain CreateDomain(string name)
220+
{
221+
// Create the domain. Make sure to set PrivateBinPath to a relative
222+
// path from the CWD (namely, 'bin').
223+
// See https://stackoverflow.com/questions/24760543/createinstanceandunwrap-in-another-domain
224+
var currentDomain = AppDomain.CurrentDomain;
225+
var domainsetup = new AppDomainSetup()
226+
{
227+
ApplicationBase = currentDomain.SetupInformation.ApplicationBase,
228+
ConfigurationFile = currentDomain.SetupInformation.ConfigurationFile,
229+
LoaderOptimization = LoaderOptimization.SingleDomain,
230+
PrivateBinPath = "."
231+
};
232+
var domain = AppDomain.CreateDomain(
233+
$"My Domain {name}",
234+
currentDomain.Evidence,
235+
domainsetup);
236+
return domain;
237+
}
238+
224239
/// <summary>
225240
/// Resolves the assembly. Why doesn't this just work normally?
226241
/// </summary>

src/runtime/clrobject.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
namespace Python.Runtime
55
{
6+
[Serializable]
67
internal class CLRObject : ManagedType
78
{
89
internal object inst;
@@ -23,7 +24,7 @@ internal CLRObject(object ob, IntPtr tp)
2324
}
2425
}
2526

26-
GCHandle gc = AllocGCHandle();
27+
GCHandle gc = AllocGCHandle(TrackTypes.Wrapper);
2728
Marshal.WriteIntPtr(py, ObjectOffset.magic(tp), (IntPtr)gc);
2829
tpHandle = tp;
2930
pyHandle = py;
@@ -68,5 +69,19 @@ internal static IntPtr GetInstHandle(object ob)
6869
CLRObject co = GetInstance(ob);
6970
return co.pyHandle;
7071
}
72+
73+
protected override void OnSave()
74+
{
75+
base.OnSave();
76+
Runtime.XIncref(pyHandle);
77+
}
78+
79+
protected override void OnLoad()
80+
{
81+
base.OnLoad();
82+
GCHandle gc = AllocGCHandle(TrackTypes.Wrapper);
83+
Marshal.WriteIntPtr(pyHandle, ObjectOffset.magic(tpHandle), (IntPtr)gc);
84+
Runtime.XDecref(pyHandle);
85+
}
7186
}
7287
}

0 commit comments

Comments
 (0)
X Tutup