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

Commit e483e52

Browse files
committed
Enforce consistent stack size for Flutter threads
1 parent 8b839ed commit e483e52

File tree

4 files changed

+135
-15
lines changed

4 files changed

+135
-15
lines changed

fml/thread.cc

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,71 @@
2424

2525
namespace fml {
2626

27+
typedef std::function<void()> ThreadFunction;
28+
29+
class ThreadHandle {
30+
public:
31+
explicit ThreadHandle(ThreadFunction&& function);
32+
~ThreadHandle();
33+
34+
void Join();
35+
36+
private:
37+
#if defined(FML_OS_WIN)
38+
HANDLE thread_;
39+
#else
40+
pthread_t thread_;
41+
#endif
42+
};
43+
44+
#if defined(FML_OS_WIN)
45+
ThreadHandle::ThreadHandle(ThreadFunction&& function) {
46+
thread_ = (HANDLE*)_beginthreadex(
47+
nullptr, Thread::GetDefaultStackSize(),
48+
[](void* arg) -> unsigned {
49+
std::unique_ptr<ThreadFunction> function(
50+
reinterpret_cast<ThreadFunction*>(arg));
51+
(*function)();
52+
return 0;
53+
},
54+
new ThreadFunction(std::move(function)), 0, nullptr);
55+
FML_CHECK(thread_ != nullptr);
56+
}
57+
58+
void ThreadHandle::Join() {
59+
WaitForSingleObjectEx(thread_, INFINITE, FALSE);
60+
}
61+
62+
ThreadHandle::~ThreadHandle() {
63+
CloseHandle(thread_);
64+
}
65+
#else
66+
ThreadHandle::ThreadHandle(ThreadFunction&& function) {
67+
pthread_attr_t attr;
68+
pthread_attr_init(&attr);
69+
int result = pthread_attr_setstacksize(&attr, Thread::GetDefaultStackSize());
70+
FML_CHECK(result == 0);
71+
result = pthread_create(
72+
&thread_, &attr,
73+
[](void* arg) -> void* {
74+
std::unique_ptr<ThreadFunction> function(
75+
reinterpret_cast<ThreadFunction*>(arg));
76+
(*function)();
77+
return nullptr;
78+
},
79+
new ThreadFunction(std::move(function)));
80+
FML_CHECK(result == 0);
81+
result = pthread_attr_destroy(&attr);
82+
FML_CHECK(result == 0);
83+
}
84+
85+
void ThreadHandle::Join() {
86+
pthread_join(thread_, nullptr);
87+
}
88+
89+
ThreadHandle::~ThreadHandle() {}
90+
#endif
91+
2792
#if defined(FML_OS_WIN)
2893
// The information on how to set the thread name comes from
2994
// a MSDN article: http://msdn2.microsoft.com/en-us/library/xcb2z8hs.aspx
@@ -75,7 +140,7 @@ Thread::Thread(const ThreadConfigSetter& setter, const ThreadConfig& config)
75140
fml::AutoResetWaitableEvent latch;
76141
fml::RefPtr<fml::TaskRunner> runner;
77142

78-
thread_ = std::make_unique<std::thread>(
143+
thread_ = std::make_unique<ThreadHandle>(
79144
[&latch, &runner, setter, config]() -> void {
80145
setter(config);
81146
fml::MessageLoop::EnsureInitializedForCurrentThread();
@@ -102,7 +167,11 @@ void Thread::Join() {
102167
}
103168
joined_ = true;
104169
task_runner_->PostTask([]() { MessageLoop::GetCurrent().Terminate(); });
105-
thread_->join();
170+
thread_->Join();
171+
}
172+
173+
size_t Thread::GetDefaultStackSize() {
174+
return 1024 * 1024 * 2;
106175
}
107176

108177
} // namespace fml

fml/thread.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@
99
#include <functional>
1010
#include <memory>
1111
#include <string>
12-
#include <thread>
1312

1413
#include "flutter/fml/macros.h"
1514
#include "flutter/fml/task_runner.h"
1615

1716
namespace fml {
1817

18+
class ThreadHandle;
19+
1920
class Thread {
2021
public:
2122
/// Valid values for priority of Thread.
@@ -59,8 +60,10 @@ class Thread {
5960

6061
static void SetCurrentThreadName(const ThreadConfig& config);
6162

63+
static size_t GetDefaultStackSize();
64+
6265
private:
63-
std::unique_ptr<std::thread> thread_;
66+
std::unique_ptr<ThreadHandle> thread_;
6467

6568
fml::RefPtr<fml::TaskRunner> task_runner_;
6669

fml/thread_unittests.cc

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
#include "flutter/fml/build_config.h"
56
#include "flutter/fml/thread.h"
67

78
#if defined(FML_OS_MACOSX) || defined(FML_OS_LINUX) || defined(FML_OS_ANDROID)
@@ -15,6 +16,10 @@
1516
#else
1617
#endif
1718

19+
#if defined(FML_OS_WIN)
20+
#include <windows.h>
21+
#endif
22+
1823
#include <memory>
1924
#include "gtest/gtest.h"
2025

@@ -37,6 +42,35 @@ TEST(Thread, HasARunningMessageLoop) {
3742
ASSERT_TRUE(done);
3843
}
3944

45+
TEST(Thread, HasExpectedStackSize) {
46+
size_t stack_size = 0;
47+
fml::Thread thread;
48+
49+
thread.GetTaskRunner()->PostTask([&stack_size]() {
50+
#if defined(FML_OS_WIN)
51+
ULONG_PTR low_limit;
52+
ULONG_PTR high_limit;
53+
GetCurrentThreadStackLimits(&low_limit, &high_limit);
54+
stack_size = high_limit - low_limit;
55+
#elif defined(FML_OS_MACOSX)
56+
stack_size = pthread_get_stacksize_np(pthread_self());
57+
#else
58+
pthread_attr_t attr;
59+
pthread_getattr_np(pthread_self(), &attr);
60+
pthread_attr_getstacksize(&attr, &stack_size);
61+
pthread_attr_destroy(&attr);
62+
#endif
63+
});
64+
thread.Join();
65+
66+
// Actual stack size will be aligned to page size, this assumes no supported
67+
// platform has a page size larger than 16k. On Linux reducing the default
68+
// stack size (8MB) does not seem to have any effect.
69+
const size_t kPageSize = 16384;
70+
ASSERT_TRUE(stack_size / kPageSize >=
71+
fml::Thread::GetDefaultStackSize() / kPageSize);
72+
}
73+
4074
#if FLUTTER_PTHREAD_SUPPORTED
4175
TEST(Thread, ThreadNameCreatedWithConfig) {
4276
const std::string name = "Thread1";
@@ -45,15 +79,27 @@ TEST(Thread, ThreadNameCreatedWithConfig) {
4579
bool done = false;
4680
thread.GetTaskRunner()->PostTask([&done, &name]() {
4781
done = true;
48-
char thread_name[8];
82+
char thread_name[16];
4983
pthread_t current_thread = pthread_self();
50-
pthread_getname_np(current_thread, thread_name, 8);
84+
pthread_getname_np(current_thread, thread_name, 16);
5185
ASSERT_EQ(thread_name, name);
5286
});
5387
thread.Join();
5488
ASSERT_TRUE(done);
5589
}
5690

91+
static int clamp_priority(int priority, int policy) {
92+
int min = sched_get_priority_min(policy);
93+
int max = sched_get_priority_max(policy);
94+
if (priority < min) {
95+
return min;
96+
} else if (priority > max) {
97+
return max;
98+
} else {
99+
return priority;
100+
}
101+
}
102+
57103
static void MockThreadConfigSetter(const fml::Thread::ThreadConfig& config) {
58104
// set thread name
59105
fml::Thread::SetCurrentThreadName(config);
@@ -63,10 +109,10 @@ static void MockThreadConfigSetter(const fml::Thread::ThreadConfig& config) {
63109
int policy = SCHED_OTHER;
64110
switch (config.priority) {
65111
case fml::Thread::ThreadPriority::kDisplay:
66-
param.sched_priority = 10;
112+
param.sched_priority = clamp_priority(10, policy);
67113
break;
68114
default:
69-
param.sched_priority = 1;
115+
param.sched_priority = clamp_priority(1, policy);
70116
}
71117
pthread_setschedparam(tid, policy, &param);
72118
}
@@ -84,27 +130,27 @@ TEST(Thread, ThreadPriorityCreatedWithConfig) {
84130
int policy;
85131
thread.GetTaskRunner()->PostTask([&]() {
86132
done = true;
87-
char thread_name[8];
133+
char thread_name[16];
88134
pthread_t current_thread = pthread_self();
89-
pthread_getname_np(current_thread, thread_name, 8);
135+
pthread_getname_np(current_thread, thread_name, 16);
90136
pthread_getschedparam(current_thread, &policy, &param);
91137
ASSERT_EQ(thread_name, thread1_name);
92138
ASSERT_EQ(policy, SCHED_OTHER);
93-
ASSERT_EQ(param.sched_priority, 1);
139+
ASSERT_EQ(param.sched_priority, clamp_priority(1, policy));
94140
});
95141

96142
fml::Thread thread2(MockThreadConfigSetter,
97143
fml::Thread::ThreadConfig(
98144
thread2_name, fml::Thread::ThreadPriority::kDisplay));
99145
thread2.GetTaskRunner()->PostTask([&]() {
100146
done = true;
101-
char thread_name[8];
147+
char thread_name[16];
102148
pthread_t current_thread = pthread_self();
103-
pthread_getname_np(current_thread, thread_name, 8);
149+
pthread_getname_np(current_thread, thread_name, 16);
104150
pthread_getschedparam(current_thread, &policy, &param);
105151
ASSERT_EQ(thread_name, thread2_name);
106152
ASSERT_EQ(policy, SCHED_OTHER);
107-
ASSERT_EQ(param.sched_priority, 10);
153+
ASSERT_EQ(param.sched_priority, clamp_priority(10, policy));
108154
});
109155
thread.Join();
110156
ASSERT_TRUE(done);

shell/profiling/sampling_profiler_unittest.cc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
#include "flutter/shell/profiling/sampling_profiler.h"
5+
#include <thread>
6+
67
#include "flutter/fml/message_loop_impl.h"
78
#include "flutter/fml/thread.h"
9+
#include "flutter/shell/profiling/sampling_profiler.h"
810
#include "flutter/testing/testing.h"
911
#include "gmock/gmock.h"
1012

0 commit comments

Comments
 (0)