Skip to content

Commit b43efd6

Browse files
committed
During image decoding, avoid using smart pointers for DartWrappables that cross thread-boundaries.
In flutter#19843, a regression was introduced where an image descriptor object that has a Dart peer could be collected on a concurrent task runner thread. Collection of such object needs to enter isolate scope which might still be active on the UI thread. [A comment in the original commit explicitly mentions][1] this and attempts to achieve the collection of the Dart wrappable on the UI task runner. However, the author missed that the object was present in the captures of a copyable closure. These captures may be released on any of the participating threads. To avoid the the indeterminism of the smart pointer being dropped on the concurrent task runner thread, this patch resorts to manually reference counting the descriptor. Fixes flutter/flutter#72144 [1]: https://github.com/flutter/engine/pull/19843/files#diff-a3034d4a06133381b6b636d841ec98cb7cfce640f316dda9e71eda4d9e7872f6R227
1 parent 52901f2 commit b43efd6

File tree

3 files changed

+54
-37
lines changed

3 files changed

+54
-37
lines changed

lib/ui/painting/image_decoder.cc

Lines changed: 47 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ static sk_sp<SkImage> ResizeRasterImage(sk_sp<SkImage> image,
7575
}
7676

7777
static sk_sp<SkImage> ImageFromDecompressedData(
78-
fml::RefPtr<ImageDescriptor> descriptor,
78+
ImageDescriptor* descriptor,
7979
uint32_t target_width,
8080
uint32_t target_height,
8181
const fml::tracing::TraceFlow& flow) {
@@ -98,7 +98,7 @@ static sk_sp<SkImage> ImageFromDecompressedData(
9898
SkISize::Make(target_width, target_height), flow);
9999
}
100100

101-
sk_sp<SkImage> ImageFromCompressedData(fml::RefPtr<ImageDescriptor> descriptor,
101+
sk_sp<SkImage> ImageFromCompressedData(ImageDescriptor* descriptor,
102102
uint32_t target_width,
103103
uint32_t target_height,
104104
const fml::tracing::TraceFlow& flow) {
@@ -216,38 +216,55 @@ static SkiaGPUObject<SkImage> UploadRasterImage(
216216
return result;
217217
}
218218

219-
void ImageDecoder::Decode(fml::RefPtr<ImageDescriptor> descriptor,
219+
void ImageDecoder::Decode(fml::RefPtr<ImageDescriptor> descriptor_ref_ptr,
220220
uint32_t target_width,
221221
uint32_t target_height,
222222
const ImageResult& callback) {
223223
TRACE_EVENT0("flutter", __FUNCTION__);
224224
fml::tracing::TraceFlow flow(__FUNCTION__);
225225

226+
// ImageDescriptors have Dart peers that must be collected on the UI thread.
227+
// However, closures in MakeCopyable below capture the descriptor. The
228+
// captures of copyable closures may be collected on any of the thread
229+
// participating in task execution.
230+
//
231+
// To avoid this issue, we resort to manually reference counting the
232+
// descriptor. Since all task flows invoke the `result` callback, the raw
233+
// descriptor is retained in the beginning and released in the `result`
234+
// callback.
235+
//
236+
// `ImageDecoder::Decode` itself is invoked on the UI thread, so the
237+
// collection of the smart pointer from which we obtained the raw descriptor
238+
// is fine in this scope.
239+
auto raw_descriptor = descriptor_ref_ptr.get();
240+
raw_descriptor->AddRef();
241+
226242
FML_DCHECK(callback);
227243
FML_DCHECK(runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
228244

229245
// Always service the callback (and cleanup the descriptor) on the UI thread.
230-
auto result = [callback, descriptor, ui_runner = runners_.GetUITaskRunner()](
231-
SkiaGPUObject<SkImage> image,
232-
fml::tracing::TraceFlow flow) {
233-
ui_runner->PostTask(
234-
fml::MakeCopyable([callback, descriptor, image = std::move(image),
235-
flow = std::move(flow)]() mutable {
236-
// We are going to terminate the trace flow here. Flows cannot
237-
// terminate without a base trace. Add one explicitly.
238-
TRACE_EVENT0("flutter", "ImageDecodeCallback");
239-
flow.End();
240-
callback(std::move(image));
241-
}));
242-
};
243-
244-
if (!descriptor->data() || descriptor->data()->size() == 0) {
246+
auto result =
247+
[callback, raw_descriptor, ui_runner = runners_.GetUITaskRunner()](
248+
SkiaGPUObject<SkImage> image, fml::tracing::TraceFlow flow) {
249+
ui_runner->PostTask(fml::MakeCopyable(
250+
[callback, raw_descriptor, image = std::move(image),
251+
flow = std::move(flow)]() mutable {
252+
// We are going to terminate the trace flow here. Flows cannot
253+
// terminate without a base trace. Add one explicitly.
254+
TRACE_EVENT0("flutter", "ImageDecodeCallback");
255+
flow.End();
256+
callback(std::move(image));
257+
raw_descriptor->Release();
258+
}));
259+
};
260+
261+
if (!raw_descriptor->data() || raw_descriptor->data()->size() == 0) {
245262
result({}, std::move(flow));
246263
return;
247264
}
248265

249266
concurrent_task_runner_->PostTask(
250-
fml::MakeCopyable([descriptor, //
267+
fml::MakeCopyable([raw_descriptor, //
251268
io_manager = io_manager_, //
252269
io_runner = runners_.GetIOTaskRunner(), //
253270
result, //
@@ -258,16 +275,15 @@ void ImageDecoder::Decode(fml::RefPtr<ImageDescriptor> descriptor,
258275
// Step 1: Decompress the image.
259276
// On Worker.
260277

261-
auto decompressed =
262-
descriptor->is_compressed()
263-
? ImageFromCompressedData(std::move(descriptor), //
264-
target_width, //
265-
target_height, //
266-
flow)
267-
: ImageFromDecompressedData(std::move(descriptor), //
268-
target_width, //
269-
target_height, //
270-
flow);
278+
auto decompressed = raw_descriptor->is_compressed()
279+
? ImageFromCompressedData(raw_descriptor, //
280+
target_width, //
281+
target_height, //
282+
flow)
283+
: ImageFromDecompressedData(raw_descriptor, //
284+
target_width, //
285+
target_height, //
286+
flow);
271287

272288
if (!decompressed) {
273289
FML_LOG(ERROR) << "Could not decompress image.";
@@ -283,7 +299,8 @@ void ImageDecoder::Decode(fml::RefPtr<ImageDescriptor> descriptor,
283299
std::move(flow)]() mutable {
284300
if (!io_manager) {
285301
FML_LOG(ERROR) << "Could not acquire IO manager.";
286-
return result({}, std::move(flow));
302+
result({}, std::move(flow));
303+
return;
287304
}
288305

289306
// If the IO manager does not have a resource context, the caller

lib/ui/painting/image_decoder.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ class ImageDecoder {
6161
FML_DISALLOW_COPY_AND_ASSIGN(ImageDecoder);
6262
};
6363

64-
sk_sp<SkImage> ImageFromCompressedData(fml::RefPtr<ImageDescriptor> descriptor,
64+
sk_sp<SkImage> ImageFromCompressedData(ImageDescriptor* descriptor,
6565
uint32_t target_width,
6666
uint32_t target_height,
6767
const fml::tracing::TraceFlow& flow);

lib/ui/painting/image_decoder_unittests.cc

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -556,10 +556,10 @@ TEST(ImageDecoderTest, VerifySimpleDecoding) {
556556
auto descriptor =
557557
fml::MakeRefCounted<ImageDescriptor>(std::move(data), std::move(codec));
558558

559-
ASSERT_EQ(
560-
ImageFromCompressedData(descriptor, 6, 2, fml::tracing::TraceFlow(""))
561-
->dimensions(),
562-
SkISize::Make(6, 2));
559+
ASSERT_EQ(ImageFromCompressedData(descriptor.get(), 6, 2,
560+
fml::tracing::TraceFlow(""))
561+
->dimensions(),
562+
SkISize::Make(6, 2));
563563
}
564564

565565
TEST(ImageDecoderTest, VerifySubpixelDecodingPreservesExifOrientation) {
@@ -574,8 +574,8 @@ TEST(ImageDecoderTest, VerifySubpixelDecodingPreservesExifOrientation) {
574574
ASSERT_EQ(SkISize::Make(600, 200), image->dimensions());
575575

576576
auto decode = [descriptor](uint32_t target_width, uint32_t target_height) {
577-
return ImageFromCompressedData(descriptor, target_width, target_height,
578-
fml::tracing::TraceFlow(""));
577+
return ImageFromCompressedData(descriptor.get(), target_width,
578+
target_height, fml::tracing::TraceFlow(""));
579579
};
580580

581581
auto expected_data = OpenFixtureAsSkData("Horizontal.png");

0 commit comments

Comments
 (0)