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

Remove circular reference from ShapeDrawable #19347

Merged
merged 6 commits into from Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion eng/pipelines/common/device-tests.yml
Expand Up @@ -29,7 +29,7 @@ stages:
clean: all
displayName: "Android emulator tests"
pool: ${{ parameters.androidPool }}
timeoutInMinutes: 240
timeoutInMinutes: 60
strategy:
matrix:
# create all the variables used for the matrix
Expand Down
@@ -1,14 +1,50 @@
using System;
using System.Threading.Tasks;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Graphics.Platform;
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Platform;
using Microsoft.Maui.Controls;
using System.Reflection;
using Xunit;

namespace Microsoft.Maui.DeviceTests
{
public partial class BoxViewTests
{
MauiShapeView GetNativeBoxView(ShapeViewHandler boxViewHandler) =>
boxViewHandler.PlatformView;

[Fact(DisplayName = "ShapeView Parts Keep Around")]
public async Task ShapeViewPartsKeepAround()
{
var boxView = new BoxView()
{
HeightRequest = 100,
WidthRequest = 200
};

await AttachAndRun<ShapeViewHandler>(boxView, async handler =>
{
var shapeView = GetNativeBoxView(handler);
var renderer = (DirectRenderer)shapeView.Renderer;

GC.Collect();
GC.WaitForPendingFinalizers();
await Task.Yield();

GC.Collect();
GC.WaitForPendingFinalizers();
await Task.Yield();

Assert.NotNull(shapeView.Renderer);
Assert.NotNull(shapeView.Drawable);
Assert.NotNull(renderer.Drawable);

var flags = BindingFlags.NonPublic | BindingFlags.Instance;
var graphicsView = renderer.GetType().GetField("_graphicsView", flags)?.GetValue(renderer) as PlatformGraphicsView;
Assert.NotNull(graphicsView);
});
}
}
}
20 changes: 18 additions & 2 deletions src/Core/src/Graphics/ShapeDrawable.cs
@@ -1,9 +1,11 @@
using System.Numerics;
using System;
using System.Numerics;

namespace Microsoft.Maui.Graphics
{
public class ShapeDrawable : IDrawable
{
WeakReference<IShapeView>? _shapeView;
public ShapeDrawable()
{

Expand All @@ -14,7 +16,21 @@ public ShapeDrawable(IShapeView? shape)
UpdateShapeView(shape);
}

internal IShapeView? ShapeView { get; set; }
internal IShapeView? ShapeView
{
get => _shapeView is not null && _shapeView.TryGetTarget(out var d) ? d : null;
set
{
if (value is null)
{
_shapeView = null;
return;
}

_shapeView = new(value);
}
}

internal WindingMode WindingMode { get; set; }
internal Matrix3x2? RenderTransform { get; set; }

Expand Down
Expand Up @@ -36,6 +36,7 @@ protected static async Task WaitForGC()
await Task.Delay(10);
GC.Collect();
GC.WaitForPendingFinalizers();
await Task.Delay(10);
}
}
}
2 changes: 2 additions & 0 deletions src/Core/tests/DeviceTests/Memory/MemoryTestTypes.cs
Expand Up @@ -9,7 +9,9 @@ public class MemoryTestTypes : IEnumerable<object[]>
{
public IEnumerator<object[]> GetEnumerator()
{
#if !ANDROID
yield return new object[] { (typeof(DatePickerStub), typeof(DatePickerHandler)) };
Copy link
Member Author

Choose a reason for hiding this comment

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

We have more exhaustive tests that validate this inside controls. This test is currently redundant, and the failure currently only happens occasionally in CI. The failure is most likely due to how the test is written and not an actual bug in the bug.

#endif
yield return new object[] { (typeof(EditorStub), typeof(EditorHandler)) };
yield return new object[] { (typeof(EntryStub), typeof(EntryHandler)) };
yield return new object[] { (typeof(SearchBarStub), typeof(SearchBarHandler)) };
Expand Down
28 changes: 13 additions & 15 deletions src/Graphics/src/Graphics/Platforms/iOS/PlatformGraphicsView.cs
Expand Up @@ -8,9 +8,9 @@ namespace Microsoft.Maui.Graphics.Platform
[Register(nameof(PlatformGraphicsView))]
public class PlatformGraphicsView : UIView
{
private WeakReference<IGraphicsRenderer> _renderer;
private IGraphicsRenderer _renderer;
Copy link
Member

Choose a reason for hiding this comment

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

This might still be an issue because PlatformGraphicsView references IGraphicsRenderer which has a reference back to PlatformGraphicsView via the DirectRenderer.GraphicsView property. Setting PlatformGraphicsView.Renderer sets the DirectRenderer.GraphicsView property to the same view, thus a circular reference in all cases.

Copy link
Member Author

Choose a reason for hiding this comment

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

#19347 (comment)
I ran the leak tests locally and they all passed.

private CGColorSpace _colorSpace;
private WeakReference<IDrawable> _drawable;
private IDrawable _drawable;
Comment on lines -11 to +13
Copy link
Member

Choose a reason for hiding this comment

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

If this test case still passes, this is probably good 👍 :

[InlineData(typeof(BoxView))]

There may also be other cases that depend on PlatformGraphicsView: Polygon? Polyline?

Copy link
Member Author

Choose a reason for hiding this comment

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

yea I ran the whole set of "leak" tests locally and they all passed

private CGRect _lastBounds;

public PlatformGraphicsView(CGRect frame, IDrawable drawable = null, IGraphicsRenderer renderer = null) : base(frame)
Expand All @@ -34,34 +34,32 @@ public PlatformGraphicsView(IntPtr aPtr) : base(aPtr)

public IGraphicsRenderer Renderer
{
get => _renderer is not null && _renderer.TryGetTarget(out var r) ? r : null;
get => _renderer;

set
{
var renderer = Renderer;
if (renderer != null)
if (_renderer != null)
{
renderer.Drawable = null;
renderer.GraphicsView = null;
renderer.Dispose();
_renderer.Drawable = null;
_renderer.GraphicsView = null;
_renderer.Dispose();
PureWeen marked this conversation as resolved.
Show resolved Hide resolved
}

renderer = value ?? new DirectRenderer();
_renderer = new(renderer);
_renderer = value ?? new DirectRenderer();

renderer.GraphicsView = this;
renderer.Drawable = Drawable;
_renderer.GraphicsView = this;
_renderer.Drawable = Drawable;
var bounds = Bounds;
renderer.SizeChanged((float)bounds.Width, (float)bounds.Height);
_renderer.SizeChanged((float)bounds.Width, (float)bounds.Height);
}
}

public IDrawable Drawable
{
get => _drawable is not null && _drawable.TryGetTarget(out var d) ? d : null;
get => _drawable;
set
{
_drawable = new(value);
_drawable = value;
if (Renderer is IGraphicsRenderer renderer)
{
renderer.Drawable = value;
Expand Down