Skip to content

Make methods of PyObject inherited from its base .NET classes GIL-safe #1711

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
merged 1 commit into from
Mar 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ details about the cause of the failure
- floating point values passed from Python are no longer silently truncated
when .NET expects an integer [#1342][i1342]
- More specific error messages for method argument mismatch
- members of `PyObject` inherited from `System.Object and `DynamicObject` now autoacquire GIL
- BREAKING: when inheriting from .NET types in Python if you override `__init__` you
must explicitly call base constructor using `super().__init__(.....)`. Not doing so will lead
to undefined behavior.
Expand All @@ -69,6 +70,7 @@ One must now either use enum members (e.g. `MyEnum.Option`), or use enum constru
- BREAKING: Names of .NET types (e.g. `str(__class__)`) changed to better support generic types
- BREAKING: overload resolution will no longer prefer basic types. Instead, first matching overload will
be chosen.
- BREAKING: acquiring GIL using `Py.GIL` no longer forces `PythonEngine` to initialize
- BREAKING: `Exec` and `Eval` from `PythonEngine` no longer accept raw pointers.
- BREAKING: .NET collections and arrays are no longer automatically converted to
Python collections. Instead, they implement standard Python
Expand Down
2 changes: 1 addition & 1 deletion src/embed_tests/Codecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ public DecoderReturningPredefinedValue(PyObject objectType, TTarget decodeResult
}

public bool CanDecode(PyType objectType, Type targetType)
=> objectType.Handle == TheOnlySupportedSourceType.Handle
=> PythonReferenceComparer.Instance.Equals(objectType, TheOnlySupportedSourceType)
&& targetType == typeof(TTarget);
public bool TryDecode<T>(PyObject pyObj, out T value)
{
Expand Down
7 changes: 7 additions & 0 deletions src/embed_tests/TestPyObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ public void GetAttrDefault_IgnoresAttributeErrorOnly()
);
Assert.AreEqual(Exceptions.TypeError, typeErrResult.Type);
}

// regression test from https://github.com/pythonnet/pythonnet/issues/1642
[Test]
public void InheritedMethodsAutoacquireGIL()
{
PythonEngine.Exec("from System import String\nString.Format('{0},{1}', 1, 2)");
}
}

public class PyObjectTestMethods
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/InternString.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public static void Initialize()
{
NewReference pyStr = Runtime.PyUnicode_InternFromString(name);
var op = new PyString(pyStr.StealOrThrow());
Debug.Assert(name == op.ToString());
Debug.Assert(name == op.As<string>());
SetIntern(name, op);
var field = type.GetField("f" + name, PyIdentifierFieldFlags)!;
field.SetValue(null, op.rawPtr);
Expand Down
10 changes: 1 addition & 9 deletions src/runtime/Py.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,7 @@ namespace Python.Runtime;

public static class Py
{
public static GILState GIL()
{
if (!PythonEngine.IsInitialized)
{
PythonEngine.Initialize();
}

return PythonEngine.DebugGIL ? new DebugGILState() : new GILState();
}
public static GILState GIL() => PythonEngine.DebugGIL ? new DebugGILState() : new GILState();

public static PyModule CreateScope() => new();
public static PyModule CreateScope(string name)
Expand Down
21 changes: 16 additions & 5 deletions src/runtime/PythonTypes/PyObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1056,6 +1056,7 @@ public PyList Dir()
/// </remarks>
public override string? ToString()
{
using var _ = Py.GIL();
using var strval = Runtime.PyObject_Str(obj);
return Runtime.GetManagedString(strval.BorrowOrThrow());
}
Expand All @@ -1072,7 +1073,11 @@ public PyList Dir()
/// Return true if this object is equal to the given object. This
/// method is based on Python equality semantics.
/// </remarks>
public override bool Equals(object o) => Equals(o as PyObject);
public override bool Equals(object o)
{
using var _ = Py.GIL();
return Equals(o as PyObject);
}

public virtual bool Equals(PyObject? other)
{
Expand Down Expand Up @@ -1101,6 +1106,7 @@ public virtual bool Equals(PyObject? other)
/// </remarks>
public override int GetHashCode()
{
using var _ = Py.GIL();
nint pyHash = Runtime.PyObject_Hash(obj);
if (pyHash == -1 && Exceptions.ErrorOccurred())
{
Expand Down Expand Up @@ -1135,12 +1141,14 @@ public long Refcount

public override bool TryGetMember(GetMemberBinder binder, out object? result)
{
using var _ = Py.GIL();
result = CheckNone(this.GetAttr(binder.Name));
return true;
}

public override bool TrySetMember(SetMemberBinder binder, object? value)
{
using var _ = Py.GIL();
using var newVal = Converter.ToPythonDetectType(value);
int r = Runtime.PyObject_SetAttrString(obj, binder.Name, newVal.Borrow());
if (r < 0)
Expand Down Expand Up @@ -1234,6 +1242,7 @@ private static NewReference GetPythonObject(object? target)

public override bool TryInvokeMember(InvokeMemberBinder binder, object?[] args, out object? result)
{
using var _ = Py.GIL();
if (this.HasAttr(binder.Name) && this.GetAttr(binder.Name).IsCallable())
{
PyTuple? pyargs = null;
Expand All @@ -1258,6 +1267,7 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object?[] args,

public override bool TryInvoke(InvokeBinder binder, object?[] args, out object? result)
{
using var _ = Py.GIL();
if (this.IsCallable())
{
PyTuple? pyargs = null;
Expand All @@ -1282,6 +1292,7 @@ public override bool TryInvoke(InvokeBinder binder, object?[] args, out object?

public override bool TryConvert(ConvertBinder binder, out object? result)
{
using var _ = Py.GIL();
// always try implicit conversion first
if (Converter.ToManaged(this.obj, binder.Type, out result, false))
{
Expand All @@ -1307,6 +1318,7 @@ public override bool TryConvert(ConvertBinder binder, out object? result)

public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object? result)
{
using var _ = Py.GIL();
NewReference res;
if (!(arg is PyObject))
{
Expand Down Expand Up @@ -1419,6 +1431,7 @@ public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg

public override bool TryUnaryOperation(UnaryOperationBinder binder, out object? result)
{
using var _ = Py.GIL();
int r;
NewReference res;
switch (binder.Operation)
Expand Down Expand Up @@ -1463,10 +1476,8 @@ public override bool TryUnaryOperation(UnaryOperationBinder binder, out object?
/// <returns>A sequence that contains dynamic member names.</returns>
public override IEnumerable<string> GetDynamicMemberNames()
{
foreach (PyObject pyObj in Dir())
{
yield return pyObj.ToString()!;
}
using var _ = Py.GIL();
return Dir().Select(pyObj => pyObj.ToString()!).ToArray();
}

void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
Expand Down