8000 Domain reload test cases fixes by BadSingleton · Pull Request #1287 · pythonnet/pythonnet · GitHub
[go: up one dir, main page]

Skip to content

Domain reload test cases fixes #1287

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

Merged

Conversation

BadSingleton
Copy link
Contributor

What does this implement/fix? Explain your changes.

This PR adds the fixes for the xfail tests in #1275 .

Serialization of System.Type, MemberInfo and MethodBase is now string based. At de-serialization, use reflection to attempt to recreate the object, which now fails safely instead of throwing a SerializationException during the de-serialization of the whole data stream. Appropriate Exceptions will now be raised when the Maybe*'s Value property is accessed.

Does this close any currently open issues?

This addresses #957 and #1250 and offers an answer to #1268 .

Any other comments?

TODO: add changelog entries once the fixes are approved. Also, add docs on best practices with domain reloads.

Checklist

Check all those that are applicable and complete.

  • Make sure to include one or more tests for your change
  • If an enhancement PR, please create docs and at best an example
  • Add yourself to AUTHORS
  • Updated the CHANGELOG

string[] types = new string[p.Length];
for (int i = 0; i < p.Length; i++)
{
types[i] = p[i].ParameterType.AssemblyQualifiedName;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work correctly for out and ref parameters? There might be more edge cases.

Potentially in parameters also.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll have to test

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did a quick check and out and ref parameters both reflect to void function (SomeType ByRef) with the type argument being of type (SomeType&, ...). Haven't been able to test with in (couldn't get to set the language version high enough in TestRunner.cs), but it looks like it's doing the correct thing. Will add a test case for it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, found an edge case where changing ref to out will still "resolve" the function even though the semantics are now different

Comment on lines 350 to 352
// Trying to remove a key that's not in the dictionary may
// raise an error. We don't care about it.
Runtime.PyErr_Clear();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You must check for the specific error type after each call. See PyObject.GetAttrOrDefault for example.

Comment on lines 80 to 81
var _cache = new Dictionary<MaybeType, IntPtr>();
storage.GetValue("cache", out _cache);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the value you put into _cache here will always be overwritten.

You can (and should) use out var _cache.

Comment on lines 89 to 87
catch
{
Runtime.XDecref(entry.Value);
continue;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a specific exception we could handle?
Can the exception be avoided in the first place by adding a precheck?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... adding a precheck?

Yes

Serialization of System.Type, MemberInfo and MethodBase is now string
based. At deserialization, use reflection to attempt to recreate the
object, which may fail safely instead of throwing a
SerializaitonException during the deserialization of the whoie data
stream. Appropriate Exceptions will now be raised when the Maybe*'s
Value property.

ClasseBase objects are now de-initialized and re-initialized in Reload
mode so that it's tp_dict picks up newly added members and removed
members no longer linger.

ModuleObject clears it's cache and remove cached members from it's
tp_dict.

Minor refactoring and modernization of MethodObject and MethodBinder
@BadSingleton BadSingleton force-pushed the domain-reload-test-cases-fixes branch from 946107f to 3dad96e Compare November 20, 2020 17:17
Changing a type's attribute causes problem with it's cache. Force the
type to refresh itself when modifying it.
* Revert line endings change in Python.Runtime.csproj
* Split maybe serialize into respective class files
* Name changes for consistency
@codecov-io
Copy link
codecov-io commented Nov 23, 2020

Codecov Report

Merging #1287 (79516f1) into master (f8c27a1) will not change coverage.
The diff coverage is n/a.

Impacted file tree graph

@@           Coverage Diff           @@
##           master    #1287   +/-   ##
=======================================
  Coverage   74.04%   74.04%           
=======================================
  Files           1        1           
  Lines         289      289           
=======================================
  Hits          214      214           
  Misses         75       75           
Flag Coverage Δ
setup_windows 74.04% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.


Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 9307bb3...5c14aad. Read the comment docs.

// based on it's setter/getter (which is a method
// info) visibility and events based on their
// AddMethod visibility.
static bool ShouldBindMember(MemberInfo mi)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not totally satisfied with this solution, but it's the best I could find. If anyone has a better idea..

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could also leave this abstract and have class MaybePropertyInfo : MaybeMemberInfo<PropertyInfo> and so on, enforcing that if the type of member changed then it doesn't count as a match.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But maybe that's not critical.

Serialization of System.Type, MemberInfo and MethodBase is now string
based. At deserialization, use reflection to attempt to recreate the
object, which may fail safely instead of throwing a
SerializaitonException during the deserialization of the whoie data
stream. Appropriate Exceptions will now be raised when the Maybe*'s
Value property.

ClasseBase objects are now de-initialized and re-initialized in Reload
mode so that it's tp_dict picks up newly added members and removed
members no longer linger.

ModuleObject clears it's cache and remove cached members from it's
tp_dict.

Minor refactoring and modernization of MethodObject and MethodBinder
Changing a type's attribute causes problem with it's cache. Force the
type to refresh itself when modifying it.
* Revert line endings change in Python.Runtime.csproj
* Split maybe serialize into respective class files
* Name changes for consistency
So that we can use that same logic when deserializing Maybe* types
BadSingleton and others added 9 commits December 18, 2020 13:45
Because it can't find the python library
Because tp_clear sets tpHandle to NULL, it can't be used.
Fortunately, we can simply read object's type from pyHandle.
… engine shutdown (pythonnet#1260)

pythonnet#1256
pythonnet#1256

During engine shutdown all links from Python to .NET instances are severed. If an instance of CLR class defined in Python survives the shutdown (for example, a reference is stored in static field) and later gets finalized, it will attempt to severe link again, which is an invalid operation.

The fix is to check if the link has already been severed and skip that step during finalization.
@BadSingleton BadSingleton force-pushed the domain-reload-test-cases-fixes branch from 8639f09 to 3069285 Compare December 18, 2020 18:45
@BadSingleton
Copy link
Contributor Author

Build failures are due to NuGet hiccups


  /usr/share/dotnet/sdk/5.0.101/NuGet.targets(131,5): error : Failed to retrieve information about 'System.Security.Permissions' from remote source 'https://api.nuget.org/v3-flatcontainer/system.security.permissions/index.json'. [/tmp/pip-req-build-49umrav0/src/runtime/Python.Runtime.csproj]
  /usr/share/dotnet/sdk/5.0.101/NuGet.targets(131,5): error :   Response status code does not indicate success: 503 (Service Not Available). [/tmp/pip-req-build-49umrav0/src/runtime/Python.Runtime.csproj]

}}
}}
";
readonly static string PythonDllLocation = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Python.Runtime.dll");
Copy link
Member
@lostmsu lostmsu Dec 18, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: I might be more reliable to do typeof(Py).Assembly.Location.

File.Delete(tempFolderPython);
}

File.Copy(PythonDllLocation, tempFolderPython);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO, the test should also delete the DLL.
I would also create a new folder (and delete after) for every run instead of always putting DLL directly in %TEMP%, because this might affect tests running in parallel.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I totally agree with this

CompilerParameters parameters = new CompilerParameters();
parameters.GenerateExecutable = exe;
var assemblyName = name;
var assemblyFullPath = Path.Combine(Path.GetTempPath(), assemblyName);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same: better have a run-specific folder.

Comment on lines 188 to 192
if (mi == null)
{
return -1;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think int.Max would be more appropriate. Higher the number - lower the priority.

UPD. can this even be null?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it could be null if we change the return type to int? but it's not useful. int.Max it is

}

private void _MethodObject(Type type, string name, MethodInfo[] info)
// `allow_threads = true`: True being the default value of MethodBinder.allow_threads
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better use a constant.

@BadSingleton
Copy link
Contributor Author

I'll be on holidays for two weeks, I'll fix that when back in office

@BadSingleton BadSingleton requested a review from lostmsu January 4, 2021 21:04

static void SetupTestFolder(string testCaseName)
{
TestPath = Path.Combine(Path.GetTempPath(), $"Python.TestRunner.{testCaseName}");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think some random number should also be added to indicate the overall run, so that different runs would not clash.

Copy link
Contributor Author
@BadSingleton BadSingleton Jan 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They wouldn't, if the folder exists when setting up the test, it gets deleted first. Also, the executable can only run one test per invocation. One could run all the tests in parallel, and there'd be no clashing. If a test fails, the folder is not deleted and the contents cans be examined.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still see a problem with running tests for multiple Python versions on the same machine in parallel.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I hadn't thought about that one. I'll add the python version and architecture to the folder name.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add the PID instead

@BadSingleton BadSingleton requested a review from lostmsu January 5, 2021 16:40
@BadSingleton BadSingleton force-pushed the domain-reload-test-cases-fixes branch from ac877c2 to 5779d5f Compare January 6, 2021 18:42
To the test folder name
@BadSingleton BadSingleton force-pushed the domain-reload-test-cases-fixes branch from 5779d5f to 73f39bc Compare January 6, 2021 20:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants
0