Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 9a2ff02

Browse files
author
Emmanuel Garcia
authored
Reland: "Fix memory leak and bug in the RunsOnCreationTaskRun" (#25317)
1 parent 2df3508 commit 9a2ff02

File tree

11 files changed

+310
-7
lines changed

11 files changed

+310
-7
lines changed

fml/memory/task_runner_checker.cc

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,18 @@
77
namespace fml {
88

99
TaskRunnerChecker::TaskRunnerChecker()
10-
: initialized_queue_id_(InitTaskQueueId()){};
10+
: initialized_queue_id_(InitTaskQueueId()),
11+
subsumed_queue_id_(
12+
MessageLoopTaskQueues::GetInstance()->GetSubsumedTaskQueueId(
13+
initialized_queue_id_)){};
1114

1215
TaskRunnerChecker::~TaskRunnerChecker() = default;
1316

1417
bool TaskRunnerChecker::RunsOnCreationTaskRunner() const {
1518
FML_CHECK(fml::MessageLoop::IsInitializedForCurrentThread());
1619
const auto current_queue_id = MessageLoop::GetCurrentTaskQueueId();
17-
return RunsOnTheSameThread(current_queue_id, initialized_queue_id_);
20+
return RunsOnTheSameThread(current_queue_id, initialized_queue_id_) ||
21+
RunsOnTheSameThread(current_queue_id, subsumed_queue_id_);
1822
};
1923

2024
bool TaskRunnerChecker::RunsOnTheSameThread(TaskQueueId queue_a,

fml/memory/task_runner_checker.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class TaskRunnerChecker final {
2222

2323
private:
2424
TaskQueueId initialized_queue_id_;
25+
TaskQueueId subsumed_queue_id_;
2526

2627
TaskQueueId InitTaskQueueId();
2728
};

fml/memory/task_runner_checker_unittest.cc

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,5 +115,57 @@ TEST(TaskRunnerCheckerTests, MergedTaskRunnersRunsOnTheSameThread) {
115115
thread2.join();
116116
}
117117

118+
TEST(TaskRunnerCheckerTests,
119+
PassesRunsOnCreationTaskRunnerIfOnDifferentTaskRunner) {
120+
fml::MessageLoop* loop1 = nullptr;
121+
fml::AutoResetWaitableEvent latch1;
122+
std::thread thread1([&]() {
123+
fml::MessageLoop::EnsureInitializedForCurrentThread();
124+
loop1 = &fml::MessageLoop::GetCurrent();
125+
latch1.Signal();
126+
loop1->Run();
127+
});
128+
129+
fml::MessageLoop* loop2 = nullptr;
130+
fml::AutoResetWaitableEvent latch2;
131+
std::thread thread2([&]() {
132+
fml::MessageLoop::EnsureInitializedForCurrentThread();
133+
loop2 = &fml::MessageLoop::GetCurrent();
134+
latch2.Signal();
135+
loop2->Run();
136+
});
137+
138+
latch1.Wait();
139+
latch2.Wait();
140+
141+
fml::TaskQueueId qid1 = loop1->GetTaskRunner()->GetTaskQueueId();
142+
fml::TaskQueueId qid2 = loop2->GetTaskRunner()->GetTaskQueueId();
143+
fml::MessageLoopTaskQueues::GetInstance()->Merge(qid1, qid2);
144+
145+
std::unique_ptr<TaskRunnerChecker> checker;
146+
147+
fml::AutoResetWaitableEvent latch3;
148+
loop2->GetTaskRunner()->PostTask([&]() {
149+
checker = std::make_unique<TaskRunnerChecker>();
150+
EXPECT_EQ(checker->RunsOnCreationTaskRunner(), true);
151+
latch3.Signal();
152+
});
153+
latch3.Wait();
154+
155+
fml::MessageLoopTaskQueues::GetInstance()->Unmerge(qid1);
156+
157+
fml::AutoResetWaitableEvent latch4;
158+
loop2->GetTaskRunner()->PostTask([&]() {
159+
EXPECT_EQ(checker->RunsOnCreationTaskRunner(), true);
160+
latch4.Signal();
161+
});
162+
latch4.Wait();
163+
164+
loop1->Terminate();
165+
loop2->Terminate();
166+
thread1.join();
167+
thread2.join();
168+
}
169+
118170
} // namespace testing
119171
} // namespace fml

fml/message_loop_task_queues.cc

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,14 @@ bool MessageLoopTaskQueues::Unmerge(TaskQueueId owner) {
238238
bool MessageLoopTaskQueues::Owns(TaskQueueId owner,
239239
TaskQueueId subsumed) const {
240240
std::lock_guard guard(queue_mutex_);
241-
return subsumed == queue_entries_.at(owner)->owner_of;
241+
return owner != _kUnmerged && subsumed != _kUnmerged &&
242+
subsumed == queue_entries_.at(owner)->owner_of;
243+
}
244+
245+
TaskQueueId MessageLoopTaskQueues::GetSubsumedTaskQueueId(
246+
TaskQueueId owner) const {
247+
std::lock_guard guard(queue_mutex_);
248+
return queue_entries_.at(owner)->owner_of;
242249
}
243250

244251
// Subsumed queues will never have pending tasks.

fml/message_loop_task_queues.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ class MessageLoopTaskQueues
122122
// Returns true if owner owns the subsumed task queue.
123123
bool Owns(TaskQueueId owner, TaskQueueId subsumed) const;
124124

125+
// Returns the subsumed task queue if any or |TaskQueueId::kUnmerged|
126+
// otherwise.
127+
TaskQueueId GetSubsumedTaskQueueId(TaskQueueId owner) const;
128+
125129
private:
126130
class MergedQueuesRunner;
127131

fml/message_loop_task_queues_unittests.cc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,13 @@ TEST(MessageLoopTaskQueue, QueueDoNotOwnItself) {
185185
ASSERT_FALSE(task_queue->Owns(queue_id, queue_id));
186186
}
187187

188+
TEST(MessageLoopTaskQueue, QueueDoNotOwnUnmergedTaskQueueId) {
189+
auto task_queue = fml::MessageLoopTaskQueues::GetInstance();
190+
ASSERT_FALSE(task_queue->Owns(task_queue->CreateTaskQueue(), _kUnmerged));
191+
ASSERT_FALSE(task_queue->Owns(_kUnmerged, task_queue->CreateTaskQueue()));
192+
ASSERT_FALSE(task_queue->Owns(_kUnmerged, _kUnmerged));
193+
}
194+
188195
// TODO(chunhtai): This unit-test is flaky and sometimes fails asynchronizely
189196
// after the test has finished.
190197
// https://github.com/flutter/flutter/issues/43858

shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,7 @@ public void detach() {
458458
if (platformViewsChannel != null) {
459459
platformViewsChannel.setPlatformViewsHandler(null);
460460
}
461+
destroyOverlaySurfaces();
461462
platformViewsChannel = null;
462463
context = null;
463464
textureRegistry = null;
@@ -485,6 +486,7 @@ public void attachToView(@NonNull View flutterView) {
485486
* the previously attached {@code View}.
486487
*/
487488
public void detachFromView() {
489+
destroyOverlaySurfaces();
488490
this.flutterView = null;
489491

490492
// Inform all existing platform views that they are no longer associated with
@@ -703,6 +705,12 @@ private void initializeRootImageViewIfNeeded() {
703705
}
704706
}
705707

708+
/**
709+
* Initializes a platform view and adds it to the view hierarchy.
710+
*
711+
* @param viewId The view ID.
712+
* @hide
713+
*/
706714
@VisibleForTesting
707715
void initializePlatformViewIfNeeded(int viewId) {
708716
final PlatformView platformView = platformViews.get(viewId);
@@ -733,6 +741,19 @@ public void attachToFlutterRenderer(FlutterRenderer flutterRenderer) {
733741
androidTouchProcessor = new AndroidTouchProcessor(flutterRenderer, /*trackMotionEvents=*/ true);
734742
}
735743

744+
/**
745+
* Called when a platform view id displayed in the current frame.
746+
*
747+
* @param viewId The ID of the platform view.
748+
* @param x The left position relative to {@code FlutterView}.
749+
* @param y The top position relative to {@code FlutterView}.
750+
* @param width The width of the platform view.
751+
* @param height The height of the platform view.
752+
* @param viewWidth The original width of the platform view before applying the mutator stack.
753+
* @param viewHeight The original height of the platform view before applying the mutator stack.
754+
* @param mutatorsStack The mutator stack.
755+
* @hide
756+
*/
736757
public void onDisplayPlatformView(
737758
int viewId,
738759
int x,
@@ -760,7 +781,20 @@ public void onDisplayPlatformView(
760781
currentFrameUsedPlatformViewIds.add(viewId);
761782
}
762783

784+
/**
785+
* Called when an overlay surface is displayed in the current frame.
786+
*
787+
* @param id The ID of the surface.
788+
* @param x The left position relative to {@code FlutterView}.
789+
* @param y The top position relative to {@code FlutterView}.
790+
* @param width The width of the surface.
791+
* @param height The height of the surface.
792+
* @hide
793+
*/
763794
public void onDisplayOverlaySurface(int id, int x, int y, int width, int height) {
795+
if (overlayLayerViews.get(id) == null) {
796+
throw new IllegalStateException("The overlay surface (id:" + id + ") doesn't exist");
797+
}
764798
initializeRootImageViewIfNeeded();
765799

766800
final FlutterImageView overlayView = overlayLayerViews.get(id);
@@ -782,6 +816,11 @@ public void onBeginFrame() {
782816
currentFrameUsedPlatformViewIds.clear();
783817
}
784818

819+
/**
820+
* Called by {@code FlutterJNI} when the Flutter frame was submitted.
821+
*
822+
* @hide
823+
*/
785824
public void onEndFrame() {
786825
final FlutterView view = (FlutterView) flutterView;
787826
// If there are no platform views in the current frame,
@@ -850,6 +889,13 @@ private void finishFrame(boolean isFrameRenderedUsingImageReaders) {
850889
}
851890
}
852891

892+
/**
893+
* Creates and tracks the overlay surface.
894+
*
895+
* @param imageView The surface that displays the overlay.
896+
* @return Wrapper object that provides the layer id and the surface.
897+
* @hide
898+
*/
853899
@VisibleForTesting
854900
@TargetApi(19)
855901
public FlutterOverlaySurface createOverlaySurface(@NonNull FlutterImageView imageView) {
@@ -858,6 +904,13 @@ public FlutterOverlaySurface createOverlaySurface(@NonNull FlutterImageView imag
858904
return new FlutterOverlaySurface(id, imageView.getSurface());
859905
}
860906

907+
/**
908+
* Creates an overlay surface while the Flutter view is rendered by {@code FlutterImageView}.
909+
*
910+
* <p>This method is invoked by {@code FlutterJNI} only.
911+
*
912+
* @hide
913+
*/
861914
@TargetApi(19)
862915
public FlutterOverlaySurface createOverlaySurface() {
863916
// Overlay surfaces have the same size as the background surface.
@@ -874,12 +927,21 @@ public FlutterOverlaySurface createOverlaySurface() {
874927
FlutterImageView.SurfaceKind.overlay));
875928
}
876929

930+
/**
931+
* Destroys the overlay surfaces and removes them from the view hierarchy.
932+
*
933+
* <p>This method is used only internally by {@code FlutterJNI}.
934+
*
935+
* @hide
936+
*/
877937
public void destroyOverlaySurfaces() {
878938
for (int i = 0; i < overlayLayerViews.size(); i++) {
879939
int overlayId = overlayLayerViews.keyAt(i);
880940
FlutterImageView overlayView = overlayLayerViews.valueAt(i);
881941
overlayView.detachFromRenderer();
882-
((FlutterView) flutterView).removeView(overlayView);
942+
if (flutterView != null) {
943+
((FlutterView) flutterView).removeView(overlayView);
944+
}
883945
}
884946
overlayLayerViews.clear();
885947
}

shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,131 @@ public void onEndFrame__removesPlatformViewParent() {
498498
assertEquals(flutterView.getChildCount(), 1);
499499
}
500500

501+
@Test
502+
@Config(shadows = {ShadowFlutterSurfaceView.class, ShadowFlutterJNI.class})
503+
public void detach__destroysOverlaySurfaces() {
504+
final PlatformViewsController platformViewsController = new PlatformViewsController();
505+
506+
final int platformViewId = 0;
507+
assertNull(platformViewsController.getPlatformViewById(platformViewId));
508+
509+
final PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
510+
final PlatformView platformView = mock(PlatformView.class);
511+
when(platformView.getView()).thenReturn(mock(View.class));
512+
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
513+
514+
platformViewsController.getRegistry().registerViewFactory("testType", viewFactory);
515+
516+
final FlutterJNI jni = new FlutterJNI();
517+
jni.attachToNative(false);
518+
attach(jni, platformViewsController);
519+
520+
jni.onFirstFrame();
521+
522+
// Simulate create call from the framework.
523+
createPlatformView(jni, platformViewsController, platformViewId, "testType");
524+
525+
// Produce a frame that displays a platform view and an overlay surface.
526+
platformViewsController.onBeginFrame();
527+
platformViewsController.onDisplayPlatformView(
528+
platformViewId,
529+
/* x=*/ 0,
530+
/* y=*/ 0,
531+
/* width=*/ 10,
532+
/* height=*/ 10,
533+
/* viewWidth=*/ 10,
534+
/* viewHeight=*/ 10,
535+
/* mutatorsStack=*/ new FlutterMutatorsStack());
536+
537+
final FlutterImageView overlayImageView = mock(FlutterImageView.class);
538+
when(overlayImageView.acquireLatestImage()).thenReturn(true);
539+
540+
final FlutterOverlaySurface overlaySurface =
541+
platformViewsController.createOverlaySurface(overlayImageView);
542+
// This is OK.
543+
platformViewsController.onDisplayOverlaySurface(
544+
overlaySurface.getId(), /* x=*/ 0, /* y=*/ 0, /* width=*/ 10, /* height=*/ 10);
545+
546+
platformViewsController.detach();
547+
548+
assertThrows(
549+
IllegalStateException.class,
550+
() -> {
551+
platformViewsController.onDisplayOverlaySurface(
552+
overlaySurface.getId(), /* x=*/ 0, /* y=*/ 0, /* width=*/ 10, /* height=*/ 10);
553+
});
554+
}
555+
556+
@Test
557+
@Config(shadows = {ShadowFlutterSurfaceView.class, ShadowFlutterJNI.class})
558+
public void detachFromView__removesOverlaySurfaces() {
559+
final PlatformViewsController platformViewsController = new PlatformViewsController();
560+
561+
final int platformViewId = 0;
562+
assertNull(platformViewsController.getPlatformViewById(platformViewId));
563+
564+
final PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
565+
final PlatformView platformView = mock(PlatformView.class);
566+
when(platformView.getView()).thenReturn(mock(View.class));
567+
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
568+
569+
platformViewsController.getRegistry().registerViewFactory("testType", viewFactory);
570+
571+
final FlutterJNI jni = new FlutterJNI();
572+
jni.attachToNative(false);
573+
attach(jni, platformViewsController);
574+
575+
final FlutterImageView overlayImageView = mock(FlutterImageView.class);
576+
when(overlayImageView.acquireLatestImage()).thenReturn(true);
577+
578+
final FlutterOverlaySurface overlaySurface =
579+
platformViewsController.createOverlaySurface(overlayImageView);
580+
581+
platformViewsController.onDisplayOverlaySurface(
582+
overlaySurface.getId(), /* x=*/ 0, /* y=*/ 0, /* width=*/ 10, /* height=*/ 10);
583+
584+
platformViewsController.detachFromView();
585+
586+
assertThrows(
587+
IllegalStateException.class,
588+
() -> {
589+
platformViewsController.onDisplayOverlaySurface(
590+
overlaySurface.getId(), /* x=*/ 0, /* y=*/ 0, /* width=*/ 10, /* height=*/ 10);
591+
});
592+
}
593+
594+
@Test
595+
@Config(shadows = {ShadowFlutterSurfaceView.class, ShadowFlutterJNI.class})
596+
public void destroyOverlaySurfaces__doesNotThrowIfControllerIsDetached() {
597+
final PlatformViewsController platformViewsController = new PlatformViewsController();
598+
599+
final int platformViewId = 0;
600+
assertNull(platformViewsController.getPlatformViewById(platformViewId));
601+
602+
final PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
603+
final PlatformView platformView = mock(PlatformView.class);
604+
when(platformView.getView()).thenReturn(mock(View.class));
605+
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
606+
607+
platformViewsController.getRegistry().registerViewFactory("testType", viewFactory);
608+
609+
final FlutterJNI jni = new FlutterJNI();
610+
jni.attachToNative(false);
611+
attach(jni, platformViewsController);
612+
613+
final FlutterImageView overlayImageView = mock(FlutterImageView.class);
614+
when(overlayImageView.acquireLatestImage()).thenReturn(true);
615+
616+
final FlutterOverlaySurface overlaySurface =
617+
platformViewsController.createOverlaySurface(overlayImageView);
618+
619+
platformViewsController.onDisplayOverlaySurface(
620+
overlaySurface.getId(), /* x=*/ 0, /* y=*/ 0, /* width=*/ 10, /* height=*/ 10);
621+
622+
platformViewsController.detachFromView();
623+
platformViewsController.destroyOverlaySurfaces();
624+
}
625+
501626
@Test
502627
public void checkInputConnectionProxy__falseIfViewIsNull() {
503628
final PlatformViewsController platformViewsController = new PlatformViewsController();

0 commit comments

Comments
 (0)