xref: /aosp_15_r20/external/cronet/base/threading/platform_thread_linux.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 // Description: Linux specific functionality. Other Linux-derivatives layer on
5 // top of this translation unit.
6 
7 #include "base/threading/platform_thread.h"
8 
9 #include <errno.h>
10 #include <pthread.h>
11 #include <sched.h>
12 #include <stddef.h>
13 #include <sys/prctl.h>
14 #include <sys/resource.h>
15 #include <sys/time.h>
16 #include <sys/types.h>
17 #include <unistd.h>
18 
19 #include <atomic>
20 #include <cstdint>
21 #include <optional>
22 
23 #include "base/base_switches.h"
24 #include "base/command_line.h"
25 #include "base/compiler_specific.h"
26 #include "base/feature_list.h"
27 #include "base/files/file_util.h"
28 #include "base/lazy_instance.h"
29 #include "base/logging.h"
30 #include "base/metrics/field_trial_params.h"
31 #include "base/no_destructor.h"
32 #include "base/notreached.h"
33 #include "base/process/internal_linux.h"
34 #include "base/strings/string_number_conversions.h"
35 #include "base/strings/stringprintf.h"
36 #include "base/threading/platform_thread_internal_posix.h"
37 #include "base/threading/thread_id_name_manager.h"
38 #include "base/threading/thread_type_delegate.h"
39 #include "build/build_config.h"
40 
41 namespace base {
42 
43 namespace {
44 
45 ThreadTypeDelegate* g_thread_type_delegate = nullptr;
46 
47 const FilePath::CharType kCgroupDirectory[] =
48     FILE_PATH_LITERAL("/sys/fs/cgroup");
49 
ThreadTypeToCgroupDirectory(const FilePath & cgroup_filepath,ThreadType thread_type)50 FilePath ThreadTypeToCgroupDirectory(const FilePath& cgroup_filepath,
51                                      ThreadType thread_type) {
52   switch (thread_type) {
53     case ThreadType::kBackground:
54     case ThreadType::kUtility:
55     case ThreadType::kResourceEfficient:
56       return cgroup_filepath.Append(FILE_PATH_LITERAL("non-urgent"));
57     case ThreadType::kDefault:
58       return cgroup_filepath;
59     case ThreadType::kCompositing:
60 #if BUILDFLAG(IS_CHROMEOS)
61       // On ChromeOS, kCompositing is also considered urgent.
62       return cgroup_filepath.Append(FILE_PATH_LITERAL("urgent"));
63 #else
64       // TODO(1329208): Experiment with bringing IS_LINUX inline with
65       // IS_CHROMEOS.
66       return cgroup_filepath;
67 #endif
68     case ThreadType::kDisplayCritical:
69     case ThreadType::kRealtimeAudio:
70       return cgroup_filepath.Append(FILE_PATH_LITERAL("urgent"));
71   }
72   NOTREACHED();
73   return FilePath();
74 }
75 
SetThreadCgroup(PlatformThreadId thread_id,const FilePath & cgroup_directory)76 void SetThreadCgroup(PlatformThreadId thread_id,
77                      const FilePath& cgroup_directory) {
78   FilePath tasks_filepath = cgroup_directory.Append(FILE_PATH_LITERAL("tasks"));
79   std::string tid = NumberToString(thread_id);
80   // TODO(crbug.com/1333521): Remove cast.
81   const int size = static_cast<int>(tid.size());
82   int bytes_written = WriteFile(tasks_filepath, tid.data(), size);
83   if (bytes_written != size) {
84     DVLOG(1) << "Failed to add " << tid << " to " << tasks_filepath.value();
85   }
86 }
87 
SetThreadCgroupForThreadType(PlatformThreadId thread_id,const FilePath & cgroup_filepath,ThreadType thread_type)88 void SetThreadCgroupForThreadType(PlatformThreadId thread_id,
89                                   const FilePath& cgroup_filepath,
90                                   ThreadType thread_type) {
91   // Append "chrome" suffix.
92   FilePath cgroup_directory = ThreadTypeToCgroupDirectory(
93       cgroup_filepath.Append(FILE_PATH_LITERAL("chrome")), thread_type);
94 
95   // Silently ignore request if cgroup directory doesn't exist.
96   if (!DirectoryExists(cgroup_directory))
97     return;
98 
99   SetThreadCgroup(thread_id, cgroup_directory);
100 }
101 
102 }  // namespace
103 
104 namespace internal {
105 
106 const ThreadPriorityToNiceValuePairForTest
107     kThreadPriorityToNiceValueMapForTest[7] = {
108         {ThreadPriorityForTest::kRealtimeAudio, -10},
109         {ThreadPriorityForTest::kDisplay, -8},
110 #if BUILDFLAG(IS_CHROMEOS)
111         {ThreadPriorityForTest::kCompositing, -8},
112 #else
113         // TODO(1329208): Experiment with bringing IS_LINUX inline with
114         // IS_CHROMEOS.
115         {ThreadPriorityForTest::kCompositing, -1},
116 #endif
117         {ThreadPriorityForTest::kNormal, 0},
118         {ThreadPriorityForTest::kResourceEfficient, 1},
119         {ThreadPriorityForTest::kUtility, 2},
120         {ThreadPriorityForTest::kBackground, 10},
121 };
122 
123 // These nice values are shared with ChromeOS platform code
124 // (platform_thread_cros.cc) and have to be unique as ChromeOS has a unique
125 // type -> nice value mapping. An exception is kCompositing and
126 // kDisplayCritical where aliasing is OK as they have the same scheduler
127 // attributes (cpusets, latency_sensitive etc) including nice value.
128 // The uniqueness of the nice value per-type helps to change and restore the
129 // scheduling params of threads when their process toggles between FG and BG.
130 const ThreadTypeToNiceValuePair kThreadTypeToNiceValueMap[7] = {
131     {ThreadType::kBackground, 10},       {ThreadType::kUtility, 2},
132     {ThreadType::kResourceEfficient, 1}, {ThreadType::kDefault, 0},
133 #if BUILDFLAG(IS_CHROMEOS)
134     {ThreadType::kCompositing, -8},
135 #else
136     // TODO(1329208): Experiment with bringing IS_LINUX inline with IS_CHROMEOS.
137     {ThreadType::kCompositing, -1},
138 #endif
139     {ThreadType::kDisplayCritical, -8},  {ThreadType::kRealtimeAudio, -10},
140 };
141 
CanSetThreadTypeToRealtimeAudio()142 bool CanSetThreadTypeToRealtimeAudio() {
143   // Check if root
144   if (geteuid() == 0) {
145     return true;
146   }
147 
148   // A non-zero soft-limit on RLIMIT_RTPRIO is required to be allowed to invoke
149   // pthread_setschedparam in SetCurrentThreadTypeForPlatform().
150   struct rlimit rlim;
151   return getrlimit(RLIMIT_RTPRIO, &rlim) != 0 && rlim.rlim_cur != 0;
152 }
153 
SetCurrentThreadTypeForPlatform(ThreadType thread_type,MessagePumpType pump_type_hint)154 bool SetCurrentThreadTypeForPlatform(ThreadType thread_type,
155                                      MessagePumpType pump_type_hint) {
156   PlatformThreadLinux::SetThreadType(PlatformThread::CurrentId(), thread_type);
157   return true;
158 }
159 
160 std::optional<ThreadPriorityForTest>
GetCurrentThreadPriorityForPlatformForTest()161 GetCurrentThreadPriorityForPlatformForTest() {
162   int maybe_sched_rr = 0;
163   struct sched_param maybe_realtime_prio = {0};
164   if (pthread_getschedparam(pthread_self(), &maybe_sched_rr,
165                             &maybe_realtime_prio) == 0 &&
166       maybe_sched_rr == SCHED_RR &&
167       maybe_realtime_prio.sched_priority ==
168           PlatformThreadLinux::kRealTimeAudioPrio.sched_priority) {
169     return std::make_optional(ThreadPriorityForTest::kRealtimeAudio);
170   }
171   return std::nullopt;
172 }
173 
174 }  // namespace internal
175 
176 // Determine if thread_id is a background thread by looking up whether
177 // it is in the urgent or non-urgent cpuset.
IsThreadBackgroundedForTest(PlatformThreadId thread_id)178 bool PlatformThreadLinux::IsThreadBackgroundedForTest(
179     PlatformThreadId thread_id) {
180   FilePath cgroup_filepath(kCgroupDirectory);
181 
182   FilePath urgent_cgroup_directory =
183       cgroup_filepath.Append(FILE_PATH_LITERAL("cpuset"))
184           .Append(FILE_PATH_LITERAL("chrome"))
185           .Append(FILE_PATH_LITERAL("urgent"));
186   FilePath non_urgent_cgroup_directory =
187       cgroup_filepath.Append(FILE_PATH_LITERAL("cpuset"))
188           .Append(FILE_PATH_LITERAL("chrome"))
189           .Append(FILE_PATH_LITERAL("non-urgent"));
190 
191   // Silently ignore request if cgroup directory doesn't exist.
192   if (!DirectoryExists(urgent_cgroup_directory) ||
193       !DirectoryExists(non_urgent_cgroup_directory)) {
194     return false;
195   }
196 
197   FilePath urgent_tasks_filepath =
198       urgent_cgroup_directory.Append(FILE_PATH_LITERAL("tasks"));
199   FilePath non_urgent_tasks_filepath =
200       non_urgent_cgroup_directory.Append(FILE_PATH_LITERAL("tasks"));
201 
202   std::string tid = NumberToString(thread_id);
203   // Check if thread_id is in the urgent cpuset
204   std::string urgent_tasks;
205   if (!ReadFileToString(urgent_tasks_filepath, &urgent_tasks)) {
206     return false;
207   }
208   if (urgent_tasks.find(tid) != std::string::npos) {
209     return false;
210   }
211 
212   // Check if thread_id is in the non-urgent cpuset
213   std::string non_urgent_tasks;
214   if (!ReadFileToString(non_urgent_tasks_filepath, &non_urgent_tasks)) {
215     return false;
216   }
217   if (non_urgent_tasks.find(tid) != std::string::npos) {
218     return true;
219   }
220 
221   return false;
222 }
223 
SetName(const std::string & name)224 void PlatformThreadBase::SetName(const std::string& name) {
225   SetNameCommon(name);
226 
227   // On linux we can get the thread names to show up in the debugger by setting
228   // the process name for the LWP.  We don't want to do this for the main
229   // thread because that would rename the process, causing tools like killall
230   // to stop working.
231   if (PlatformThread::CurrentId() == getpid())
232     return;
233 
234   // http://0pointer.de/blog/projects/name-your-threads.html
235   // Set the name for the LWP (which gets truncated to 15 characters).
236   // Note that glibc also has a 'pthread_setname_np' api, but it may not be
237   // available everywhere and it's only benefit over using prctl directly is
238   // that it can set the name of threads other than the current thread.
239   int err = prctl(PR_SET_NAME, name.c_str());
240   // We expect EPERM failures in sandboxed processes, just ignore those.
241   if (err < 0 && errno != EPERM)
242     DPLOG(ERROR) << "prctl(PR_SET_NAME)";
243 }
244 
245 // static
SetThreadTypeDelegate(ThreadTypeDelegate * delegate)246 void PlatformThreadLinux::SetThreadTypeDelegate(ThreadTypeDelegate* delegate) {
247   // A component cannot override a delegate set by another component, thus
248   // disallow setting a delegate when one already exists.
249   DCHECK(!g_thread_type_delegate || !delegate);
250 
251   g_thread_type_delegate = delegate;
252 }
253 
254 // static
SetThreadCgroupsForThreadType(PlatformThreadId thread_id,ThreadType thread_type)255 void PlatformThreadLinux::SetThreadCgroupsForThreadType(
256     PlatformThreadId thread_id,
257     ThreadType thread_type) {
258   FilePath cgroup_filepath(kCgroupDirectory);
259   SetThreadCgroupForThreadType(
260       thread_id, cgroup_filepath.Append(FILE_PATH_LITERAL("cpuset")),
261       thread_type);
262   SetThreadCgroupForThreadType(
263       thread_id, cgroup_filepath.Append(FILE_PATH_LITERAL("schedtune")),
264       thread_type);
265 }
266 
267 // static
SetThreadType(ProcessId process_id,PlatformThreadId thread_id,ThreadType thread_type,IsViaIPC via_ipc)268 void PlatformThreadLinux::SetThreadType(ProcessId process_id,
269                                         PlatformThreadId thread_id,
270                                         ThreadType thread_type,
271                                         IsViaIPC via_ipc) {
272   SetThreadTypeInternal(process_id, thread_id, thread_type, via_ipc);
273 }
274 
275 // static
SetThreadType(PlatformThreadId thread_id,ThreadType thread_type)276 void PlatformThreadLinux::SetThreadType(PlatformThreadId thread_id,
277                                         ThreadType thread_type) {
278   if (g_thread_type_delegate &&
279       g_thread_type_delegate->HandleThreadTypeChange(thread_id, thread_type)) {
280     return;
281   }
282   SetThreadTypeInternal(getpid(), thread_id, thread_type, IsViaIPC(false));
283 }
284 
285 // static
SetThreadTypeInternal(ProcessId process_id,PlatformThreadId thread_id,ThreadType thread_type,IsViaIPC via_ipc)286 void PlatformThreadLinux::SetThreadTypeInternal(ProcessId process_id,
287                                                 PlatformThreadId thread_id,
288                                                 ThreadType thread_type,
289                                                 IsViaIPC via_ipc) {
290   SetThreadCgroupsForThreadType(thread_id, thread_type);
291 
292   // Some scheduler syscalls require thread ID of 0 for current thread.
293   // This prevents us from requiring to translate the NS TID to
294   // global TID.
295   PlatformThreadId syscall_tid = thread_id;
296   if (thread_id == PlatformThread::CurrentId()) {
297     syscall_tid = 0;
298   }
299 
300   if (thread_type == ThreadType::kRealtimeAudio) {
301     if (sched_setscheduler(syscall_tid, SCHED_RR,
302                            &PlatformThreadLinux::kRealTimeAudioPrio) == 0) {
303       return;
304     }
305     // If failed to set to RT, fallback to setpriority to set nice value.
306     DPLOG(ERROR) << "Failed to set realtime priority for thread " << thread_id;
307   }
308 
309   const int nice_setting = internal::ThreadTypeToNiceValue(thread_type);
310   if (setpriority(PRIO_PROCESS, static_cast<id_t>(syscall_tid), nice_setting)) {
311     DVPLOG(1) << "Failed to set nice value of thread (" << thread_id << ") to "
312               << nice_setting;
313   }
314 }
315 
316 }  // namespace base
317