Skip to content

Commit a400e79

Browse files
knopploic-sharma
andauthored
[Embedder] Only call removeview callback when raster thread is done with the view (flutter#164571)
Fixes flutter#164564 (comment) This would ensure that raster thread is completely done with the view, i.e. it won't try to use the opengl context, which might be associated with view window. So the client can know for sure, that when the callback returns, it is safe to destroy the view and container window. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --------- Co-authored-by: Loïc Sharma <[email protected]>
1 parent f9dd5c0 commit a400e79

File tree

3 files changed

+115
-12
lines changed

3 files changed

+115
-12
lines changed

engine/src/flutter/shell/common/shell.cc

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2101,20 +2101,21 @@ void Shell::OnPlatformViewRemoveView(int64_t view_id,
21012101
rasterizer = rasterizer_->GetWeakPtr(), //
21022102
view_id, //
21032103
callback = std::move(callback) //
2104-
] {
2104+
]() mutable {
2105+
bool removed = false;
21052106
if (engine) {
2106-
bool removed = engine->RemoveView(view_id);
2107-
callback(removed);
2107+
removed = engine->RemoveView(view_id);
21082108
}
2109-
// Don't wait for the raster task here, which only cleans up memory and
2110-
// does not affect functionality. Make sure it is done after Dart
2111-
// removes the view to avoid receiving another rasterization request
2112-
// that adds back the view record.
2113-
task_runners.GetRasterTaskRunner()->PostTask([rasterizer, view_id]() {
2114-
if (rasterizer) {
2115-
rasterizer->CollectView(view_id);
2116-
}
2117-
});
2109+
task_runners.GetRasterTaskRunner()->PostTask(
2110+
[rasterizer, view_id, callback = std::move(callback), removed]() {
2111+
if (rasterizer) {
2112+
rasterizer->CollectView(view_id);
2113+
}
2114+
// Only call the callback after it is known for certain that the
2115+
// raster thread will not try to use resources associated with
2116+
// the view.
2117+
callback(removed);
2118+
});
21182119
});
21192120
}
21202121

engine/src/flutter/shell/platform/embedder/fixtures/main.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1511,6 +1511,12 @@ void window_metrics_event_all_view_ids() {
15111511
signalNativeTest();
15121512
}
15131513

1514+
@pragma('vm:entry-point')
1515+
// ignore: non_constant_identifier_names
1516+
void remove_view_callback_too_early() {
1517+
signalNativeTest();
1518+
}
1519+
15141520
@pragma('vm:entry-point')
15151521
// ignore: non_constant_identifier_names
15161522
Future<void> channel_listener_response() async {

engine/src/flutter/shell/platform/embedder/tests/embedder_unittests.cc

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1649,6 +1649,102 @@ TEST_F(EmbedderTest, CanRemoveView) {
16491649
ASSERT_EQ(message, "View IDs: [0]");
16501650
}
16511651

1652+
// Regression test for:
1653+
// https://github.com/flutter/flutter/issues/164564
1654+
TEST_F(EmbedderTest, RemoveViewCallbackIsInvokedAfterRasterThreadIsDone) {
1655+
auto& context = GetEmbedderContext<EmbedderTestContextSoftware>();
1656+
EmbedderConfigBuilder builder(context);
1657+
std::mutex engine_mutex;
1658+
UniqueEngine engine;
1659+
auto render_thread = CreateNewThread("custom_render_thread");
1660+
EmbedderTestTaskRunner render_task_runner(
1661+
render_thread, [&](FlutterTask task) {
1662+
std::scoped_lock engine_lock(engine_mutex);
1663+
if (engine.is_valid()) {
1664+
ASSERT_EQ(FlutterEngineRunTask(engine.get(), &task), kSuccess);
1665+
}
1666+
});
1667+
1668+
builder.SetSurface(SkISize::Make(1, 1));
1669+
builder.SetDartEntrypoint("remove_view_callback_too_early");
1670+
builder.SetRenderTaskRunner(
1671+
&render_task_runner.GetFlutterTaskRunnerDescription());
1672+
1673+
fml::AutoResetWaitableEvent ready_latch;
1674+
context.AddNativeCallback(
1675+
"SignalNativeTest",
1676+
CREATE_NATIVE_ENTRY(
1677+
[&ready_latch](Dart_NativeArguments args) { ready_latch.Signal(); }));
1678+
1679+
{
1680+
std::scoped_lock lock(engine_mutex);
1681+
engine = builder.InitializeEngine();
1682+
}
1683+
ASSERT_EQ(FlutterEngineRunInitialized(engine.get()), kSuccess);
1684+
ASSERT_TRUE(engine.is_valid());
1685+
1686+
ready_latch.Wait();
1687+
1688+
fml::AutoResetWaitableEvent add_view_latch;
1689+
// Add view 123.
1690+
FlutterWindowMetricsEvent metrics = {};
1691+
metrics.struct_size = sizeof(FlutterWindowMetricsEvent);
1692+
metrics.width = 800;
1693+
metrics.height = 600;
1694+
metrics.pixel_ratio = 1.0;
1695+
metrics.view_id = 123;
1696+
1697+
FlutterAddViewInfo add_info = {};
1698+
add_info.struct_size = sizeof(FlutterAddViewInfo);
1699+
add_info.view_id = 123;
1700+
add_info.view_metrics = &metrics;
1701+
add_info.user_data = &add_view_latch;
1702+
add_info.add_view_callback = [](const FlutterAddViewResult* result) {
1703+
ASSERT_TRUE(result->added);
1704+
auto add_view_latch =
1705+
reinterpret_cast<fml::AutoResetWaitableEvent*>(result->user_data);
1706+
add_view_latch->Signal();
1707+
};
1708+
ASSERT_EQ(FlutterEngineAddView(engine.get(), &add_info), kSuccess);
1709+
add_view_latch.Wait();
1710+
1711+
std::atomic_bool view_available = true;
1712+
1713+
// Simulate pending rasterization task scheduled before view removal request
1714+
// that accesses view resources.
1715+
fml::AutoResetWaitableEvent raster_thread_latch;
1716+
render_thread->PostTask([&] {
1717+
std::this_thread::sleep_for(std::chrono::milliseconds(50));
1718+
// View must be available.
1719+
EXPECT_TRUE(view_available);
1720+
raster_thread_latch.Signal();
1721+
});
1722+
1723+
fml::AutoResetWaitableEvent remove_view_latch;
1724+
FlutterRemoveViewInfo remove_view_info = {};
1725+
remove_view_info.struct_size = sizeof(FlutterRemoveViewInfo);
1726+
remove_view_info.view_id = 123;
1727+
remove_view_info.user_data = &remove_view_latch;
1728+
remove_view_info.remove_view_callback =
1729+
[](const FlutterRemoveViewResult* result) {
1730+
ASSERT_TRUE(result->removed);
1731+
auto remove_view_latch =
1732+
reinterpret_cast<fml::AutoResetWaitableEvent*>(result->user_data);
1733+
remove_view_latch->Signal();
1734+
};
1735+
1736+
// Remove the view and wait until the callback is called.
1737+
ASSERT_EQ(FlutterEngineRemoveView(engine.get(), &remove_view_info), kSuccess);
1738+
remove_view_latch.Wait();
1739+
1740+
// After FlutterEngineRemoveViewCallback is called it should be safe to
1741+
// remove view - raster thread must not be accessing any view resources.
1742+
view_available = false;
1743+
raster_thread_latch.Wait();
1744+
1745+
FlutterEngineDeinitialize(engine.get());
1746+
}
1747+
16521748
//------------------------------------------------------------------------------
16531749
/// The implicit view is a special view that the engine and framework assume
16541750
/// can *always* be rendered to. Test that this view cannot be removed.

0 commit comments

Comments
 (0)