File: thread_helpers.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (202 lines) | stat: -rw-r--r-- 6,550 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "sandbox/linux/services/thread_helpers.h"

#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include <string>

#include "base/files/scoped_file.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/posix/eintr_wrapper.h"
#include "base/strings/string_number_conversions.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread.h"
#include "sandbox/linux/services/proc_util.h"

namespace sandbox {

namespace {

const char kAssertSingleThreadedError[] =
    "Current process is not mono-threaded!";
const char kAssertThreadDoesNotAppearInProcFS[] =
    "Started thread does not appear in /proc";
const char kAssertThreadDoesNotDisappearInProcFS[] =
    "Stopped thread does not disappear in /proc";

bool IsSingleThreadedImpl(int proc_fd) {
  CHECK_LE(0, proc_fd);
  struct stat task_stat;
  int fstat_ret = fstatat(proc_fd, "self/task/", &task_stat, 0);
  PCHECK(0 == fstat_ret);

  // At least "..", "." and the current thread should be present.
  CHECK_LE(3UL, task_stat.st_nlink);
  // Counting threads via /proc/self/task could be racy. For the purpose of
  // determining if the current proces is monothreaded it works: if at any
  // time it becomes monothreaded, it'll stay so.
  return task_stat.st_nlink == 3;
}

bool IsThreadPresentInProcFS(int proc_fd,
                             const std::string& thread_id_dir_str) {
  struct stat task_stat;
  const int fstat_ret =
      fstatat(proc_fd, thread_id_dir_str.c_str(), &task_stat, 0);
  if (fstat_ret < 0) {
    PCHECK(ENOENT == errno);
    return false;
  }
  return true;
}

bool IsNotThreadPresentInProcFS(int proc_fd,
                                const std::string& thread_id_dir_str) {
  return !IsThreadPresentInProcFS(proc_fd, thread_id_dir_str);
}

// Run |cb| in a loop until it returns false. Every time |cb| runs, sleep
// for an exponentially increasing amount of time. |cb| is expected to return
// false very quickly and this will crash if it doesn't happen within ~64ms on
// Debug builds (2s on Release builds).
// This is guaranteed to not sleep more than twice as much as the bare minimum
// amount of time.
void RunWhileTrue(const base::RepeatingCallback<bool(void)>& cb,
                  const char* message) {
#if defined(NDEBUG)
  // In Release mode, crash after 30 iterations, which means having spent
  // roughly 2s in
  // nanosleep(2) cumulatively.
  const unsigned int kMaxIterations = 30U;
#else
  // In practice, this never goes through more than a couple iterations. In
  // debug mode, crash after 64ms (+ eventually 25 times the granularity of
  // the clock) in nanosleep(2). This ensures that this is not becoming too
  // slow.
  const unsigned int kMaxIterations = 25U;
#endif

  // Run |cb| with an exponential back-off, sleeping 2^iterations nanoseconds
  // in nanosleep(2).
  // Note: the clock may not allow for nanosecond granularity, in this case the
  // first iterations would sleep a tiny bit more instead, which would not
  // change the calculations significantly.
  for (unsigned int i = 0; i < kMaxIterations; ++i) {
    if (!cb.Run()) {
      return;
    }

    // Increase the waiting time exponentially.
    struct timespec ts = {0, 1L << i /* nanoseconds */};
    PCHECK(0 == HANDLE_EINTR(nanosleep(&ts, &ts)));
  }

  LOG(FATAL) << message << " (iterations: " << kMaxIterations << ")";
}

bool IsMultiThreaded(int proc_fd) {
  return !ThreadHelpers::IsSingleThreaded(proc_fd);
}

enum class ThreadAction { Start, Stop };

bool ChangeThreadStateAndWatchProcFS(
    int proc_fd, base::Thread* thread, ThreadAction action) {
  DCHECK_LE(0, proc_fd);
  DCHECK(thread);
  DCHECK(action == ThreadAction::Start || action == ThreadAction::Stop);

  base::RepeatingCallback<bool(void)> cb;
  const char* message;

  if (action == ThreadAction::Start) {
    // Should start the thread before calling thread_id().
    if (!thread->Start())
      return false;
  }

  const base::PlatformThreadId thread_id = thread->GetThreadId();
  const std::string thread_id_dir_str =
      "self/task/" + base::NumberToString(thread_id.raw()) + "/";

  if (action == ThreadAction::Stop) {
    // The target thread should exist in /proc.
    DCHECK(IsThreadPresentInProcFS(proc_fd, thread_id_dir_str));
    thread->Stop();
  }

  // The kernel is at liberty to wake the thread id futex before updating
  // /proc. Start() above or following Stop(), the thread is started or joined,
  // but entries in /proc may not have been updated.
  if (action == ThreadAction::Start) {
    cb = base::BindRepeating(&IsNotThreadPresentInProcFS, proc_fd,
                             thread_id_dir_str);
    message = kAssertThreadDoesNotAppearInProcFS;
  } else {
    cb = base::BindRepeating(&IsThreadPresentInProcFS, proc_fd,
                             thread_id_dir_str);
    message = kAssertThreadDoesNotDisappearInProcFS;
  }
  RunWhileTrue(cb, message);

  DCHECK_EQ(action == ThreadAction::Start,
            IsThreadPresentInProcFS(proc_fd, thread_id_dir_str));

  return true;
}

}  // namespace

// static
bool ThreadHelpers::IsSingleThreaded(int proc_fd) {
  DCHECK_LE(0, proc_fd);
  return IsSingleThreadedImpl(proc_fd);
}

// static
bool ThreadHelpers::IsSingleThreaded() {
  base::ScopedFD task_fd(ProcUtil::OpenProc());
  return IsSingleThreaded(task_fd.get());
}

// static
void ThreadHelpers::AssertSingleThreaded(int proc_fd) {
  DCHECK_LE(0, proc_fd);
  const base::RepeatingCallback<bool(void)> cb =
      base::BindRepeating(&IsMultiThreaded, proc_fd);
  RunWhileTrue(cb, kAssertSingleThreadedError);
}

void ThreadHelpers::AssertSingleThreaded() {
  base::ScopedFD task_fd(ProcUtil::OpenProc());
  AssertSingleThreaded(task_fd.get());
}

// static
bool ThreadHelpers::StartThreadAndWatchProcFS(int proc_fd,
                                              base::Thread* thread) {
  return ChangeThreadStateAndWatchProcFS(proc_fd, thread, ThreadAction::Start);
}

// static
bool ThreadHelpers::StopThreadAndWatchProcFS(int proc_fd,
                                             base::Thread* thread) {
  return ChangeThreadStateAndWatchProcFS(proc_fd, thread, ThreadAction::Stop);
}

// static
const char* ThreadHelpers::GetAssertSingleThreadedErrorMessageForTests() {
  return kAssertSingleThreadedError;
}

}  // namespace sandbox