-
Notifications
You must be signed in to change notification settings - Fork 6k
Conversation
lib/ui/fixtures/ui_test.dart
Outdated
} | ||
|
||
void _captureImageAndPicture(Image image, Picture picture) native 'CaptureImageAndPicture'; | ||
Future<void> _done() native 'Done'; | ||
void _encodeImage(Image i, int format, void Function(Uint8List result)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd leave these adjacent to the Dart code above that calls them.
(Even better would be to split these different fixtures into separate files, but this PR is probably big enough already)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, bad merge. Fixed.
// Copyright 2013 The Flutter Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
// FLUTTER_NOLINT |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When we add new files, we should have the lints enabled.
} | ||
|
||
TEST_F(ShellTest, ImageReleasedAfterFrame) { | ||
fml::AutoResetWaitableEvent message_latch; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might be getting a lint for the lifetime of this? If so you can make it a global or (I think) a field of a new subclass of ShellTest
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are very smart.
@@ -33,11 +37,22 @@ class LayerTreeTest : public CanvasTest { | |||
CompositorContext::ScopedFrame& frame() { return *scoped_frame_.get(); } | |||
const SkMatrix& root_transform() { return root_transform_; } | |||
|
|||
void OnCompositorEndFrame(size_t freed_hint) override { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// |CompositorContext::Delegate|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done and done
last_freed_hint_ = freed_hint; | ||
} | ||
|
||
fml::Milliseconds GetFrameBudget() override { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
@@ -528,10 +529,14 @@ class Shell final : public PlatformView::Delegate, | |||
void OnFrameRasterized(const FrameTiming&) override; | |||
|
|||
// |Rasterizer::Delegate| | |||
fml::Milliseconds GetFrameBudget() override; | |||
fml::TimePoint GetLatestFrameTargetTime() const override; | |||
|
|||
// |Rasterizer::Delegate| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this stale?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure which line you're referring to - I moved this member up from the Rasterizer delegate to the CompositorContext delegate (Which the rasterizer delegate is now a subclass of).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, okay. I was just confused by the two // |...|
comments, and thought one of them might be extra.
std::shared_ptr<DartIsolate> root_isolate = root_isolate_.lock(); | ||
if (!root_isolate) { | ||
return false; | ||
} | ||
|
||
tonic::DartState::Scope scope(root_isolate); | ||
|
||
Dart_HintFreed(freed_hint); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the ordering of these two calls is important, then a comment here explaining that will probably be helpful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
It looks like the new test is failing in the presubmit checks. |
I fixed up a merge cconflict, but I'm wondering if this test might be flaky. I trie drunning it 50 times locally and it did not fail, but it did fail on CI once. |
lib/ui/fixtures/ui_test.dart
Outdated
final Scene scene = builder.build(); | ||
window.render(scene); | ||
scene.dispose(); | ||
window.onBeginFrame = (Duration duration) => _done(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should _done
go on window.onDrawFrame
. The docs say that that will flush the microtask queue before running it, which may reduce flakiness?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like that worked - just had to put it in onDrawFrame on the next frame.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm but this could probably also use a review from @chinmaygarde
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The access to the engine in the shell delegate callback is unsafe. Also, I am still trying to convince myself that there is no data race or race condition in test (I am not sure yet but will keep trying to convince myself)
flow/layers/layer_tree_unittests.cc
Outdated
@@ -15,11 +15,15 @@ | |||
namespace flutter { | |||
namespace testing { | |||
|
|||
class LayerTreeTest : public CanvasTest { | |||
class CompositorDelegate : public CompositorContext::Delegate { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you mean to leave this in? It is not used and this class cannot be instantiated anyway since it is virtual. You may have wanted to use this in the fixture but instead opted to subclass it directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, will delete.
}; | ||
|
||
TEST_F(ImageReleaseTest, ImageReleasedAfterFrame) { | ||
auto nativeCaptureImageAndPicture = [&](Dart_NativeArguments args) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very minor nit: We don't use camelCase for locals.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here and elsewhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
|
||
AddNativeCallback("CaptureImageAndPicture", | ||
CREATE_NATIVE_ENTRY(nativeCaptureImageAndPicture)); | ||
AddNativeCallback("Done", CREATE_NATIVE_ENTRY(nativeDone)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just "Done" is a bit too terse IMO. How about onBeginFrameDone
since that is when you are making the call in the test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done ;)
shell/common/shell.cc
Outdated
@@ -1205,6 +1205,12 @@ void Shell::OnFrameRasterized(const FrameTiming& timing) { | |||
} | |||
} | |||
|
|||
void Shell::OnCompositorEndFrame(size_t freed_hint) { | |||
if (engine_) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shell APIs are only safely accessible on the platform task runner. The engine OTOH can only be accessed on the UI task runner. I believe you are on the raster task runner here. So this this API is not resilient to death of the shell, accesses the engine in a thread unsafe manner and is a non-standard shell method to boot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Its fine for some methods to have different threading restrictions. But when they veer from convention, please document the same and also add an FML_DCHECK. In this case though, it just not thread-safe so lets fix that first.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. I want the shell to be the delegate here. This was originally living on the raster task runner.
I should be ok as long as I just jump to the UI task runner for this block with a weak ptr to the engine right?
Do I need to get the weak ptr on the platform task runner?
public: | ||
/// Called at the end of a frame with approximately how many bytes mightbe | ||
/// freed if a GC ran now. | ||
virtual void OnCompositorEndFrame(size_t freed_hint) = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please document the threading restrictions that implementors must adhere to. I believe the shell is not thread-unsafe. Details below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what thread restrictions to document here. This is just called when there's a hint available for bytes freed - shouldn't it be up to the implementer what threads it uses when processing that information?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant whoever implements the delegate protocol will receive events on the raster thread. This might be unexpected to the implementer.
message_latch.Wait(); | ||
|
||
ASSERT_TRUE(current_picture_); | ||
ASSERT_TRUE(current_image_); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am trying to convince myself this is not a data race. The latch will be signalled when the frame has begun. However, the current image will be set as the frame is constructed. Not sure how this is working.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My thought was that it's not because Dart gives us to us in the capture call (while we're still latched), and then we unlatch when Dart calls onBeginFrameDone (by which point it has already let go of its references).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I was thinking this was onBeginFrame instead of onBeginFrameDone. I think that makes sense now. Thanks.
I've updated the test to avoid racing the garbage collector - I'm not sure this is quite the race @chinmaygarde was concerned about, but it should be less flaky now. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure this is quite the race ....
No that was not an actual issue but a misreading of the way the test was setup. I clarified it. Thanks and my bad.
public: | ||
/// Called at the end of a frame with approximately how many bytes mightbe | ||
/// freed if a GC ran now. | ||
virtual void OnCompositorEndFrame(size_t freed_hint) = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant whoever implements the delegate protocol will receive events on the raster thread. This might be unexpected to the implementer.
message_latch.Wait(); | ||
|
||
ASSERT_TRUE(current_picture_); | ||
ASSERT_TRUE(current_image_); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I was thinking this was onBeginFrame instead of onBeginFrameDone. I think that makes sense now. Thanks.
I'm removing the test - it's flaky, and efforts to make it not be flaky are making it pass without these changes. I don't think it's accurately representing the state of the framework, and it really wants to be a benchmark about memory usage and collection. |
We still have coverage that |
* Hint the VM when a layer or picture goes out of scope
std::unique_ptr<RasterCacheResult> image; | ||
}; | ||
|
||
template <class Cache> | ||
static void SweepOneCacheAfterFrame(Cache& cache) { | ||
static size_t SweepOneCacheAfterFrame(Cache& cache) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to document what size is returned from this function. Currently, it seems to only return the layer external size, and one might ask why not also include the image size of the raster cache. Is it because we only want to report the memory size that's managed by Dart VM so the SkImage memory size is irrelevant?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The image size is included in the layer external size. The SkImage size is really of primary importance here.
However, we have to figure out how to get that out of what the Dart VM sees - @jason-simmons has a patch open to remove that, but we'd still want it here.
This reverts commit 3930ac1.
Description
Uses the new Dart_HintFreed API to notify the VM when the engine is done with a layer tree, and that a collection could potentially free memory.
This assumes https://dart-review.googlesource.com/c/sdk/+/150503 will land.
/cc @a-siva @rmacnak-google @chinmaygarde
Related Issues
flutter/flutter#56482
Tests
I added the following tests:
Unit tests that PictureLayer reports what it should
Unit test that showing an image, showing a new frame, and then notifying idle (which normally would happen in a "regular app" as frames are rendered) releases the SkImage/SkPicture.
Breaking Change
Did any tests fail when you ran them? Please read handling breaking changes.