summaryrefslogtreecommitdiff
path: root/sandbox/linux/services/thread_helpers.cc
blob: 80766a9bc5dc970666efbb5c3e7ff21ad646c4ff (plain)
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
// Copyright 2014 The Chromium Authors. All rights reserved.
// 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/bind.h"
#include "base/callback.h"
#include "base/files/scoped_file.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!";

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;
}

// 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::Callback<bool(void)>& cb) {
#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) << kAssertSingleThreadedError << " (iterations: " << kMaxIterations
             << ")";

  NOTREACHED();
}

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

}  // 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::Callback<bool(void)> cb = base::Bind(&IsMultiThreaded, proc_fd);
  RunWhileTrue(cb);
}

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

// static
bool ThreadHelpers::StopThreadAndWatchProcFS(int proc_fd,
                                             base::Thread* thread) {
  DCHECK_LE(0, proc_fd);
  DCHECK(thread);
  const base::PlatformThreadId thread_id = thread->thread_id();
  const std::string thread_id_dir_str =
      "self/task/" + base::IntToString(thread_id) + "/";

  // The kernel is at liberty to wake the thread id futex before updating
  // /proc. Following Stop(), the thread is joined, but entries in /proc may
  // not have been updated.
  thread->Stop();

  const base::Callback<bool(void)> cb =
      base::Bind(&IsThreadPresentInProcFS, proc_fd, thread_id_dir_str);

  RunWhileTrue(cb);

  return true;
}

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

}  // namespace sandbox