diff --git a/pythonnet.15.sln b/pythonnet.15.sln
index ce863817f..cfe3807ae 100644
--- a/pythonnet.15.sln
+++ b/pythonnet.15.sln
@@ -14,6 +14,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Python.Test.15", "src\testi
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Python.PerformanceTests", "src\perf_tests\Python.PerformanceTests.csproj", "{6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Python.DomainReloadTests", "src\domain_tests\Python.DomainReloadTests.15.csproj", "{F2FB6DA3-318E-4F30-9A1F-932C667E38C5}"
+EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Repo", "Repo", "{441A0123-F4C6-4EE4-9AEE-315FD79BE2D5}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
@@ -388,6 +390,66 @@ Global
{6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWinPY3|x64.Build.0 = ReleaseWinPY3|x64
{6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWinPY3|x86.ActiveCfg = ReleaseWinPY3|x86
{6FB0D091-9CEC-4DCC-8701-C40F9BFC9EDE}.ReleaseWinPY3|x86.Build.0 = ReleaseWinPY3|x86
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Debug|x64.Build.0 = Debug|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Debug|x86.Build.0 = Debug|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMono|Any CPU.ActiveCfg = Debug|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMono|Any CPU.Build.0 = Debug|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMono|x64.ActiveCfg = Debug|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMono|x64.Build.0 = Debug|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMono|x86.ActiveCfg = Debug|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMono|x86.Build.0 = Debug|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMonoPY3|Any CPU.ActiveCfg = Debug|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMonoPY3|Any CPU.Build.0 = Debug|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMonoPY3|x64.ActiveCfg = Debug|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMonoPY3|x64.Build.0 = Debug|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMonoPY3|x86.ActiveCfg = Debug|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugMonoPY3|x86.Build.0 = Debug|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWin|Any CPU.ActiveCfg = Debug|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWin|Any CPU.Build.0 = Debug|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWin|x64.ActiveCfg = Debug|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWin|x64.Build.0 = Debug|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWin|x86.ActiveCfg = Debug|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWin|x86.Build.0 = Debug|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWinPY3|Any CPU.ActiveCfg = Debug|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWinPY3|Any CPU.Build.0 = Debug|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWinPY3|x64.ActiveCfg = Debug|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWinPY3|x64.Build.0 = Debug|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWinPY3|x86.ActiveCfg = Debug|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.DebugWinPY3|x86.Build.0 = Debug|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Release|x64.ActiveCfg = Release|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Release|x64.Build.0 = Release|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Release|x86.ActiveCfg = Release|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.Release|x86.Build.0 = Release|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMono|Any CPU.ActiveCfg = Release|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMono|Any CPU.Build.0 = Release|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMono|x64.ActiveCfg = Release|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMono|x64.Build.0 = Release|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMono|x86.ActiveCfg = Release|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMono|x86.Build.0 = Release|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMonoPY3|Any CPU.ActiveCfg = Release|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMonoPY3|Any CPU.Build.0 = Release|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMonoPY3|x64.ActiveCfg = Release|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMonoPY3|x64.Build.0 = Release|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMonoPY3|x86.ActiveCfg = Release|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseMonoPY3|x86.Build.0 = Release|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWin|Any CPU.ActiveCfg = Release|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWin|Any CPU.Build.0 = Release|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWin|x64.ActiveCfg = Release|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWin|x64.Build.0 = Release|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWin|x86.ActiveCfg = Release|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWin|x86.Build.0 = Release|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWinPY3|Any CPU.ActiveCfg = Release|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWinPY3|Any CPU.Build.0 = Release|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWinPY3|x64.ActiveCfg = Release|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWinPY3|x64.Build.0 = Release|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWinPY3|x86.ActiveCfg = Release|Any CPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}.ReleaseWinPY3|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/pythonnet.sln b/pythonnet.sln
index c5afd66c3..7b198b336 100644
--- a/pythonnet.sln
+++ b/pythonnet.sln
@@ -12,6 +12,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Console", "src\console\Cons
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "clrmodule", "src\clrmodule\clrmodule.csproj", "{86E834DE-1139-4511-96CC-69636A56E7AC}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Python.DomainReloadTests", "src\domain_tests\Python.DomainReloadTests.csproj", "{7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
DebugMono|x64 = DebugMono|x64
@@ -184,6 +186,30 @@ Global
{86E834DE-1139-4511-96CC-69636A56E7AC}.ReleaseWinPY3|x64.Build.0 = ReleaseWinPY3|x64
{86E834DE-1139-4511-96CC-69636A56E7AC}.ReleaseWinPY3|x86.ActiveCfg = ReleaseWinPY3|x86
{86E834DE-1139-4511-96CC-69636A56E7AC}.ReleaseWinPY3|x86.Build.0 = ReleaseWinPY3|x86
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugMono|x64.ActiveCfg = DebugMono|x64
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugMono|x86.ActiveCfg = DebugMono|x86
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugMonoPY3|x64.ActiveCfg = DebugMonoPY3|x64
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugMonoPY3|x86.ActiveCfg = DebugMonoPY3|x86
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugWin|x64.ActiveCfg = DebugWin|x64
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugWin|x64.Build.0 = DebugWin|x64
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugWin|x86.ActiveCfg = DebugWin|x86
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugWin|x86.Build.0 = DebugWin|x86
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugWinPY3|x64.ActiveCfg = DebugWinPY3|x64
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugWinPY3|x64.Build.0 = DebugWinPY3|x64
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugWinPY3|x86.ActiveCfg = DebugWinPY3|x86
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.DebugWinPY3|x86.Build.0 = DebugWinPY3|x86
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseMono|x64.ActiveCfg = ReleaseMono|x64
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseMono|x86.ActiveCfg = ReleaseMono|x86
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseMonoPY3|x64.ActiveCfg = ReleaseMonoPY3|x64
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseMonoPY3|x86.ActiveCfg = ReleaseMonoPY3|x86
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseWin|x64.ActiveCfg = ReleaseWin|x64
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseWin|x64.Build.0 = ReleaseWin|x64
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseWin|x86.ActiveCfg = ReleaseWin|x86
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseWin|x86.Build.0 = ReleaseWin|x86
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseWinPY3|x64.ActiveCfg = ReleaseWinPY3|x64
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseWinPY3|x64.Build.0 = ReleaseWinPY3|x64
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseWinPY3|x86.ActiveCfg = ReleaseWinPY3|x86
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}.ReleaseWinPY3|x86.Build.0 = ReleaseWinPY3|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/domain_tests/App.config b/src/domain_tests/App.config
new file mode 100644
index 000000000..56efbc7b5
--- /dev/null
+++ b/src/domain_tests/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/domain_tests/Python.DomainReloadTests.15.csproj b/src/domain_tests/Python.DomainReloadTests.15.csproj
new file mode 100644
index 000000000..0b69ccb3d
--- /dev/null
+++ b/src/domain_tests/Python.DomainReloadTests.15.csproj
@@ -0,0 +1,53 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {F2FB6DA3-318E-4F30-9A1F-932C667E38C5}
+ Exe
+ Python.DomainReloadTests
+ Python.DomainReloadTests
+ bin\
+ v4.0
+ 512
+ true
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/domain_tests/Python.DomainReloadTests.csproj b/src/domain_tests/Python.DomainReloadTests.csproj
new file mode 100644
index 000000000..0914070ce
--- /dev/null
+++ b/src/domain_tests/Python.DomainReloadTests.csproj
@@ -0,0 +1,86 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {7539EBD5-7A15-4513-BCB0-1D9BEF112BC2}
+ Exe
+ Python.DomainReloadTests
+ Python.DomainReloadTests
+ v4.0
+ bin\
+ 512
+ true
+ true
+
+
+ x86
+
+
+ x64
+
+
+ true
+ DEBUG;TRACE
+ full
+
+
+
+
+ true
+ pdbonly
+
+
+ true
+ DEBUG;TRACE
+ full
+
+
+
+
+ true
+ pdbonly
+
+
+ true
+ DEBUG;TRACE
+ full
+
+
+
+
+ true
+ pdbonly
+
+
+ true
+ DEBUG;TRACE
+ full
+
+
+
+
+ true
+ pdbonly
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/domain_tests/TestRunner.cs b/src/domain_tests/TestRunner.cs
new file mode 100644
index 000000000..33c203ba0
--- /dev/null
+++ b/src/domain_tests/TestRunner.cs
@@ -0,0 +1,232 @@
+// We can't refer to or use Python.Runtime here.
+// We want it to be loaded only inside the subdomains
+using System;
+using Microsoft.CSharp;
+using System.CodeDom.Compiler;
+using System.IO;
+
+namespace Python.DomainReloadTests
+{
+ ///
+ /// This class compiles a DLL that contains the class which code will change
+ /// and a runner executable that will run Python code referencing the class.
+ /// It's Main() will:
+ /// * Run the runner and unlod it's domain
+ /// * Modify and re-compile the test class
+ /// * Re-run the runner and unload it twice
+ ///
+ class TestRunner
+ {
+ ///
+ /// The code of the test class that changes
+ ///
+ const string ChangingClassTemplate = @"
+using System;
+
+namespace TestNamespace
+{
+ [Serializable]
+ public class {class}
+ {
+ }
+}";
+
+ ///
+ /// The Python code that accesses the test class
+ ///
+ const string PythonCode = @"import clr
+clr.AddReference('TestClass')
+import sys
+from TestNamespace import {class}
+import TestNamespace
+foo = None
+def do_work():
+ sys.my_obj = {class}
+
+def test_work():
+ print({class})
+ print(sys.my_obj)
+";
+
+ ///
+ /// The runner's code. Runs the python code
+ ///
+ const string CaseRunnerTemplate = @"
+using System;
+using System.IO;
+using Python.Runtime;
+namespace CaseRunner
+{{
+ class CaseRunner
+ {{
+ public static int Main()
+ {{
+ try
+ {{
+ PythonEngine.Initialize(mode:{0});
+ using (Py.GIL())
+ {{
+ // Because the generated assemblies are in the $TEMP folder, add it to the path
+ var temp = Path.GetTempPath();
+ dynamic sys = Py.Import(""sys"");
+ sys.path.append(new PyString(temp));
+ dynamic test_mod = Py.Import(""domain_test_module.mod"");
+ test_mod.{1}_work();
+ }}
+ PythonEngine.Shutdown();
+ }}
+ catch (PythonException pe)
+ {{
+ throw new ArgumentException(message:pe.Message+"" ""+pe.StackTrace);
+ }}
+ return 0;
+ }}
+ }}
+}}
+";
+ readonly static string PythonDllLocation = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "../../runtime/bin/Python.Runtime.dll");
+
+ public static int Main(string[] args)
+ {
+ if (args.Length < 1)
+ {
+ args = new string[] {"TestClass", "NewTestClass"};
+ // return 123;
+ }
+ Console.WriteLine($"Testing with arguments: {string.Join(", ", args)}");
+
+ var tempFolderPython = Path.Combine(Path.GetTempPath(), "Python.Runtime.dll");
+ if (File.Exists(tempFolderPython))
+ {
+ File.Delete(tempFolderPython);
+ }
+
+ File.Copy(PythonDllLocation, tempFolderPython);
+
+ CreatePythonModule(args[0]);
+ {
+ var runnerAssembly = CreateCaseRunnerAssembly(verb:"do");
+ CreateTestClassAssembly(className: args[0]);
+
+ var runnerDomain = CreateDomain("case runner");
+ RunAndUnload(runnerDomain, runnerAssembly);
+ }
+
+ {
+ var runnerAssembly = CreateCaseRunnerAssembly(verb:"test");
+ // remove the method
+ CreateTestClassAssembly(className: args[1]);
+
+ // Do it twice for good measure
+ {
+ var runnerDomain = CreateDomain("case runner 2");
+ RunAndUnload(runnerDomain, runnerAssembly);
+ }
+ {
+ var runnerDomain = CreateDomain("case runner 3");
+ RunAndUnload(runnerDomain, runnerAssembly);
+ }
+ }
+
+ return 0;
+ }
+
+ static void RunAndUnload(AppDomain domain, string assemblyPath)
+ {
+ // Somehow the stack traces during execution sometimes have the wrong line numbers.
+ // Add some info for when debugging is required.
+ Console.WriteLine($"Runining domain {domain.FriendlyName}");
+ domain.ExecuteAssembly(assemblyPath);
+ AppDomain.Unload(domain);
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ GC.Collect();
+ }
+
+ static string CreateTestClassAssembly(string className)
+ {
+ var name = "TestClass.dll";
+ string code = ChangingClassTemplate.Replace("{class}", className);
+
+ return CreateAssembly(name, code, exe: false);
+ }
+
+ static string CreateCaseRunnerAssembly(string shutdownMode = "ShutdownMode.Reload", string verb = "do")
+ {
+ var code = string.Format(CaseRunnerTemplate, shutdownMode, verb);
+ var name = "TestCaseRunner.exe";
+
+ return CreateAssembly(name, code, exe: true);
+ }
+
+ static string CreateAssembly(string name, string code, bool exe = false)
+ {
+ // Console.WriteLine(code);
+ // Never return or hold the Assembly instance. This will cause
+ // the assembly to be loaded into the current domain and this
+ // interferes with the tests. The Domain can execute fine from a
+ // path, so let's return that.
+ CSharpCodeProvider provider = new CSharpCodeProvider();
+ CompilerParameters parameters = new CompilerParameters();
+ parameters.GenerateExecutable = exe;
+ var assemblyName = name;
+ var assemblyFullPath = Path.Combine(Path.GetTempPath(), assemblyName);
+ parameters.OutputAssembly = assemblyFullPath;
+ parameters.ReferencedAssemblies.Add("System.dll");
+ parameters.ReferencedAssemblies.Add("System.Core.dll");
+ parameters.ReferencedAssemblies.Add("Microsoft.CSharp.dll");
+ parameters.ReferencedAssemblies.Add(PythonDllLocation);
+ CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);
+ if (results.NativeCompilerReturnValue != 0)
+ {
+ foreach (var error in results.Errors)
+ {
+ System.Console.WriteLine(error);
+ }
+ throw new ArgumentException();
+ }
+
+ return assemblyFullPath;
+ }
+
+ static AppDomain CreateDomain(string name)
+ {
+ // Create the domain. Make sure to set PrivateBinPath to a relative
+ // path from the CWD (namely, 'bin').
+ // See https://stackoverflow.com/questions/24760543/createinstanceandunwrap-in-another-domain
+ var currentDomain = AppDomain.CurrentDomain;
+ var domainsetup = new AppDomainSetup()
+ {
+ ApplicationBase = Path.GetTempPath(),
+ ConfigurationFile = currentDomain.SetupInformation.ConfigurationFile,
+ LoaderOptimization = LoaderOptimization.SingleDomain,
+ PrivateBinPath = "."
+ };
+ var domain = AppDomain.CreateDomain(
+ $"My Domain {name}",
+ currentDomain.Evidence,
+ domainsetup);
+
+ return domain;
+ }
+
+ static string CreatePythonModule(string className)
+ {
+ var modulePath = Path.Combine(Path.GetTempPath(), "domain_test_module");
+ if (Directory.Exists(modulePath))
+ {
+ Directory.Delete(modulePath, recursive: true);
+ }
+ Directory.CreateDirectory(modulePath);
+
+ File.Create(Path.Combine(modulePath, "__init__.py")).Close(); //Create and don't forget to close!
+ using (var writer = File.CreateText(Path.Combine(modulePath, "mod.py")))
+ {
+ writer.Write(PythonCode.Replace("{class}", className));
+ }
+
+ return null;
+ }
+
+ }
+}
diff --git a/src/domain_tests/test_domain_reload.py b/src/domain_tests/test_domain_reload.py
new file mode 100644
index 000000000..991ae7a14
--- /dev/null
+++ b/src/domain_tests/test_domain_reload.py
@@ -0,0 +1,22 @@
+import subprocess
+import os
+
+def runit(m1, m2, member):
+ proc = subprocess.Popen([os.path.join(os.path.split(__file__)[0], 'bin', 'Python.DomainReloadTests.exe'), m1, m2, member])
+ proc.wait()
+
+ assert proc.returncode == 0
+
+def test_remove_method():
+
+ m1 = 'public static void TestMethod() {Console.WriteLine("from test method");}'
+ m2 = 'public static void TestMethod2() {Console.WriteLine("from test method");}'
+ member = 'TestMethod'
+ runit(m1, m2, member)
+
+def test_remove_member():
+
+ m1 = 'public static int TestMember = -1;'
+ m2 = 'public static int TestMember2 = -1;'
+ member = 'TestMember'
+ runit(m1, m2, member)
\ No newline at end of file
diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj
index 08dc1d860..484cc48c4 100644
--- a/src/runtime/Python.Runtime.csproj
+++ b/src/runtime/Python.Runtime.csproj
@@ -116,6 +116,7 @@
+
diff --git a/src/runtime/fieldobject.cs b/src/runtime/fieldobject.cs
index 86b93dd1b..6820927c2 100644
--- a/src/runtime/fieldobject.cs
+++ b/src/runtime/fieldobject.cs
@@ -1,5 +1,8 @@
using System;
using System.Reflection;
+using System.Runtime.Serialization;
+using System.Runtime.Serialization.Formatters.Binary;
+using System.IO;
namespace Python.Runtime
{
@@ -9,11 +12,11 @@ namespace Python.Runtime
[Serializable]
internal class FieldObject : ExtensionType
{
- private FieldInfo info;
+ private MaybeSerialize m_info;
public FieldObject(FieldInfo info)
{
- this.info = info;
+ m_info = new MaybeSerialize(info);
}
///
@@ -24,14 +27,25 @@ public FieldObject(FieldInfo info)
public static IntPtr tp_descr_get(IntPtr ds, IntPtr ob, IntPtr tp)
{
var self = (FieldObject)GetManagedObject(ds);
- object result;
if (self == null)
{
return IntPtr.Zero;
}
+ try
+ {
+ return self.tp_descr_get(ob, tp);
+ }
+ catch (Exception e)
+ {
+ Exceptions.SetError(Exceptions.TypeError, e.Message);
+ return IntPtr.Zero;
+ }
+ }
- FieldInfo info = self.info;
+ IntPtr tp_descr_get(IntPtr ob, IntPtr tp)
+ {
+ FieldInfo info = m_info.Value;
if (ob == IntPtr.Zero || ob == Runtime.PyNone)
{
@@ -43,7 +57,7 @@ public static IntPtr tp_descr_get(IntPtr ds, IntPtr ob, IntPtr tp)
}
try
{
- result = info.GetValue(null);
+ var result = info.GetValue(null);
return Converter.ToPython(result, info.FieldType);
}
catch (Exception e)
@@ -61,7 +75,7 @@ public static IntPtr tp_descr_get(IntPtr ds, IntPtr ob, IntPtr tp)
Exceptions.SetError(Exceptions.TypeError, "instance is not a clr object");
return IntPtr.Zero;
}
- result = info.GetValue(co.inst);
+ var result = info.GetValue(co.inst);
return Converter.ToPython(result, info.FieldType);
}
catch (Exception e)
@@ -79,7 +93,6 @@ public static IntPtr tp_descr_get(IntPtr ds, IntPtr ob, IntPtr tp)
public new static int tp_descr_set(IntPtr ds, IntPtr ob, IntPtr val)
{
var self = (FieldObject)GetManagedObject(ds);
- object newval;
if (self == null)
{
@@ -91,8 +104,21 @@ public static IntPtr tp_descr_get(IntPtr ds, IntPtr ob, IntPtr tp)
Exceptions.SetError(Exceptions.TypeError, "cannot delete field");
return -1;
}
+ try
+ {
+ return self.tp_descr_set(ob, val);
+ }
+ catch (Exception e)
+ {
+ Exceptions.SetError(Exceptions.TypeError, e.Message);
+ return -1;
+ }
+ }
- FieldInfo info = self.info;
+
+ int tp_descr_set(IntPtr ob, IntPtr val)
+ {
+ FieldInfo info = m_info.Value;
if (info.IsLiteral || info.IsInitOnly)
{
@@ -111,6 +137,7 @@ public static IntPtr tp_descr_get(IntPtr ds, IntPtr ob, IntPtr tp)
}
}
+ object newval;
if (!Converter.ToManaged(val, info.FieldType, out newval, true))
{
return -1;
@@ -147,7 +174,7 @@ public static IntPtr tp_descr_get(IntPtr ds, IntPtr ob, IntPtr tp)
public static IntPtr tp_repr(IntPtr ob)
{
var self = (FieldObject)GetManagedObject(ob);
- return Runtime.PyString_FromString($"");
+ return Runtime.PyString_FromString($"");
}
}
}
diff --git a/src/runtime/maybeserialize.cs b/src/runtime/maybeserialize.cs
new file mode 100644
index 000000000..70733476a
--- /dev/null
+++ b/src/runtime/maybeserialize.cs
@@ -0,0 +1,132 @@
+using System;
+using System.Reflection;
+using System.Runtime.Serialization;
+using System.Runtime.Serialization.Formatters.Binary;
+using System.IO;
+
+namespace Python.Runtime
+{
+ ///
+ /// A MaybeSerialize<T> delays errors from serialization and
+ /// deserialization until the item is used.
+ ///
+ /// Python for .NET uses this in the C# reloading architecture.
+ /// If e.g. a class member was renamed when reloading, references to the
+ /// old field will be invalid, but the rest of the system will still work.
+ /// Code that tries to use the old field will receive an exception.
+ ///
+ /// Assumption: the item being wrapped by MaybeSerialize will never be null.
+ ///
+ [Serializable]
+ internal struct MaybeSerialize : ISerializable where T : class
+ {
+ ///
+ /// The item being wrapped.
+ ///
+ /// If this is null, that means we failed to serialize or deserialize it.
+ ///
+ private T m_item;
+
+ ///
+ /// A string useful for debugging the error.
+ ///
+ /// This is null if m_item deserialized properly.
+ /// Otherwise, it will be derived off of m_item.ToString() when we
+ /// serialized.
+ ///
+ private string m_name;
+
+ ///
+ /// Store an item in such a way that it can be deserialized.
+ ///
+ /// It must not be null.
+ ///
+ public MaybeSerialize(T item)
+ {
+ if (item == null)
+ {
+ throw new System.ArgumentNullException("Trying to store a null");
+ }
+ m_item = item;
+ m_name = null;
+ }
+
+ ///
+ /// Get the underlying deserialized value, or throw an exception
+ /// if deserialiation failed.
+ ///
+ public T Value
+ {
+ get
+ {
+ if (m_item == null)
+ {
+ throw new SerializationException($"The .NET object underlying {m_name} no longer exists");
+ }
+ return m_item;
+ }
+ }
+
+ ///
+ /// Get a printable name.
+ ///
+ public string ToString()
+ {
+ if (m_item == null)
+ {
+ return $"(missing {m_name})";
+ }
+ else
+ {
+ return m_item.ToString();
+ }
+ }
+
+ ///
+ /// Implements ISerializable
+ ///
+ public void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ if (m_item == null)
+ {
+ // Save the name; this failed to reload in a previous
+ // generation but we still need to remember what it was.
+ info.AddValue("n", m_name);
+ }
+ else
+ {
+ // Try to save the item. If it fails, too bad.
+ try
+ {
+ info.AddValue("i", m_item);
+ }
+ catch(SerializationException _)
+ {
+ }
+
+ // Also save the name in case the item doesn't deserialize
+ info.AddValue("n", m_item.ToString());
+ }
+ }
+
+ ///
+ /// Implements ISerializable
+ ///
+ private MaybeSerialize(SerializationInfo info, StreamingContext context)
+ {
+ try
+ {
+ // Try to deserialize the item. It might fail, or it might
+ // have already failed so there just isn't an "i" to find.
+ m_item = (T)info.GetValue("i", typeof(T));
+ m_name = null;
+ }
+ catch (SerializationException _)
+ {
+ // Getting the item failed, so get the name.
+ m_item = null;
+ m_name = info.GetString("n");
+ }
+ }
+ }
+}