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

Commit 735876e

Browse files
author
Emmanuel Garcia
authored
Fix memory leak and bug in the RunsOnCreationTaskRunner check (#24690)
1 parent 44ba1cc commit 735876e

File tree

11 files changed

+181
-6
lines changed

11 files changed

+181
-6
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: 4 additions & 0 deletions
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;
@@ -761,6 +762,9 @@ public void onDisplayPlatformView(
761762
}
762763

763764
public void onDisplayOverlaySurface(int id, int x, int y, int width, int height) {
765+
if (overlayLayerViews.get(id) == null) {
766+
throw new IllegalStateException("The overlay surface (id:" + id + ") doesn't exist");
767+
}
764768
initializeRootImageViewIfNeeded();
765769

766770
final FlutterImageView overlayView = overlayLayerViews.get(id);

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

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,61 @@ public void onEndFrame__revertsFlutterSurface() {
562562
assertEquals(flutterView.getRenderSurface().getClass().getSimpleName(), "FlutterSurfaceView");
563563
}
564564

565+
@Test
566+
@Config(shadows = {ShadowFlutterSurfaceView.class, ShadowFlutterJNI.class})
567+
public void detach__destroysOverlaySurfaces() {
568+
final PlatformViewsController platformViewsController = new PlatformViewsController();
569+
570+
final int platformViewId = 0;
571+
assertNull(platformViewsController.getPlatformViewById(platformViewId));
572+
573+
final PlatformViewFactory viewFactory = mock(PlatformViewFactory.class);
574+
final PlatformView platformView = mock(PlatformView.class);
575+
when(platformView.getView()).thenReturn(mock(View.class));
576+
when(viewFactory.create(any(), eq(platformViewId), any())).thenReturn(platformView);
577+
578+
platformViewsController.getRegistry().registerViewFactory("testType", viewFactory);
579+
580+
final FlutterJNI jni = new FlutterJNI();
581+
jni.attachToNative(false);
582+
attach(jni, platformViewsController);
583+
584+
jni.onFirstFrame();
585+
586+
// Simulate create call from the framework.
587+
createPlatformView(jni, platformViewsController, platformViewId, "testType");
588+
589+
// Produce a frame that displays a platform view and an overlay surface.
590+
platformViewsController.onBeginFrame();
591+
platformViewsController.onDisplayPlatformView(
592+
platformViewId,
593+
/* x=*/ 0,
594+
/* y=*/ 0,
595+
/* width=*/ 10,
596+
/* height=*/ 10,
597+
/* viewWidth=*/ 10,
598+
/* viewHeight=*/ 10,
599+
/* mutatorsStack=*/ new FlutterMutatorsStack());
600+
601+
final FlutterImageView overlayImageView = mock(FlutterImageView.class);
602+
when(overlayImageView.acquireLatestImage()).thenReturn(true);
603+
604+
final FlutterOverlaySurface overlaySurface =
605+
platformViewsController.createOverlaySurface(overlayImageView);
606+
// This is OK.
607+
platformViewsController.onDisplayOverlaySurface(
608+
overlaySurface.getId(), /* x=*/ 0, /* y=*/ 0, /* width=*/ 10, /* height=*/ 10);
609+
610+
platformViewsController.detach();
611+
612+
assertThrows(
613+
IllegalStateException.class,
614+
() -> {
615+
platformViewsController.onDisplayOverlaySurface(
616+
overlaySurface.getId(), /* x=*/ 0, /* y=*/ 0, /* width=*/ 10, /* height=*/ 10);
617+
});
618+
}
619+
565620
@Test
566621
public void checkInputConnectionProxy__falseIfViewIsNull() {
567622
final PlatformViewsController platformViewsController = new PlatformViewsController();

testing/scenario_app/android/app/build.gradle

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
apply plugin: 'com.android.application'
22
apply plugin: 'com.facebook.testing.screenshot'
33

4+
def leakcanary_version = '2.6'
5+
46
screenshots {
57
failureDir = "${rootProject.buildDir}/reports/diff_failures"
68
recordDir = "${rootProject.projectDir}/reports/screenshots"
@@ -13,12 +15,14 @@ android {
1315
targetCompatibility JavaVersion.VERSION_1_8
1416
}
1517
defaultConfig {
16-
applicationId "dev.flutter.scenarios"
18+
applicationId 'dev.flutter.scenarios'
1719
minSdkVersion 18
1820
targetSdkVersion 30
1921
versionCode 1
20-
versionName "1.0"
21-
testInstrumentationRunner "dev.flutter.TestRunner"
22+
versionName '1.0'
23+
testInstrumentationRunner 'dev.flutter.TestRunner'
24+
testInstrumentationRunnerArgument 'listener', 'leakcanary.FailTestOnLeakRunListener'
25+
testInstrumentationRunnerArguments clearPackageData: 'true'
2226
}
2327
buildTypes {
2428
release {
@@ -38,8 +42,10 @@ dependencies {
3842
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
3943
implementation 'com.google.android.material:material:1.0.0'
4044
implementation 'androidx.lifecycle:lifecycle-common-java8:2.2.0-alpha01'
45+
implementation "com.squareup.leakcanary:leakcanary-android:$leakcanary_version"
4146
testImplementation 'junit:junit:4.12'
4247
androidTestImplementation 'androidx.test:runner:1.2.0'
4348
androidTestImplementation 'androidx.test:rules:1.2.0'
4449
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
50+
androidTestImplementation "com.squareup.leakcanary:leakcanary-android-instrumentation:$leakcanary_version"
4551
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package dev.flutter.scenariosui;
6+
7+
import android.content.Intent;
8+
import androidx.test.filters.LargeTest;
9+
import androidx.test.rule.ActivityTestRule;
10+
import androidx.test.runner.AndroidJUnit4;
11+
import dev.flutter.scenarios.TextPlatformViewActivity;
12+
import leakcanary.FailTestOnLeak;
13+
import org.junit.Rule;
14+
import org.junit.Test;
15+
import org.junit.runner.RunWith;
16+
17+
@RunWith(AndroidJUnit4.class)
18+
@LargeTest
19+
public class MemoryLeakTests {
20+
@Rule
21+
public ActivityTestRule<TextPlatformViewActivity> activityRule =
22+
new ActivityTestRule<>(
23+
TextPlatformViewActivity.class, /*initialTouchMode=*/ false, /*launchActivity=*/ false);
24+
25+
@Test
26+
@FailTestOnLeak
27+
public void platformViewHybridComposition_launchActivityFinishAndLaunchAgain() throws Exception {
28+
Intent intent = new Intent(Intent.ACTION_MAIN);
29+
intent.putExtra("scenario", "platform_view");
30+
intent.putExtra("use_android_view", true);
31+
32+
activityRule.launchActivity(intent);
33+
}
34+
}

0 commit comments

Comments
 (0)