Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
7ed0c7a
WIP
lostmsu Oct 24, 2021
d6a853f
avoid generating and handling useless SerializationException when May…
lostmsu Oct 28, 2021
b0c25c1
finalizer does not attempt to finalize objects when runtime is shut down
lostmsu Oct 28, 2021
5ca474a
PyType Dict and MRO properties to assist debugging
lostmsu Oct 28, 2021
48078b3
WIP 2
lostmsu Oct 28, 2021
a624dd8
fixed PyObject disposal crashing when runtime is still finalizing
lostmsu Oct 29, 2021
e7ab071
arrays: use 64 bit indexing, and avoid first chance .NET exceptions o…
lostmsu Oct 29, 2021
cbe1dd2
refactored conditional ClassBase slot initialization
lostmsu Oct 29, 2021
d5f1c48
removed DisposePythonWrappersForClrTypes
lostmsu Oct 29, 2021
74d87c5
simplified outdated condition in ClassBase.tp_clear
lostmsu Oct 29, 2021
82d6c33
sprinkled a few DebuggerHidden to make debugging easier
lostmsu Oct 29, 2021
eeebcd7
fixed derived classes not inheriting slots correctly
lostmsu Oct 29, 2021
8ee8d3d
remove unused TypeManager._slotImpls
lostmsu Oct 29, 2021
1a4ada7
fixed TestRuntime not building in Release mode
lostmsu Oct 30, 2021
a610aa3
can't really clear managed references to Python objects from ManagedT…
lostmsu Oct 30, 2021
03f32cb
PythonException is serializable
lostmsu Oct 30, 2021
b1c9f5b
EventObject no longer used for static events. EventBinding is constru…
lostmsu Oct 30, 2021
cb4bb9a
use a special class to stub .NET types that no longer exist after a d…
lostmsu Nov 2, 2021
652f946
make EventHandlerCollection serializable
lostmsu Nov 2, 2021
84db670
fixed MaybeMemberInfo always failing for properties
lostmsu Nov 2, 2021
56fafe3
fixed construct_removed_class domain reload test case
lostmsu Nov 2, 2021
d33dcdd
domain reload test runner can run test by index
lostmsu Nov 2, 2021
b737e10
minor docs change
lostmsu Nov 2, 2021
d3e4fba
assert check in GetUnmanagedBaseType for null base
lostmsu Nov 2, 2021
e003e12
PythonEngine .Exec and .Eval no longer work with raw pointers
lostmsu Nov 9, 2021
d0a6f44
a few annotation to ease debugging
lostmsu Nov 9, 2021
e31f7ba
ensure Python types continue to exist when registered decoders for th…
lostmsu Nov 9, 2021
48c0dfc
GC-related WIP
lostmsu Nov 9, 2021
cb58147
merge latest changes from upstream
lostmsu Nov 23, 2021
2fdbf0e
added TraceAlloc solution configuration
lostmsu Nov 24, 2021
a8ef06c
fixed sending PyObject across domain boundary
lostmsu Nov 24, 2021
7167229
fixed accidental premature disposal of Runtime.PyNone
lostmsu Nov 24, 2021
ab11fa2
made freeing GCHandles more robust
lostmsu Nov 24, 2021
7a4daeb
removed bad assert in generated constructor for derived classes
lostmsu Nov 24, 2021
e422367
fixed __pyobj__ access
lostmsu Nov 24, 2021
a74ea86
minor
lostmsu Nov 24, 2021
0325a8c
fixed Python derived types trying to double-free GCHandle when collec…
lostmsu Nov 24, 2021
85fab3b
reinstate collection assert on shutdown from Python
lostmsu Nov 24, 2021
932fce2
fixed crash when Python derived class instances survive past early sh…
lostmsu Nov 24, 2021
c2e207a
delay nulling GC handles of reflected instances until the last moment…
lostmsu Nov 24, 2021
c8f0f09
fixed assert in XDecref in case _Py_IsFinalizing is not present
lostmsu Nov 24, 2021
e269cf0
when initialized from Python, reset slots implemented in CLR: CLR mig…
lostmsu Nov 25, 2021
d7d5cb7
fixed minor warnings
lostmsu Nov 25, 2021
d6edace
fixed line endings in intern_.cs
lostmsu Nov 25, 2021
a86994f
use NonCopyableAnalyzer 0.7.0-m05
lostmsu Nov 25, 2021
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
Prev Previous commit
Next Next commit
EventObject no longer used for static events. EventBinding is constru…
…cted directly instead.

Also fixes event_rename domain reload test case
  • Loading branch information
lostmsu committed Oct 30, 2021
commit b1c9f5b0430ed2034c07eb7cdd012ed11f52d9f1
105 changes: 105 additions & 0 deletions src/runtime/EventHandlerCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using System.Reflection;

namespace Python.Runtime;

internal class EventHandlerCollection: Dictionary<object, List<Handler>>
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

mostly moved from eventobject.cs

{
readonly EventInfo info;
public EventHandlerCollection(EventInfo @event)
{
info = @event;
}

/// <summary>
/// Register a new Python object event handler with the event.
/// </summary>
internal bool AddEventHandler(BorrowedReference target, PyObject handler)
{
object? obj = null;
if (target != null)
{
var co = (CLRObject)ManagedType.GetManagedObject(target)!;
obj = co.inst;
}

// Create a true delegate instance of the appropriate type to
// wrap the Python handler. Note that wrapper delegate creation
// always succeeds, though calling the wrapper may fail.
Type type = info.EventHandlerType;
Delegate d = PythonEngine.DelegateManager.GetDelegate(type, handler);

// Now register the handler in a mapping from instance to pairs
// of (handler hash, delegate) so we can lookup to remove later.
object key = obj ?? info.ReflectedType;
if (!TryGetValue(key, out var list))
{
list = new List<Handler>();
this[key] = list;
}
list.Add(new Handler(Runtime.PyObject_Hash(handler), d));

// Note that AddEventHandler helper only works for public events,
// so we have to get the underlying add method explicitly.
object[] args = { d };
MethodInfo mi = info.GetAddMethod(true);
mi.Invoke(obj, BindingFlags.Default, null, args, null);

return true;
}


/// <summary>
/// Remove the given Python object event handler.
/// </summary>
internal bool RemoveEventHandler(BorrowedReference target, BorrowedReference handler)
{
object? obj = null;
if (target != null)
{
var co = (CLRObject)ManagedType.GetManagedObject(target)!;
obj = co.inst;
}

nint hash = Runtime.PyObject_Hash(handler);
if (hash == -1 && Exceptions.ErrorOccurred())
{
return false;
}

object key = obj ?? info.ReflectedType;

if (!TryGetValue(key, out var list))
{
Exceptions.SetError(Exceptions.ValueError, "unknown event handler");
return false;
}

object?[] args = { null };
MethodInfo mi = info.GetRemoveMethod(true);

for (var i = 0; i < list.Count; i++)
{
var item = (Handler)list[i];
if (item.hash != hash)
{
continue;
}
args[0] = item.del;
try
{
mi.Invoke(obj, BindingFlags.Default, null, args, null);
}
catch
{
continue;
}
list.RemoveAt(i);
return true;
}

Exceptions.SetError(Exceptions.ValueError, "unknown event handler");
return false;
}
}
4 changes: 3 additions & 1 deletion src/runtime/classmanager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,9 @@ private static ClassInfo GetClassInfo(Type type)
{
continue;
}
ob = new EventObject(ei);
ob = ei.AddMethod.IsStatic
? new EventBinding(ei)
: new EventObject(ei);
ci.members[ei.Name] = ob;
continue;

Expand Down
19 changes: 16 additions & 3 deletions src/runtime/eventbinding.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Diagnostics;
using System.Reflection;

namespace Python.Runtime
{
Expand All @@ -8,15 +10,22 @@ namespace Python.Runtime
[Serializable]
internal class EventBinding : ExtensionType
{
private EventObject e;
private readonly string name;
private readonly EventHandlerCollection e;
private PyObject? target;

public EventBinding(EventObject e, PyObject? target)
public EventBinding(string name, EventHandlerCollection e, PyObject? target)
{
this.name = name;
this.target = target;
this.e = e;
}

public EventBinding(EventInfo @event) : this(@event.Name, new EventHandlerCollection(@event), target: null)
{
Debug.Assert(@event.AddMethod.IsStatic);
}


/// <summary>
/// EventBinding += operator implementation.
Expand Down Expand Up @@ -61,6 +70,10 @@ public static NewReference nb_inplace_subtract(BorrowedReference ob, BorrowedRef
return new NewReference(ob);
}

/// </summary>
public static int tp_descr_set(BorrowedReference ds, BorrowedReference ob, BorrowedReference val)
=> EventObject.tp_descr_set(ds, ob, val);


/// <summary>
/// EventBinding __hash__ implementation.
Expand Down Expand Up @@ -91,7 +104,7 @@ public static NewReference tp_repr(BorrowedReference ob)
{
var self = (EventBinding)GetManagedObject(ob)!;
string type = self.target == null ? "unbound" : "bound";
string s = string.Format("<{0} event '{1}'>", type, self.e.name);
string s = string.Format("<{0} event '{1}'>", type, self.name);
return Runtime.PyString_FromString(s);
}
}
Expand Down
136 changes: 7 additions & 129 deletions src/runtime/eventobject.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections;
using System.Diagnostics;
using System.Reflection;

namespace Python.Runtime
Expand All @@ -10,124 +11,16 @@ namespace Python.Runtime
[Serializable]
internal class EventObject : ExtensionType
{
internal string name;
internal PyObject? unbound;
internal EventInfo info;
internal Hashtable? reg;
internal readonly string name;
internal readonly EventHandlerCollection reg;

public EventObject(EventInfo info)
{
Debug.Assert(!info.AddMethod.IsStatic);
this.name = info.Name;
this.info = info;
this.reg = new EventHandlerCollection(info);
}


/// <summary>
/// Register a new Python object event handler with the event.
/// </summary>
internal bool AddEventHandler(BorrowedReference target, PyObject handler)
{
object? obj = null;
if (target != null)
{
var co = (CLRObject)GetManagedObject(target)!;
obj = co.inst;
}

// Create a true delegate instance of the appropriate type to
// wrap the Python handler. Note that wrapper delegate creation
// always succeeds, though calling the wrapper may fail.
Type type = info.EventHandlerType;
Delegate d = PythonEngine.DelegateManager.GetDelegate(type, handler);

// Now register the handler in a mapping from instance to pairs
// of (handler hash, delegate) so we can lookup to remove later.
// All this is done lazily to avoid overhead until an event is
// actually subscribed to by a Python event handler.
if (reg == null)
{
reg = new Hashtable();
}
object key = obj ?? info.ReflectedType;
var list = reg[key] as ArrayList;
if (list == null)
{
list = new ArrayList();
reg[key] = list;
}
list.Add(new Handler(Runtime.PyObject_Hash(handler), d));

// Note that AddEventHandler helper only works for public events,
// so we have to get the underlying add method explicitly.
object[] args = { d };
MethodInfo mi = info.GetAddMethod(true);
mi.Invoke(obj, BindingFlags.Default, null, args, null);

return true;
}


/// <summary>
/// Remove the given Python object event handler.
/// </summary>
internal bool RemoveEventHandler(BorrowedReference target, BorrowedReference handler)
{
if (reg == null)
{
Exceptions.SetError(Exceptions.ValueError, "unknown event handler");
return false;
}

object? obj = null;
if (target != null)
{
var co = (CLRObject)GetManagedObject(target)!;
obj = co.inst;
}

nint hash = Runtime.PyObject_Hash(handler);
if (hash == -1 && Exceptions.ErrorOccurred())
{
return false;
}

object key = obj ?? info.ReflectedType;
var list = reg[key] as ArrayList;

if (list == null)
{
Exceptions.SetError(Exceptions.ValueError, "unknown event handler");
return false;
}

object?[] args = { null };
MethodInfo mi = info.GetRemoveMethod(true);

for (var i = 0; i < list.Count; i++)
{
var item = (Handler)list[i];
if (item.hash != hash)
{
continue;
}
args[0] = item.del;
try
{
mi.Invoke(obj, BindingFlags.Default, null, args, null);
}
catch
{
continue;
}
list.RemoveAt(i);
return true;
}

Exceptions.SetError(Exceptions.ValueError, "unknown event handler");
return false;
}


/// <summary>
/// Descriptor __get__ implementation. A getattr on an event returns
/// a "bound" event that keeps a reference to the object instance.
Expand All @@ -141,25 +34,17 @@ public static NewReference tp_descr_get(BorrowedReference ds, BorrowedReference
return Exceptions.RaiseTypeError("invalid argument");
}

// If the event is accessed through its type (rather than via
// an instance) we return an 'unbound' EventBinding that will
// be cached for future accesses through the type.

if (ob == null)
{
if (self.unbound == null)
{
self.unbound = new EventBinding(self, target: null).Alloc().MoveToPyObject();
}
return new NewReference(self.unbound);
return new NewReference(ds);
}

if (Runtime.PyObject_IsInstance(ob, tp) < 1)
{
return Exceptions.RaiseTypeError("invalid argument");
}

return new EventBinding(self, new PyObject(ob)).Alloc();
return new EventBinding(self.name, self.reg, new PyObject(ob)).Alloc();
}


Expand Down Expand Up @@ -192,13 +77,6 @@ public static NewReference tp_repr(BorrowedReference ob)
var self = (EventObject)GetManagedObject(ob)!;
return Runtime.PyString_FromString($"<event '{self.name}'>");
}


protected override void Clear(BorrowedReference ob)
{
this.unbound = null!;
base.Clear(ob);
}
}


Expand Down
16 changes: 12 additions & 4 deletions tests/domain_tests/TestRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ public class Cls
public static event Action Before;
public static void Call()
{
Before();
if (Before != null) Before();
}
}
}",
Expand All @@ -324,7 +324,7 @@ public class Cls
public static event Action After;
public static void Call()
{
After();
if (After != null) After();
}
}
}",
Expand All @@ -335,21 +335,29 @@ import sys
from TestNamespace import Cls

called = False
before_reload_called = False
after_reload_called = False

def callback_function():
global called
called = True

def before_reload():
global called
global called, before_reload_called
called = False
Cls.Before += callback_function
Cls.Call()
assert called is True
before_reload_called = True

def after_reload():
global called
global called, after_reload_called, before_reload_called

assert before_reload_called is True
if not after_reload_called:
assert called is True
after_reload_called = True

called = False
Cls.Call()
assert called is False
Expand Down