Skip to content
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

Added caching of event registration state to support unsubscribes across garbage collections #910

Merged
merged 22 commits into from
Jul 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
57516a6
restore event source caching
Scottj1s Jun 17, 2021
bbcd648
fixed bad merge
Scottj1s Jun 17, 2021
ad58eaa
Made weak references to handlers
ujjwalchadha Jun 30, 2021
3b3644a
Added cache cleaner
ujjwalchadha Jul 1, 2021
3fb27d1
Remove state empty condition
ujjwalchadha Jul 1, 2021
51b08d1
Fix event source caching.
manodasanW Jul 8, 2021
9a0d121
Fix merge.
manodasanW Jul 8, 2021
4c76b68
Fix merge.
manodasanW Jul 9, 2021
cbdcdae
Spacing.
manodasanW Jul 9, 2021
e89710b
reorder
manodasanW Jul 9, 2021
bc25c95
Fix issue where registered delegates caused the WinRT object to not g…
manodasanW Jul 12, 2021
a0a0ba5
Update delegates to take advantage of agile changes in IObjectReference.
manodasanW Jun 3, 2021
c48ba7c
Fix merge
manodasanW Jul 13, 2021
efb3f1c
Make all event ConditionalWeakTables Lazy to reduce finalizer load.
manodasanW Jul 13, 2021
792461b
Fix memory leak.
manodasanW Jul 14, 2021
d129bff
Re-enable tests as they are now passing.
manodasanW Jul 14, 2021
d5b0fb3
Fix scenario where delegate can be null.
manodasanW Jul 15, 2021
ac7e25b
Don't wrap native delegates.
manodasanW Jul 19, 2021
7b81868
Merge branch 'master' into manodasanw/fix_event_source_caching_leak2
manodasanW Jul 19, 2021
c681479
Merge branch 'master' into manodasanw/fix_event_source_caching_leak2
manodasanW Jul 19, 2021
b6a0137
Merge branch 'master' into manodasanw/fix_event_source_caching_leak2
manodasanW Jul 22, 2021
1f34fb4
Merge branch 'master' into manodasanw/fix_event_source_caching_leak2
manodasanW Jul 24, 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
43 changes: 21 additions & 22 deletions src/Tests/ObjectLifetimeTests/ObjectLifetimeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -355,10 +355,10 @@ public void BasicTest6b()


AutoResetEvent loadedSignal = new AutoResetEvent(false);
AutoResetEvent unloadedSignal = new AutoResetEvent(false);


// Bug: _parentRef is still alive https://github.com/microsoft/CsWinRT/issues/897
AutoResetEvent unloadedSignal = new AutoResetEvent(false);
[TestMethod]
public void CycleTest1()
{
//Debugger.Launch();
Expand Down Expand Up @@ -414,15 +414,14 @@ public void CycleTest1()
});

_asyncQueue.Run();
}

//
// Cycle from StackPanel to custom element to delegate back to StackPanel.
//


//[MethodImplAttribute(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
//Bug:_parentRef is still alive https://github.com/microsoft/CsWinRT/issues/897
}

//
// Cycle from StackPanel to custom element to delegate back to StackPanel.
//

[TestMethod]
[MethodImplAttribute(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
public void CycleTest1b()
{
_asyncQueue
Expand Down Expand Up @@ -476,15 +475,15 @@ public void CycleTest1b()
});

_asyncQueue.Run();
}

//
// Cycle from StackPanel to built-in element to delegate on framework event back to StackPanel.
//

//[MethodImplAttribute(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
//Bug:_element1Ref is still alive https://github.com/microsoft/CsWinRT/issues/897
}
//
// Cycle from StackPanel to built-in element to delegate on framework event back to StackPanel.
//
[TestMethod]
[MethodImplAttribute(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
public void CycleTest1c()
{
_asyncQueue
Expand Down
43 changes: 43 additions & 0 deletions src/Tests/TestComponentCSharp/Singleton.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#include "pch.h"
#include "Singleton.h"
#include "Singleton.g.cpp"

using namespace winrt;
using namespace Windows::Foundation;

namespace winrt::TestComponentCSharp::implementation
{
TestComponentCSharp::ISingleton Singleton::Instance()
{
struct singleton : winrt::implements<singleton, ISingleton>
{
int _int{};
winrt::event<EventHandler<int32_t>> _intChanged {};

int32_t IntProperty()
{
return _int;
}
void IntProperty(int32_t value)
{
_int = value;
_intChanged(nullptr, _int);
}
winrt::event_token IntPropertyChanged(EventHandler<int32_t> const& handler)
{
return _intChanged.add(handler);
}
void IntPropertyChanged(winrt::event_token const& token) noexcept
{
_intChanged.remove(token);
}
};
static TestComponentCSharp::ISingleton _singleton = winrt::make<singleton>();
return _singleton;
}

void Singleton::Instance(TestComponentCSharp::ISingleton const& value)
{
throw hresult_not_implemented();
}
}
19 changes: 19 additions & 0 deletions src/Tests/TestComponentCSharp/Singleton.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#pragma once
#include "Singleton.g.h"

namespace winrt::TestComponentCSharp::implementation
{
struct Singleton
{
Singleton() = default;

static TestComponentCSharp::ISingleton Instance();
static void Instance(TestComponentCSharp::ISingleton const& value);
};
}
namespace winrt::TestComponentCSharp::factory_implementation
{
struct Singleton : SingletonT<Singleton, implementation::Singleton>
{
};
}
13 changes: 12 additions & 1 deletion src/Tests/TestComponentCSharp/TestComponentCSharp.idl
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,17 @@ namespace TestComponentCSharp
static Int32 NumObjects{ get; };
}

interface ISingleton
{
Int32 IntProperty;
event Windows.Foundation.EventHandler<Int32> IntPropertyChanged;
}

static runtimeclass Singleton
{
static ISingleton Instance;
}

[default_interface, gc_pressure(Windows.Foundation.Metadata.GCPressureAmount.High)]
runtimeclass Class :
Windows.Foundation.IStringable
Expand Down Expand Up @@ -471,7 +482,7 @@ namespace TestComponentCSharp
static event Windows.Foundation.EventHandler<Int32> WarningEvent;
}
}

[contract(Windows.Foundation.UniversalApiContract, 8)]
interface IWarning1
{
Expand Down
2 changes: 2 additions & 0 deletions src/Tests/TestComponentCSharp/TestComponentCSharp.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
</ClInclude>
<ClInclude Include="NonAgileClass.h" />
<ClInclude Include="ComImports.h" />
<ClInclude Include="Singleton.h" />
<ClInclude Include="WarningClass.h" />
<ClInclude Include="WarningStatic.h" />
<ClInclude Include="Windows.Class.h" />
Expand All @@ -88,6 +89,7 @@
<ClCompile Include="NonAgileClass.cpp" />
<ClCompile Include="ComImports.cpp" />
<ClCompile Include="ManualProjectionTestClasses.cpp" />
<ClCompile Include="Singleton.cpp" />
<ClCompile Include="WarningClass.cpp" />
<ClCompile Include="WarningStatic.cpp" />
<ClCompile Include="Windows.Class.cpp" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<ClCompile Include="ManualProjectionTestClasses.cpp" />
<ClCompile Include="WarningClass.cpp" />
<ClCompile Include="WarningStatic.cpp" />
<ClCompile Include="Singleton.cpp" />
<ClCompile Include="Windows.Class.cpp" />
<ClCompile Include="ABCDEFGHIJKLMNOPQRSTUVQXYZabcdefghijklmnopqrstuvqxyzABCDEFGHIJKLMNOPQRSTUVQXYZabcdefghijklmnopqrstuvqxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.cpp" />
</ItemGroup>
Expand All @@ -25,6 +26,7 @@
<ClInclude Include="ManualProjectionTestClasses.h" />
<ClInclude Include="WarningStatic.h" />
<ClInclude Include="WarningClass.h" />
<ClInclude Include="Singleton.h" />
<ClInclude Include="Windows.Class.h" />
<ClInclude Include="ABCDEFGHIJKLMNOPQRSTUVQXYZabcdefghijklmnopqrstuvqxyzABCDEFGHIJKLMNOPQRSTUVQXYZabcdefghijklmnopqrstuvqxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.h" />
</ItemGroup>
Expand Down
80 changes: 75 additions & 5 deletions src/Tests/UnitTest/TestComponentCSharp_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2461,15 +2461,44 @@ public void TestCovariance()
Assert.True(TestObject.IterableOfObjectIterablesProperty.SequenceEqual(listOfListOfUris));
}

(System.WeakReference<Class>, System.WeakReference<EventHandlerClass>) TestEventDelegateCleanup()
{
// Both WinRT object and handler class alive.
var eventCalled = false;
var eventHandlerClass = new EventHandlerClass(() => eventCalled = true);
var classInstance = new Class();
classInstance.IntPropertyChanged += eventHandlerClass.IntPropertyChanged;
GC.Collect(2, GCCollectionMode.Forced, true);
GC.WaitForPendingFinalizers();
classInstance.IntProperty = 3;
Assert.True(eventCalled);

// No strong reference to handler class, but delegate is still registered on
// the WinRT object keeping it alive.
eventCalled = false;
var weakEventHandlerClass = new System.WeakReference<EventHandlerClass>(eventHandlerClass);
eventHandlerClass = null;
GC.Collect(2, GCCollectionMode.Forced, true);
GC.WaitForPendingFinalizers();
classInstance.IntProperty = 3;
Assert.True(eventCalled);
Assert.True(weakEventHandlerClass.TryGetTarget(out var _));

// No strong reference to WinRT object. It should no longer be alive
// and should also cause for the event handler class to be no longer alive.
var weakClassInstance = new System.WeakReference<Class>(classInstance);
classInstance = null;
return (weakClassInstance, weakEventHandlerClass);
}

// Ensure that event subscription state is properly cached to enable later unsubscribes
[Fact]
public void TestStaticEventWithGC()
public void TestEventSourceCaching()
{
bool eventCalled = false;
void Class_StaticIntPropertyChanged(object sender, int e)
{
eventCalled = (e == 3);
}
void Class_StaticIntPropertyChanged(object sender, int e) => eventCalled = (e == 3);

// Test static codegen-based EventSource caching
Class.StaticIntPropertyChanged += Class_StaticIntPropertyChanged;
GC.Collect(2, GCCollectionMode.Forced, true);
GC.WaitForPendingFinalizers();
Expand All @@ -2481,6 +2510,47 @@ void Class_StaticIntPropertyChanged(object sender, int e)
GC.WaitForPendingFinalizers();
Class.StaticIntProperty = 3;
Assert.True(eventCalled);

// Test dynamic WeakRef-based EventSource caching
eventCalled = false;
static void Subscribe(EventHandler<int> handler) => Singleton.Instance.IntPropertyChanged += handler;
static void Unsubscribe(EventHandler<int> handler) => Singleton.Instance.IntPropertyChanged -= handler;
static void Assign(int value) => Singleton.Instance.IntProperty = value;
Subscribe(Class_StaticIntPropertyChanged);
GC.Collect(2, GCCollectionMode.Forced, true);
GC.WaitForPendingFinalizers();
Unsubscribe(Class_StaticIntPropertyChanged);
Assign(3);
Assert.False(eventCalled);
Subscribe(Class_StaticIntPropertyChanged);
GC.Collect(2, GCCollectionMode.Forced, true);
GC.WaitForPendingFinalizers();
Assign(3);
Assert.True(eventCalled);

// Test that event delegates don't leak when not unsubscribed.
// Test runs into a different function as the finalizer wasn't
// getting triggered otherwise with a weak reference.
(System.WeakReference<Class> weakClassInstance, System.WeakReference<EventHandlerClass> weakEventHandlerClass) =
TestEventDelegateCleanup();
GC.Collect(2, GCCollectionMode.Forced, true);
GC.WaitForPendingFinalizers();
GC.Collect(2, GCCollectionMode.Forced, true);
GC.WaitForPendingFinalizers();
Assert.False(weakClassInstance.TryGetTarget(out _));
Assert.False(weakEventHandlerClass.TryGetTarget(out _));
}

class EventHandlerClass
{
private readonly Action eventCalled;

public EventHandlerClass(Action eventCalled)
{
this.eventCalled = eventCalled;
}

public void IntPropertyChanged(object sender, int e) => eventCalled();
}

#if NET
Expand Down
2 changes: 1 addition & 1 deletion src/WinRT.Runtime/ComWrappersSupport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public static IObjectReference GetObjectReferenceForInterface(IntPtr externalCom
{
if (externalComObject == IntPtr.Zero)
{
return null;
return null;
}

using var unknownRef = ObjectReference<IUnknownVftbl>.FromAbi(externalComObject);
Expand Down
9 changes: 8 additions & 1 deletion src/WinRT.Runtime/Interop/IWeakReferenceSource.net5.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,14 @@ private static int Do_Abi_Resolve(IntPtr thisPtr, Guid* riid, IntPtr* objectRefe
var ThisPtr = _obj.ThisPtr;

ExceptionHelpers.ThrowExceptionForHR(_obj.Vftbl.Resolve(ThisPtr, ref riid, out IntPtr objRef));
return ComWrappersSupport.GetObjectReferenceForInterface(objRef);
try
{
return ComWrappersSupport.GetObjectReferenceForInterface(objRef);
}
finally
{
MarshalInspectable<object>.DisposeAbi(objRef);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,14 @@ public IWeakReference(ObjectReference<Vftbl> obj)
public IObjectReference Resolve(Guid riid)
{
ExceptionHelpers.ThrowExceptionForHR(_obj.Vftbl.Resolve(ThisPtr, ref riid, out IntPtr objRef));
return ComWrappersSupport.GetObjectReferenceForInterface(objRef);
try
{
return ComWrappersSupport.GetObjectReferenceForInterface(objRef);
}
finally
{
MarshalInspectable<object>.DisposeAbi(objRef);
}
}
}
}
Loading