Skip to content

Commit b1c9f5b

Browse files
committed
EventObject no longer used for static events. EventBinding is constructed directly instead.
Also fixes event_rename domain reload test case
1 parent 03f32cb commit b1c9f5b

File tree

5 files changed

+143
-137
lines changed

5 files changed

+143
-137
lines changed

src/runtime/EventHandlerCollection.cs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Reflection;
4+
5+
namespace Python.Runtime;
6+
7+
internal class EventHandlerCollection: Dictionary<object, List<Handler>>
8+
{
9+
readonly EventInfo info;
10+
public EventHandlerCollection(EventInfo @event)
11+
{
12+
info = @event;
13+
}
14+
15+
/// <summary>
16+
/// Register a new Python object event handler with the event.
17+
/// </summary>
18+
internal bool AddEventHandler(BorrowedReference target, PyObject handler)
19+
{
20+
object? obj = null;
21+
if (target != null)
22+
{
23+
var co = (CLRObject)ManagedType.GetManagedObject(target)!;
24+
obj = co.inst;
25+
}
26+
27+
// Create a true delegate instance of the appropriate type to
28+
// wrap the Python handler. Note that wrapper delegate creation
29+
// always succeeds, though calling the wrapper may fail.
30+
Type type = info.EventHandlerType;
31+
Delegate d = PythonEngine.DelegateManager.GetDelegate(type, handler);
32+
33+
// Now register the handler in a mapping from instance to pairs
34+
// of (handler hash, delegate) so we can lookup to remove later.
35+
object key = obj ?? info.ReflectedType;
36+
if (!TryGetValue(key, out var list))
37+
{
38+
list = new List<Handler>();
39+
this[key] = list;
40+
}
41+
list.Add(new Handler(Runtime.PyObject_Hash(handler), d));
42+
43+
// Note that AddEventHandler helper only works for public events,
44+
// so we have to get the underlying add method explicitly.
45+
object[] args = { d };
46+
MethodInfo mi = info.GetAddMethod(true);
47+
mi.Invoke(obj, BindingFlags.Default, null, args, null);
48+
49+
return true;
50+
}
51+
52+
53+
/// <summary>
54+
/// Remove the given Python object event handler.
55+
/// </summary>
56+
internal bool RemoveEventHandler(BorrowedReference target, BorrowedReference handler)
57+
{
58+
object? obj = null;
59+
if (target != null)
60+
{
61+
var co = (CLRObject)ManagedType.GetManagedObject(target)!;
62+
obj = co.inst;
63+
}
64+
65+
nint hash = Runtime.PyObject_Hash(handler);
66+
if (hash == -1 && Exceptions.ErrorOccurred())
67+
{
68+
return false;
69+
}
70+
71+
object key = obj ?? info.ReflectedType;
72+
73+
if (!TryGetValue(key, out var list))
74+
{
75+
Exceptions.SetError(Exceptions.ValueError, "unknown event handler");
76+
return false;
77+
}
78+
79+
object?[] args = { null };
80+
MethodInfo mi = info.GetRemoveMethod(true);
81+
82+
for (var i = 0; i < list.Count; i++)
83+
{
84+
var item = (Handler)list[i];
85+
if (item.hash != hash)
86+
{
87+
continue;
88+
}
89+
args[0] = item.del;
90+
try
91+
{
92+
mi.Invoke(obj, BindingFlags.Default, null, args, null);
93+
}
94+
catch
95+
{
96+
continue;
97+
}
98+
list.RemoveAt(i);
99+
return true;
100+
}
101+
102+
Exceptions.SetError(Exceptions.ValueError, "unknown event handler");
103+
return false;
104+
}
105+
}

src/runtime/classmanager.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,9 @@ private static ClassInfo GetClassInfo(Type type)
491491
{
492492
continue;
493493
}
494-
ob = new EventObject(ei);
494+
ob = ei.AddMethod.IsStatic
495+
? new EventBinding(ei)
496+
: new EventObject(ei);
495497
ci.members[ei.Name] = ob;
496498
continue;
497499

src/runtime/eventbinding.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using System;
2+
using System.Diagnostics;
3+
using System.Reflection;
24

35
namespace Python.Runtime
46
{
@@ -8,15 +10,22 @@ namespace Python.Runtime
810
[Serializable]
911
internal class EventBinding : ExtensionType
1012
{
11-
private EventObject e;
13+
private readonly string name;
14+
private readonly EventHandlerCollection e;
1215
private PyObject? target;
1316

14-
public EventBinding(EventObject e, PyObject? target)
17+
public EventBinding(string name, EventHandlerCollection e, PyObject? target)
1518
{
19+
this.name = name;
1620
this.target = target;
1721
this.e = e;
1822
}
1923

24+
public EventBinding(EventInfo @event) : this(@event.Name, new EventHandlerCollection(@event), target: null)
25+
{
26+
Debug.Assert(@event.AddMethod.IsStatic);
27+
}
28+
2029

2130
/// <summary>
2231
/// EventBinding += operator implementation.
@@ -61,6 +70,10 @@ public static NewReference nb_inplace_subtract(BorrowedReference ob, BorrowedRef
6170
return new NewReference(ob);
6271
}
6372

73+
/// </summary>
74+
public static int tp_descr_set(BorrowedReference ds, BorrowedReference ob, BorrowedReference val)
75+
=> EventObject.tp_descr_set(ds, ob, val);
76+
6477

6578
/// <summary>
6679
/// EventBinding __hash__ implementation.
@@ -91,7 +104,7 @@ public static NewReference tp_repr(BorrowedReference ob)
91104
{
92105
var self = (EventBinding)GetManagedObject(ob)!;
93106
string type = self.target == null ? "unbound" : "bound";
94-
string s = string.Format("<{0} event '{1}'>", type, self.e.name);
107+
string s = string.Format("<{0} event '{1}'>", type, self.name);
95108
return Runtime.PyString_FromString(s);
96109
}
97110
}

src/runtime/eventobject.cs

Lines changed: 7 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections;
3+
using System.Diagnostics;
34
using System.Reflection;
45

56
namespace Python.Runtime
@@ -10,124 +11,16 @@ namespace Python.Runtime
1011
[Serializable]
1112
internal class EventObject : ExtensionType
1213
{
13-
internal string name;
14-
internal PyObject? unbound;
15-
internal EventInfo info;
16-
internal Hashtable? reg;
14+
internal readonly string name;
15+
internal readonly EventHandlerCollection reg;
1716

1817
public EventObject(EventInfo info)
1918
{
19+
Debug.Assert(!info.AddMethod.IsStatic);
2020
this.name = info.Name;
21-
this.info = info;
21+
this.reg = new EventHandlerCollection(info);
2222
}
2323

24-
25-
/// <summary>
26-
/// Register a new Python object event handler with the event.
27-
/// </summary>
28-
internal bool AddEventHandler(BorrowedReference target, PyObject handler)
29-
{
30-
object? obj = null;
31-
if (target != null)
32-
{
33-
var co = (CLRObject)GetManagedObject(target)!;
34-
obj = co.inst;
35-
}
36-
37-
// Create a true delegate instance of the appropriate type to
38-
// wrap the Python handler. Note that wrapper delegate creation
39-
// always succeeds, though calling the wrapper may fail.
40-
Type type = info.EventHandlerType;
41-
Delegate d = PythonEngine.DelegateManager.GetDelegate(type, handler);
42-
43-
// Now register the handler in a mapping from instance to pairs
44-
// of (handler hash, delegate) so we can lookup to remove later.
45-
// All this is done lazily to avoid overhead until an event is
46-
// actually subscribed to by a Python event handler.
47-
if (reg == null)
48-
{
49-
reg = new Hashtable();
50-
}
51-
object key = obj ?? info.ReflectedType;
52-
var list = reg[key] as ArrayList;
53-
if (list == null)
54-
{
55-
list = new ArrayList();
56-
reg[key] = list;
57-
}
58-
list.Add(new Handler(Runtime.PyObject_Hash(handler), d));
59-
60-
// Note that AddEventHandler helper only works for public events,
61-
// so we have to get the underlying add method explicitly.
62-
object[] args = { d };
63-
MethodInfo mi = info.GetAddMethod(true);
64-
mi.Invoke(obj, BindingFlags.Default, null, args, null);
65-
66-
return true;
67-
}
68-
69-
70-
/// <summary>
71-
/// Remove the given Python object event handler.
72-
/// </summary>
73-
internal bool RemoveEventHandler(BorrowedReference target, BorrowedReference handler)
74-
{
75-
if (reg == null)
76-
{
77-
Exceptions.SetError(Exceptions.ValueError, "unknown event handler");
78-
return false;
79-
}
80-
81-
object? obj = null;
82-
if (target != null)
83-
{
84-
var co = (CLRObject)GetManagedObject(target)!;
85-
obj = co.inst;
86-
}
87-
88-
nint hash = Runtime.PyObject_Hash(handler);
89-
if (hash == -1 && Exceptions.ErrorOccurred())
90-
{
91-
return false;
92-
}
93-
94-
object key = obj ?? info.ReflectedType;
95-
var list = reg[key] as ArrayList;
96-
97-
if (list == null)
98-
{
99-
Exceptions.SetError(Exceptions.ValueError, "unknown event handler");
100-
return false;
101-
}
102-
103-
object?[] args = { null };
104-
MethodInfo mi = info.GetRemoveMethod(true);
105-
106-
for (var i = 0; i < list.Count; i++)
107-
{
108-
var item = (Handler)list[i];
109-
if (item.hash != hash)
110-
{
111-
continue;
112-
}
113-
args[0] = item.del;
114-
try
115-
{
116-
mi.Invoke(obj, BindingFlags.Default, null, args, null);
117-
}
118-
catch
119-
{
120-
continue;
121-
}
122-
list.RemoveAt(i);
123-
return true;
124-
}
125-
126-
Exceptions.SetError(Exceptions.ValueError, "unknown event handler");
127-
return false;
128-
}
129-
130-
13124
/// <summary>
13225
/// Descriptor __get__ implementation. A getattr on an event returns
13326
/// a "bound" event that keeps a reference to the object instance.
@@ -141,25 +34,17 @@ public static NewReference tp_descr_get(BorrowedReference ds, BorrowedReference
14134
return Exceptions.RaiseTypeError("invalid argument");
14235
}
14336

144-
// If the event is accessed through its type (rather than via
145-
// an instance) we return an 'unbound' EventBinding that will
146-
// be cached for future accesses through the type.
147-
14837
if (ob == null)
14938
{
150-
if (self.unbound == null)
151-
{
152-
self.unbound = new EventBinding(self, target: null).Alloc().MoveToPyObject();
153-
}
154-
return new NewReference(self.unbound);
39+
return new NewReference(ds);
15540
}
15641

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

162-
return new EventBinding(self, new PyObject(ob)).Alloc();
47+
return new EventBinding(self.name, self.reg, new PyObject(ob)).Alloc();
16348
}
16449

16550

@@ -192,13 +77,6 @@ public static NewReference tp_repr(BorrowedReference ob)
19277
var self = (EventObject)GetManagedObject(ob)!;
19378
return Runtime.PyString_FromString($"<event '{self.name}'>");
19479
}
195-
196-
197-
protected override void Clear(BorrowedReference ob)
198-
{
199-
this.unbound = null!;
200-
base.Clear(ob);
201-
}
20280
}
20381

20482

tests/domain_tests/TestRunner.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ public class Cls
310310
public static event Action Before;
311311
public static void Call()
312312
{
313-
Before();
313+
if (Before != null) Before();
314314
}
315315
}
316316
}",
@@ -324,7 +324,7 @@ public class Cls
324324
public static event Action After;
325325
public static void Call()
326326
{
327-
After();
327+
if (After != null) After();
328328
}
329329
}
330330
}",
@@ -335,21 +335,29 @@ import sys
335335
from TestNamespace import Cls
336336
337337
called = False
338+
before_reload_called = False
339+
after_reload_called = False
338340
339341
def callback_function():
340342
global called
341343
called = True
342344
343345
def before_reload():
344-
global called
346+
global called, before_reload_called
345347
called = False
346348
Cls.Before += callback_function
347349
Cls.Call()
348350
assert called is True
351+
before_reload_called = True
349352
350353
def after_reload():
351-
global called
354+
global called, after_reload_called, before_reload_called
355+
356+
assert before_reload_called is True
357+
if not after_reload_called:
352358
assert called is True
359+
after_reload_called = True
360+
353361
called = False
354362
Cls.Call()
355363
assert called is False

0 commit comments

Comments
 (0)