Skip to content

Commit 8b97619

Browse files
author
George Wright
authored
Re-enable WeakPtr ThreadChecker and fix associated failures (flutter#12257)
This re-enables thread safety checks for WeakPtr. WeakPtrs can't be used on a thread other than the one the WeakPtrFactory was created on. This fixes the unit tests and adds a getUnsafe() method to WeakPtr to work around the remaining unresolved locations where we are using WeakPtr unsafely.
1 parent c92613b commit 8b97619

13 files changed

Lines changed: 178 additions & 46 deletions

fml/memory/thread_checker.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,7 @@ class ThreadChecker final {
5555
#endif
5656
};
5757

58-
// TODO(chinmaygarde): Re-enable this after auditing all new users of
59-
// fml::WeakPtr.
60-
#if !defined(NDEBUG) && false
58+
#if !defined(NDEBUG)
6159
#define FML_DECLARE_THREAD_CHECKER(c) fml::ThreadChecker c
6260
#define FML_DCHECK_CREATION_THREAD_IS_CURRENT(c) \
6361
FML_DCHECK((c).IsCreationThreadCurrent())

fml/memory/weak_ptr.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,18 @@ class WeakPtr {
8484
return *this ? ptr_ : nullptr;
8585
}
8686

87+
// TODO(gw280): Remove all remaining usages of getUnsafe().
88+
// No new usages of getUnsafe() are allowed.
89+
//
90+
// https://github.com/flutter/flutter/issues/42949
91+
T* getUnsafe() const {
92+
// This is an unsafe method to get access to the raw pointer.
93+
// We still check the flag_ to determine if the pointer is valid
94+
// but callees should note that this WeakPtr could have been
95+
// invalidated on another thread.
96+
return flag_ && flag_->is_valid() ? ptr_ : nullptr;
97+
}
98+
8799
T& operator*() const {
88100
FML_DCHECK_CREATION_THREAD_IS_CURRENT(checker_.checker);
89101
FML_DCHECK(*this);

lib/ui/painting/image_decoder_unittests.cc

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -168,11 +168,15 @@ TEST_F(ImageDecoderFixtureTest, ValidImageResultsInSuccess) {
168168
fml::AutoResetWaitableEvent latch;
169169

170170
std::unique_ptr<IOManager> io_manager;
171-
std::unique_ptr<ImageDecoder> image_decoder;
172171

172+
auto release_io_manager = [&]() {
173+
io_manager.reset();
174+
latch.Signal();
175+
};
173176
auto decode_image = [&]() {
174-
image_decoder = std::make_unique<ImageDecoder>(
175-
runners, loop->GetTaskRunner(), io_manager->GetWeakIOManager());
177+
std::unique_ptr<ImageDecoder> image_decoder =
178+
std::make_unique<ImageDecoder>(runners, loop->GetTaskRunner(),
179+
io_manager->GetWeakIOManager());
176180

177181
ImageDecoder::ImageDescriptor image_descriptor;
178182
image_descriptor.data = OpenFixtureAsSkData("DashInNooglerHat.jpg");
@@ -183,7 +187,7 @@ TEST_F(ImageDecoderFixtureTest, ValidImageResultsInSuccess) {
183187
ImageDecoder::ImageResult callback = [&](SkiaGPUObject<SkImage> image) {
184188
ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread());
185189
ASSERT_TRUE(image.get());
186-
latch.Signal();
190+
runners.GetIOTaskRunner()->PostTask(release_io_manager);
187191
};
188192
image_decoder->Decode(std::move(image_descriptor), callback);
189193
};
@@ -210,12 +214,17 @@ TEST_F(ImageDecoderFixtureTest, ExifDataIsRespectedOnDecode) {
210214
fml::AutoResetWaitableEvent latch;
211215

212216
std::unique_ptr<IOManager> io_manager;
213-
std::unique_ptr<ImageDecoder> image_decoder;
217+
218+
auto release_io_manager = [&]() {
219+
io_manager.reset();
220+
latch.Signal();
221+
};
214222

215223
SkISize decoded_size = SkISize::MakeEmpty();
216224
auto decode_image = [&]() {
217-
image_decoder = std::make_unique<ImageDecoder>(
218-
runners, loop->GetTaskRunner(), io_manager->GetWeakIOManager());
225+
std::unique_ptr<ImageDecoder> image_decoder =
226+
std::make_unique<ImageDecoder>(runners, loop->GetTaskRunner(),
227+
io_manager->GetWeakIOManager());
219228

220229
ImageDecoder::ImageDescriptor image_descriptor;
221230
image_descriptor.data = OpenFixtureAsSkData("Horizontal.jpg");
@@ -227,7 +236,7 @@ TEST_F(ImageDecoderFixtureTest, ExifDataIsRespectedOnDecode) {
227236
ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread());
228237
ASSERT_TRUE(image.get());
229238
decoded_size = image.get()->dimensions();
230-
latch.Signal();
239+
runners.GetIOTaskRunner()->PostTask(release_io_manager);
231240
};
232241
image_decoder->Decode(std::move(image_descriptor), callback);
233242
};
@@ -257,11 +266,16 @@ TEST_F(ImageDecoderFixtureTest, CanDecodeWithoutAGPUContext) {
257266
fml::AutoResetWaitableEvent latch;
258267

259268
std::unique_ptr<IOManager> io_manager;
260-
std::unique_ptr<ImageDecoder> image_decoder;
269+
270+
auto release_io_manager = [&]() {
271+
io_manager.reset();
272+
latch.Signal();
273+
};
261274

262275
auto decode_image = [&]() {
263-
image_decoder = std::make_unique<ImageDecoder>(
264-
runners, loop->GetTaskRunner(), io_manager->GetWeakIOManager());
276+
std::unique_ptr<ImageDecoder> image_decoder =
277+
std::make_unique<ImageDecoder>(runners, loop->GetTaskRunner(),
278+
io_manager->GetWeakIOManager());
265279

266280
ImageDecoder::ImageDescriptor image_descriptor;
267281
image_descriptor.data = OpenFixtureAsSkData("DashInNooglerHat.jpg");
@@ -272,7 +286,7 @@ TEST_F(ImageDecoderFixtureTest, CanDecodeWithoutAGPUContext) {
272286
ImageDecoder::ImageResult callback = [&](SkiaGPUObject<SkImage> image) {
273287
ASSERT_TRUE(runners.GetUITaskRunner()->RunsTasksOnCurrentThread());
274288
ASSERT_TRUE(image.get());
275-
latch.Signal();
289+
runners.GetIOTaskRunner()->PostTask(release_io_manager);
276290
};
277291
image_decoder->Decode(std::move(image_descriptor), callback);
278292
};
@@ -355,6 +369,20 @@ TEST_F(ImageDecoderFixtureTest, CanDecodeWithResizes) {
355369
ASSERT_EQ(decoded_size(100, {}), SkISize::Make(100, 133));
356370
ASSERT_EQ(decoded_size({}, 100), SkISize::Make(75, 100));
357371
ASSERT_EQ(decoded_size(100, 100), SkISize::Make(100, 100));
372+
373+
// Destroy the IO manager
374+
runners.GetIOTaskRunner()->PostTask([&]() {
375+
io_manager.reset();
376+
latch.Signal();
377+
});
378+
latch.Wait();
379+
380+
// Destroy the image decoder
381+
runners.GetUITaskRunner()->PostTask([&]() {
382+
image_decoder.reset();
383+
latch.Signal();
384+
});
385+
latch.Wait();
358386
}
359387

360388
TEST_F(ImageDecoderFixtureTest, CanResizeWithoutDecode) {
@@ -441,6 +469,20 @@ TEST_F(ImageDecoderFixtureTest, CanResizeWithoutDecode) {
441469
ASSERT_EQ(decoded_size(100, {}), SkISize::Make(100, 133));
442470
ASSERT_EQ(decoded_size({}, 100), SkISize::Make(75, 100));
443471
ASSERT_EQ(decoded_size(100, 100), SkISize::Make(100, 100));
472+
473+
// Destroy the IO manager
474+
runners.GetIOTaskRunner()->PostTask([&]() {
475+
io_manager.reset();
476+
latch.Signal();
477+
});
478+
latch.Wait();
479+
480+
// Destroy the image decoder
481+
runners.GetUITaskRunner()->PostTask([&]() {
482+
image_decoder.reset();
483+
latch.Signal();
484+
});
485+
latch.Wait();
444486
}
445487

446488
} // namespace testing

lib/ui/ui_dart_state.cc

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,16 @@ const TaskRunners& UIDartState::GetTaskRunners() const {
7979
}
8080

8181
fml::RefPtr<flutter::SkiaUnrefQueue> UIDartState::GetSkiaUnrefQueue() const {
82-
if (!io_manager_) {
82+
// TODO(gw280): The WeakPtr here asserts that we are derefing it on the
83+
// same thread as it was created on. As we can't guarantee that currently
84+
// we're being called from the IO thread (construction thread), we need
85+
// to use getUnsafe() here to avoid failing the assertion.
86+
//
87+
// https://github.com/flutter/flutter/issues/42946
88+
if (!io_manager_.getUnsafe()) {
8389
return nullptr;
8490
}
85-
return io_manager_->GetSkiaUnrefQueue();
91+
return io_manager_.getUnsafe()->GetSkiaUnrefQueue();
8692
}
8793

8894
void UIDartState::ScheduleMicrotask(Dart_Handle closure) {
@@ -114,6 +120,7 @@ void UIDartState::AddOrRemoveTaskObserver(bool add) {
114120
}
115121

116122
fml::WeakPtr<GrContext> UIDartState::GetResourceContext() const {
123+
FML_DCHECK(task_runners_.GetIOTaskRunner()->RunsTasksOnCurrentThread());
117124
if (!io_manager_) {
118125
return {};
119126
}

lib/ui/ui_dart_state.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "flutter/flow/skia_gpu_object.h"
1515
#include "flutter/fml/build_config.h"
1616
#include "flutter/fml/memory/weak_ptr.h"
17+
#include "flutter/fml/synchronization/waitable_event.h"
1718
#include "flutter/lib/ui/io_manager.h"
1819
#include "flutter/lib/ui/isolate_name_server/isolate_name_server.h"
1920
#include "flutter/lib/ui/painting/image_decoder.h"

shell/common/input_events_unittests.cc

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,14 @@ static void TestSimulatedInputEvents(
127127
});
128128

129129
simulation.wait();
130-
shell.reset();
130+
131+
TaskRunners task_runners = fixture->GetTaskRunnersForFixture();
132+
fml::AutoResetWaitableEvent latch;
133+
task_runners.GetPlatformTaskRunner()->PostTask([&shell, &latch]() mutable {
134+
shell.reset();
135+
latch.Signal();
136+
});
137+
latch.Wait();
131138

132139
// Make sure that all events have been consumed so
133140
// https://github.com/flutter/flutter/issues/40863 won't happen again.

shell/common/persistent_cache_unittests.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ TEST_F(ShellTest, CacheSkSLWorks) {
8787
settings.dump_skp_on_shader_compilation = true;
8888
auto normal_config = RunConfiguration::InferFromSettings(settings);
8989
normal_config.SetEntrypoint("emptyMain");
90-
shell.reset();
90+
DestroyShell(std::move(shell));
9191
shell = CreateShell(settings);
9292
PlatformViewNotifyCreated(shell.get());
9393
RunEngine(shell.get(), std::move(normal_config));
@@ -120,6 +120,7 @@ TEST_F(ShellTest, CacheSkSLWorks) {
120120
return true;
121121
};
122122
fml::VisitFiles(dir.fd(), remove_visitor);
123+
DestroyShell(std::move(shell));
123124
}
124125

125126
} // namespace testing

shell/common/shell.cc

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,13 @@ std::unique_ptr<Shell> Shell::CreateShellOnPlatformThread(
9090
std::promise<fml::WeakPtr<ShellIOManager>> weak_io_manager_promise;
9191
auto weak_io_manager_future = weak_io_manager_promise.get_future();
9292
auto io_task_runner = shell->GetTaskRunners().GetIOTaskRunner();
93+
94+
// TODO(gw280): The WeakPtr here asserts that we are derefing it on the
95+
// same thread as it was created on. We are currently on the IO thread
96+
// inside this lambda but we need to deref the PlatformView, which was
97+
// constructed on the platform thread.
98+
//
99+
// https://github.com/flutter/flutter/issues/42948
93100
fml::TaskRunner::RunNowOrPostTask(
94101
io_task_runner,
95102
[&io_manager_promise, //
@@ -99,7 +106,7 @@ std::unique_ptr<Shell> Shell::CreateShellOnPlatformThread(
99106
]() {
100107
TRACE_EVENT0("flutter", "ShellSetupIOSubsystem");
101108
auto io_manager = std::make_unique<ShellIOManager>(
102-
platform_view->CreateResourceContext(), io_task_runner);
109+
platform_view.getUnsafe()->CreateResourceContext(), io_task_runner);
103110
weak_io_manager_promise.set_value(io_manager->GetWeakPtr());
104111
io_manager_promise.set_value(std::move(io_manager));
105112
});
@@ -277,11 +284,21 @@ Shell::Shell(DartVMRef vm, TaskRunners task_runners, Settings settings)
277284
: task_runners_(std::move(task_runners)),
278285
settings_(std::move(settings)),
279286
vm_(std::move(vm)),
280-
weak_factory_(this) {
287+
weak_factory_(this),
288+
weak_factory_gpu_(nullptr) {
281289
FML_CHECK(vm_) << "Must have access to VM to create a shell.";
282290
FML_DCHECK(task_runners_.IsValid());
283291
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
284292

293+
// Generate a WeakPtrFactory for use with the GPU thread. This does not need
294+
// to wait on a latch because it can only ever be used from the GPU thread
295+
// from this class, so we have ordering guarantees.
296+
fml::TaskRunner::RunNowOrPostTask(
297+
task_runners_.GetGPUTaskRunner(), fml::MakeCopyable([this]() mutable {
298+
this->weak_factory_gpu_ =
299+
std::make_unique<fml::WeakPtrFactory<Shell>>(this);
300+
}));
301+
285302
// Install service protocol handlers.
286303

287304
service_protocol_handlers_[ServiceProtocol::kScreenshotExtensionName] = {
@@ -331,11 +348,13 @@ Shell::~Shell() {
331348

332349
fml::TaskRunner::RunNowOrPostTask(
333350
task_runners_.GetGPUTaskRunner(),
334-
fml::MakeCopyable(
335-
[rasterizer = std::move(rasterizer_), &gpu_latch]() mutable {
336-
rasterizer.reset();
337-
gpu_latch.Signal();
338-
}));
351+
fml::MakeCopyable([rasterizer = std::move(rasterizer_),
352+
weak_factory_gpu = std::move(weak_factory_gpu_),
353+
&gpu_latch]() mutable {
354+
rasterizer.reset();
355+
weak_factory_gpu.reset();
356+
gpu_latch.Signal();
357+
}));
339358
gpu_latch.Wait();
340359

341360
fml::TaskRunner::RunNowOrPostTask(
@@ -393,9 +412,6 @@ void Shell::RunEngine(RunConfiguration run_configuration,
393412
FML_DCHECK(is_setup_);
394413
FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
395414

396-
if (!weak_engine_) {
397-
result(Engine::RunStatus::Failure);
398-
}
399415
fml::TaskRunner::RunNowOrPostTask(
400416
task_runners_.GetUITaskRunner(),
401417
fml::MakeCopyable(
@@ -486,8 +502,13 @@ bool Shell::Setup(std::unique_ptr<PlatformView> platform_view,
486502
PersistentCache::GetCacheForProcess()->SetIsDumpingSkp(
487503
settings_.dump_skp_on_shader_compilation);
488504

489-
// Shell::Setup is running on the UI thread so we can do the following.
490-
display_refresh_rate_ = weak_engine_->GetDisplayRefreshRate();
505+
// TODO(gw280): The WeakPtr here asserts that we are derefing it on the
506+
// same thread as it was created on. Shell is constructed on the platform
507+
// thread but we need to call into the Engine on the UI thread, so we need
508+
// to use getUnsafe() here to avoid failing the assertion.
509+
//
510+
// https://github.com/flutter/flutter/issues/42947
511+
display_refresh_rate_ = weak_engine_.getUnsafe()->GetDisplayRefreshRate();
491512

492513
return true;
493514
}
@@ -1069,7 +1090,7 @@ void Shell::OnFrameRasterized(const FrameTiming& timing) {
10691090
// never be reported until the next animation starts.
10701091
frame_timings_report_scheduled_ = true;
10711092
task_runners_.GetGPUTaskRunner()->PostDelayedTask(
1072-
[self = weak_factory_.GetWeakPtr()]() {
1093+
[self = weak_factory_gpu_->GetWeakPtr()]() {
10731094
if (!self.get()) {
10741095
return;
10751096
}

shell/common/shell.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ enum class DartErrorCode {
7474
/// platform task runner. In case the embedder wants to directly access a shell
7575
/// subcomponent, it is the embedder's responsibility to acquire a weak pointer
7676
/// to that component and post a task to the task runner used by the component
77-
/// to access its methods.
77+
/// to access its methods. The shell must also be destroyed on the platform
78+
/// task runner.
7879
///
7980
/// There is no explicit API to bootstrap and shutdown the Dart VM. The first
8081
/// instance of the shell in the process bootstraps the Dart VM and the
@@ -516,6 +517,10 @@ class Shell final : public PlatformView::Delegate,
516517

517518
fml::WeakPtrFactory<Shell> weak_factory_;
518519

520+
// For accessing the Shell via the GPU thread, necessary for various
521+
// rasterizer callbacks.
522+
std::unique_ptr<fml::WeakPtrFactory<Shell>> weak_factory_gpu_;
523+
519524
friend class testing::ShellTest;
520525

521526
FML_DISALLOW_COPY_AND_ASSIGN(Shell);

shell/common/shell_benchmarks.cc

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,15 @@ static void StartupAndShutdownShell(benchmark::State& state,
7878

7979
{
8080
benchmarking::ScopedPauseTiming pause(state, !measure_shutdown);
81-
shell.reset(); // Shutdown is synchronous.
81+
// Shutdown must occur synchronously on the platform thread.
82+
fml::AutoResetWaitableEvent latch;
83+
fml::TaskRunner::RunNowOrPostTask(
84+
thread_host->platform_thread->GetTaskRunner(),
85+
[&shell, &latch]() mutable {
86+
shell.reset();
87+
latch.Signal();
88+
});
89+
latch.Wait();
8290
thread_host.reset();
8391
}
8492

0 commit comments

Comments
 (0)