Skip to content

Commit

Permalink
Added caching of event registration state to support unsubscribes acr…
Browse files Browse the repository at this point in the history
…oss garbage collections (#861)

* added caching of event registration state to support unsubscribes across GCs

* PR feedback and add missing files

* treat compile warnings as errors

* remove warnings
  • Loading branch information
Scottj1s authored Jun 11, 2021
1 parent af996c1 commit fa038af
Show file tree
Hide file tree
Showing 13 changed files with 327 additions and 110 deletions.
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 @@ -460,7 +471,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 @@ -70,6 +70,7 @@
</ClInclude>
<ClInclude Include="NonAgileClass.h" />
<ClInclude Include="ComImports.h" />
<ClInclude Include="Singleton.h" />
<ClInclude Include="WarningClass.h" />
<ClInclude Include="WarningStatic.h" />
</ItemGroup>
Expand All @@ -84,6 +85,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" />
</ItemGroup>
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" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
Expand All @@ -23,6 +24,7 @@
<ClInclude Include="ManualProjectionTestClasses.h" />
<ClInclude Include="WarningStatic.h" />
<ClInclude Include="WarningClass.h" />
<ClInclude Include="Singleton.h" />
</ItemGroup>
<ItemGroup>
<Midl Include="TestComponentCSharp.idl" />
Expand Down
26 changes: 21 additions & 5 deletions src/Tests/UnitTest/TestComponentCSharp_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2444,15 +2444,14 @@ public void TestCovariance()
Assert.True(TestObject.IterableOfObjectIterablesProperty.SequenceEqual(listOfListOfUris));
}

// 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 @@ -2464,6 +2463,23 @@ 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);
}

#if NET5_0
Expand Down
5 changes: 5 additions & 0 deletions src/WinRT.Runtime/ComWrappersSupport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ public static void MarshalDelegateInvoke<T>(IntPtr thisPtr, Action<T> invoke)

public static IObjectReference GetObjectReferenceForInterface(IntPtr externalComObject)
{
if (externalComObject == IntPtr.Zero)
{
return null;
}

using var unknownRef = ObjectReference<IUnknownVftbl>.FromAbi(externalComObject);

if (IsFreeThreaded())
Expand Down
4 changes: 2 additions & 2 deletions src/WinRT.Runtime/Interop/IWeakReferenceSource.net5.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ namespace WinRT.Interop
{
[WindowsRuntimeType]
[Guid("00000038-0000-0000-C000-000000000046")]
internal interface IWeakReferenceSource
public interface IWeakReferenceSource
{
IWeakReference GetWeakReference();
}

[WindowsRuntimeType]
[Guid("00000037-0000-0000-C000-000000000046")]
internal interface IWeakReference
public interface IWeakReference
{
IObjectReference Resolve(Guid riid);
}
Expand Down
22 changes: 2 additions & 20 deletions src/WinRT.Runtime/Interop/IWeakReferenceSource.netstandard2.0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ namespace WinRT.Interop
{
[WindowsRuntimeType]
[Guid("00000038-0000-0000-C000-000000000046")]
internal interface IWeakReferenceSource
public interface IWeakReferenceSource
{
IWeakReference GetWeakReference();
}

[WindowsRuntimeType]
[Guid("00000037-0000-0000-C000-000000000046")]
internal interface IWeakReference
public interface IWeakReference
{
IObjectReference Resolve(Guid riid);
}
Expand Down Expand Up @@ -61,28 +61,19 @@ internal struct Vftbl
public static readonly Vftbl AbiToProjectionVftable;
public static readonly IntPtr AbiToProjectionVftablePtr;

#if NETSTANDARD2_0
internal delegate int GetWeakReferenceDelegate(IntPtr thisPtr, IntPtr* weakReference);
private static readonly Delegate[] DelegateCache = new Delegate[1];
#endif
static Vftbl()
{
AbiToProjectionVftable = new Vftbl
{
IUnknownVftbl = global::WinRT.Interop.IUnknownVftbl.AbiToProjectionVftbl,
#if NETSTANDARD2_0
_GetWeakReference = Marshal.GetFunctionPointerForDelegate(DelegateCache[0] = new GetWeakReferenceDelegate(Do_Abi_GetWeakReference)).ToPointer(),
#else
_GetWeakReference = (delegate* unmanaged<IntPtr, IntPtr*, int>)&Do_Abi_GetWeakReference
#endif
};
AbiToProjectionVftablePtr = Marshal.AllocHGlobal(Marshal.SizeOf<Vftbl>());
Marshal.StructureToPtr(AbiToProjectionVftable, AbiToProjectionVftablePtr, false);
}

#if !NETSTANDARD2_0
[UnmanagedCallersOnly]
#endif
private static int Do_Abi_GetWeakReference(IntPtr thisPtr, IntPtr* weakReference)
{
*weakReference = default;
Expand Down Expand Up @@ -141,28 +132,19 @@ public struct Vftbl
public static readonly Vftbl AbiToProjectionVftable;
public static readonly IntPtr AbiToProjectionVftablePtr;

#if NETSTANDARD2_0
public delegate int ResolveDelegate(IntPtr thisPtr, Guid* riid, IntPtr* objectReference);
private static readonly Delegate[] DelegateCache = new Delegate[1];
#endif
static Vftbl()
{
AbiToProjectionVftable = new Vftbl
{
IUnknownVftbl = global::WinRT.Interop.IUnknownVftbl.AbiToProjectionVftbl,
#if NETSTANDARD2_0
_Resolve = Marshal.GetFunctionPointerForDelegate(DelegateCache[0] = new ResolveDelegate(Do_Abi_Resolve)).ToPointer(),
#else
_Resolve = (delegate* unmanaged<IntPtr, Guid*, IntPtr*, int>)&Do_Abi_Resolve
#endif
};
AbiToProjectionVftablePtr = Marshal.AllocHGlobal(Marshal.SizeOf<Vftbl>());
Marshal.StructureToPtr(AbiToProjectionVftable, AbiToProjectionVftablePtr, false);
}

#if !NETSTANDARD2_0
[UnmanagedCallersOnly]
#endif
private static int Do_Abi_Resolve(IntPtr thisPtr, Guid* riid, IntPtr* objectReference)
{
IObjectReference _objectReference = default;
Expand Down
4 changes: 3 additions & 1 deletion src/WinRT.Runtime/MatchingRefApiCompatBaseline.net5.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@ TypesMustExist : Type 'System.Numerics.VectorExtensions' does not exist in the r
TypesMustExist : Type 'WinRT.ComWrappersHelper' does not exist in the reference but it does exist in the implementation.
MembersMustExist : Member 'public void WinRT.ComWrappersSupport.RegisterObjectForInterface(System.Object, System.IntPtr, System.Runtime.InteropServices.CreateObjectFlags)' does not exist in the reference but it does exist in the implementation.
MembersMustExist : Member 'protected void WinRT.IObjectReference.AddRef(System.Boolean)' does not exist in the reference but it does exist in the implementation.
Total Issues: 4
TypesMustExist : Type 'WinRT.Interop.IWeakReference' does not exist in the reference but it does exist in the implementation.
TypesMustExist : Type 'WinRT.Interop.IWeakReferenceSource' does not exist in the reference but it does exist in the implementation.
Total Issues: 6
Loading

0 comments on commit fa038af

Please sign in to comment.