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

Do not serialize on UI/Raster threads for Shell::OnCreatePlatformView #29145

Merged
merged 5 commits into from
Oct 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
23 changes: 23 additions & 0 deletions shell/common/fixtures/shell_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,29 @@ void nativeReportTimingsCallback(List<int> timings) native 'NativeReportTimingsC
void nativeOnBeginFrame(int microseconds) native 'NativeOnBeginFrame';
void nativeOnPointerDataPacket(List<int> sequences) native 'NativeOnPointerDataPacket';


@pragma('vm:entry-point')
void drawFrames() {
// Wait for native to tell us to start going.
notifyNative();

PlatformDispatcher.instance.onBeginFrame = (Duration beginTime) {
final SceneBuilder builder = SceneBuilder();
final PictureRecorder recorder = PictureRecorder();
final Canvas canvas = Canvas(recorder);
canvas.drawPaint(Paint()..color = const Color(0xFFABCDEF));
final Picture picture = recorder.endRecording();
builder.addPicture(Offset.zero, picture);

final Scene scene = builder.build();
window.render(scene);

scene.dispose();
picture.dispose();
};
PlatformDispatcher.instance.scheduleFrame();
}

@pragma('vm:entry-point')
void reportTimingsMain() {
PlatformDispatcher.instance.onReportTimings = (List<FrameTiming> timings) {
Expand Down
90 changes: 30 additions & 60 deletions shell/common/shell.cc
Original file line number Diff line number Diff line change
Expand Up @@ -733,16 +733,11 @@ void Shell::OnPlatformViewCreated(std::unique_ptr<Surface> surface) {
const bool should_post_raster_task =
!task_runners_.GetRasterTaskRunner()->RunsTasksOnCurrentThread();

// Note:
// This is a synchronous operation because certain platforms depend on
// setup/suspension of all activities that may be interacting with the GPU in
// a synchronous fashion.
fml::AutoResetWaitableEvent latch;
auto raster_task =
fml::MakeCopyable([&waiting_for_first_frame = waiting_for_first_frame_,
rasterizer = rasterizer_->GetWeakPtr(), //
surface = std::move(surface), //
&latch]() mutable {
surface = std::move(surface)]() mutable {
if (rasterizer) {
// Enables the thread merger which may be used by the external view
// embedder.
Expand All @@ -751,29 +746,15 @@ void Shell::OnPlatformViewCreated(std::unique_ptr<Surface> surface) {
}

waiting_for_first_frame.store(true);

// Step 3: All done. Signal the latch that the platform thread is
// waiting on.
latch.Signal();
});

auto ui_task = [engine = engine_->GetWeakPtr(), //
raster_task_runner = task_runners_.GetRasterTaskRunner(), //
raster_task, should_post_raster_task,
&latch //
] {
// TODO(91717): This probably isn't necessary. The engine should be able to
// handle things here via normal lifecycle messages.
// https://github.com/flutter/flutter/issues/91717
auto ui_task = [engine = engine_->GetWeakPtr()] {
if (engine) {
engine->OnOutputSurfaceCreated();
}
// Step 2: Next, tell the raster thread that it should create a surface for
// its rasterizer.
if (should_post_raster_task) {
fml::TaskRunner::RunNowOrPostTask(raster_task_runner, raster_task);
} else {
// See comment on should_post_raster_task, in this case we just unblock
// the platform thread.
latch.Signal();
}
};

// Threading: Capture platform view by raw pointer and not the weak pointer.
Expand All @@ -786,7 +767,9 @@ void Shell::OnPlatformViewCreated(std::unique_ptr<Surface> surface) {

auto io_task = [io_manager = io_manager_->GetWeakPtr(), platform_view,
ui_task_runner = task_runners_.GetUITaskRunner(), ui_task,
shared_resource_context = shared_resource_context_] {
shared_resource_context = shared_resource_context_,
raster_task_runner = task_runners_.GetRasterTaskRunner(),
raster_task, should_post_raster_task, &latch] {
if (io_manager && !io_manager->GetResourceContext()) {
sk_sp<GrDirectContext> resource_context;
if (shared_resource_context) {
Expand All @@ -796,9 +779,16 @@ void Shell::OnPlatformViewCreated(std::unique_ptr<Surface> surface) {
}
io_manager->NotifyResourceContextAvailable(resource_context);
}
// Step 1: Next, post a task on the UI thread to tell the engine that it has
// Step 1: Post a task on the UI thread to tell the engine that it has
// an output surface.
fml::TaskRunner::RunNowOrPostTask(ui_task_runner, ui_task);

// Step 2: Tell the raster thread that it should create a surface for
// its rasterizer.
if (should_post_raster_task) {
fml::TaskRunner::RunNowOrPostTask(raster_task_runner, raster_task);
}
latch.Signal();
};

fml::TaskRunner::RunNowOrPostTask(task_runners_.GetIOTaskRunner(), io_task);
Expand Down Expand Up @@ -826,26 +816,15 @@ void Shell::OnPlatformViewDestroyed() {
// configuration is changed by a task, and the assumption is no longer true.
//
// This incorrect assumption can lead to deadlock.
// See `should_post_raster_task` for more.
rasterizer_->DisableThreadMergerIfNeeded();

// The normal flow executed by this method is that the platform thread is
// starting the sequence and waiting on the latch. Later the UI thread posts
// raster_task to the raster thread triggers signaling the latch(on the IO
// thread). If the raster and the platform threads are the same this results
// in a deadlock as the raster_task will never be posted to platform/raster
// thread that is blocked on a latch. To avoid the described deadlock, if the
// raster and the platform threads are the same, should_post_raster_task will
// be false, and then instead of posting a task to the raster thread, the ui
// thread just signals the latch and the platform/raster thread follows with
// executing raster_task.
const bool should_post_raster_task =
!task_runners_.GetRasterTaskRunner()->RunsTasksOnCurrentThread();

// Note:
// This is a synchronous operation because certain platforms depend on
// setup/suspension of all activities that may be interacting with the GPU in
// a synchronous fashion.
// The UI thread does not need to be serialized here - there is sufficient
// guardrailing in the rasterizer to allow the UI thread to post work to it
// even after the surface has been torn down.

fml::AutoResetWaitableEvent latch;

Expand All @@ -855,7 +834,7 @@ void Shell::OnPlatformViewDestroyed() {
io_manager->GetIsGpuDisabledSyncSwitch()->Execute(
fml::SyncSwitch::Handlers().SetIfFalse(
[&] { io_manager->GetSkiaUnrefQueue()->Drain(); }));
// Step 3: All done. Signal the latch that the platform thread is waiting
// Step 4: All done. Signal the latch that the platform thread is waiting
// on.
latch.Signal();
};
Expand All @@ -870,37 +849,28 @@ void Shell::OnPlatformViewDestroyed() {
rasterizer->EnableThreadMergerIfNeeded();
rasterizer->Teardown();
}
// Step 2: Next, tell the IO thread to complete its remaining work.
// Step 3: Tell the IO thread to complete its remaining work.
fml::TaskRunner::RunNowOrPostTask(io_task_runner, io_task);
};

auto ui_task = [engine = engine_->GetWeakPtr(),
raster_task_runner = task_runners_.GetRasterTaskRunner(),
raster_task, should_post_raster_task, &latch]() {
// TODO(91717): This probably isn't necessary. The engine should be able to
// handle things here via normal lifecycle messages.
// https://github.com/flutter/flutter/issues/91717
auto ui_task = [engine = engine_->GetWeakPtr()]() {
if (engine) {
engine->OnOutputSurfaceDestroyed();
}
if (should_post_raster_task) {
fml::TaskRunner::RunNowOrPostTask(raster_task_runner, raster_task);
} else {
// See comment on should_post_raster_task, in this case we just unblock
// the platform thread.
latch.Signal();
}
};

// Step 0: Post a task onto the UI thread to tell the engine that its output
// Step 1: Post a task onto the UI thread to tell the engine that its output
// surface is about to go away.
fml::TaskRunner::RunNowOrPostTask(task_runners_.GetUITaskRunner(), ui_task);

// Step 2: Post a task to the Raster thread (possibly this thread) to tell the
// rasterizer the output surface is going away.
fml::TaskRunner::RunNowOrPostTask(task_runners_.GetRasterTaskRunner(),
raster_task);
latch.Wait();
if (!should_post_raster_task) {
// See comment on should_post_raster_task, in this case the raster_task
// wasn't executed, and we just run it here as the platform thread
// is the raster thread.
raster_task();
latch.Wait();
}
}

// |PlatformView::Delegate|
Expand Down
62 changes: 62 additions & 0 deletions shell/common/shell_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3098,5 +3098,67 @@ TEST_F(ShellTest, PrefetchDefaultFontManager) {
DestroyShell(std::move(shell));
}

TEST_F(ShellTest, OnPlatformViewCreatedWhenUIThreadIsBusy) {
// This test will deadlock if the threading logic in
// Shell::OnCreatePlatformView is wrong.
auto settings = CreateSettingsForFixture();
auto shell = CreateShell(std::move(settings));

fml::AutoResetWaitableEvent latch;
fml::TaskRunner::RunNowOrPostTask(shell->GetTaskRunners().GetUITaskRunner(),
[&latch]() { latch.Wait(); });

ShellTest::PlatformViewNotifyCreated(shell.get());
latch.Signal();

DestroyShell(std::move(shell));
}

TEST_F(ShellTest, UIWorkAfterOnPlatformViewDestroyed) {
auto settings = CreateSettingsForFixture();
auto shell = CreateShell(std::move(settings));
auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("drawFrames");

fml::AutoResetWaitableEvent latch;
fml::AutoResetWaitableEvent notify_native_latch;
AddNativeCallback("NotifyNative", CREATE_NATIVE_ENTRY([&](auto args) {
notify_native_latch.Signal();
latch.Wait();
}));

RunEngine(shell.get(), std::move(configuration));
// Wait to make sure we get called back from Dart and thus have latched
// the UI thread before we create/destroy the platform view.
notify_native_latch.Wait();

ShellTest::PlatformViewNotifyCreated(shell.get());

fml::AutoResetWaitableEvent destroy_latch;
fml::TaskRunner::RunNowOrPostTask(
shell->GetTaskRunners().GetPlatformTaskRunner(),
[&shell, &destroy_latch]() {
shell->GetPlatformView()->NotifyDestroyed();
destroy_latch.Signal();
});

destroy_latch.Wait();

// Unlatch the UI thread and let it send us a scene to render.
latch.Signal();

// Flush the UI task runner to make sure we process the render/scheduleFrame
// request.
fml::AutoResetWaitableEvent ui_flush_latch;
fml::TaskRunner::RunNowOrPostTask(shell->GetTaskRunners().GetUITaskRunner(),
[&ui_flush_latch]() {
FML_LOG(ERROR) << "123";
ui_flush_latch.Signal();
});
ui_flush_latch.Wait();

DestroyShell(std::move(shell));
}

} // namespace testing
} // namespace flutter