YugabyteDB (2.13.0.0-b42, bfc6a6643e7399ac8a0e81d06a3ee6d6571b33ab)

Coverage Report

Created: 2022-03-09 17:30

/Users/deen/code/yugabyte-db/src/yb/util/stack_trace.cc
Line
Count
Source (jump to first uncovered line)
1
// Copyright (c) YugaByte, Inc.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
4
// in compliance with the License.  You may obtain a copy of the License at
5
//
6
// http://www.apache.org/licenses/LICENSE-2.0
7
//
8
// Unless required by applicable law or agreed to in writing, software distributed under the License
9
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
10
// or implied.  See the License for the specific language governing permissions and limitations
11
// under the License.
12
//
13
14
#include "yb/util/stack_trace.h"
15
16
#include <signal.h>
17
18
#ifdef __linux__
19
#include <linux/futex.h>
20
#include <sys/syscall.h>
21
#endif
22
23
#include <mutex>
24
25
#include "yb/gutil/casts.h"
26
#include "yb/gutil/linux_syscall_support.h"
27
28
#include "yb/util/lockfree.h"
29
#include "yb/util/monotime.h"
30
#include "yb/util/result.h"
31
#include "yb/util/thread.h"
32
33
using namespace std::literals;
34
35
#if defined(__APPLE__)
36
typedef sig_t sighandler_t;
37
#endif
38
39
namespace yb {
40
41
namespace {
42
43
YB_DEFINE_ENUM(ThreadStackState, (kNone)(kSendFailed)(kReady));
44
45
struct ThreadStackEntry : public MPSCQueueEntry<ThreadStackEntry> {
46
  ThreadIdForStack tid;
47
  StackTrace stack;
48
};
49
50
class CompletionFlag {
51
 public:
52
2.04k
  void Signal() {
53
2.04k
    complete_.store(1, std::memory_order_release);
54
#ifndef __APPLE__
55
    sys_futex(reinterpret_cast<int32_t*>(&complete_),
56
              FUTEX_WAKE | FUTEX_PRIVATE_FLAG,
57
              INT_MAX, // wake all
58
              nullptr, nullptr,
59
              0 /* ignored */);
60
#endif
61
2.04k
  }
62
63
2.01k
  bool TimedWait(MonoDelta timeout) {
64
2.01k
    if (complete()) {
65
1
      return true;
66
1
    }
67
68
2.01k
    auto now = MonoTime::Now();
69
2.01k
    auto deadline = now + timeout;
70
249k
    while (now < deadline) {
71
#ifndef __APPLE__
72
      MonoDelta rem = deadline - now;
73
      struct timespec ts;
74
      rem.ToTimeSpec(&ts);
75
      sys_futex(reinterpret_cast<int32_t*>(&complete_),
76
                FUTEX_WAIT | FUTEX_PRIVATE_FLAG,
77
                0, // wait if value is still 0
78
                reinterpret_cast<struct kernel_timespec *>(&ts), nullptr, 0);
79
#else
80
249k
      sched_yield();
81
249k
#endif
82
249k
      if (complete()) {
83
2.01k
        return true;
84
2.01k
      }
85
247k
      now = MonoTime::Now();
86
247k
    }
87
88
0
    return complete();
89
2.01k
  }
90
91
2.04k
  void Reset() {
92
2.04k
    complete_.store(0, std::memory_order_release);
93
2.04k
  }
94
95
251k
  bool complete() const {
96
251k
    return complete_.load(std::memory_order_acquire);
97
251k
  }
98
 private:
99
  std::atomic<int32_t> complete_ { 0 };
100
};
101
102
// Global structure used to communicate between the signal handler
103
// and a dumping thread.
104
struct ThreadStackHelper {
105
  std::mutex mutex; // Locked by ThreadStacks, so only one could be executed in parallel.
106
107
  LockFreeStack<ThreadStackEntry> collected;
108
  // Reuse previously allocated memory. We expect this size to be merely small, near 152 bytes
109
  // per application thread.
110
  LockFreeStack<ThreadStackEntry> allocated;
111
  CompletionFlag completion_flag;
112
113
  // Could be modified only by ThreadStacks.
114
  CoarseTimePoint deadline;
115
  size_t allocated_entries = 0;
116
117
  // Incremented by each signal handler.
118
  std::atomic<int64_t> left_to_collect{0};
119
120
  std::vector<std::unique_ptr<ThreadStackEntry[]>> allocated_chunks;
121
122
2.04k
  void SetNumEntries(size_t len) {
123
2.04k
    len += 5; // We reserve several entries, because threads from previous request could still be
124
              // processing signal and write their results.
125
2.04k
    if (len <= allocated_entries) {
126
1.90k
      return;
127
1.90k
    }
128
129
137
    size_t new_chunk_size = std::max<size_t>(len - allocated_entries, 0x10);
130
137
    allocated_chunks.emplace_back(new ThreadStackEntry[new_chunk_size]);
131
137
    allocated_entries += new_chunk_size;
132
133
2.32k
    for (auto entry = allocated_chunks.back().get(), end = entry + new_chunk_size; entry != end;
134
2.19k
         ++entry) {
135
2.19k
      allocated.Push(entry);
136
2.19k
    }
137
137
  }
138
139
  void StoreResult(
140
2.04k
      const std::vector<ThreadIdForStack>& tids, std::vector<Result<StackTrace>>* out) {
141
    // We give the thread ~1s to respond. In testing, threads typically respond within
142
    // a few iterations of the loop, so this timeout is very conservative.
143
    //
144
    // The main reason that a thread would not respond is that it has blocked signals. For
145
    // example, glibc's timer_thread doesn't respond to our signal, so we always time out
146
    // on that one.
147
2.04k
    if (left_to_collect.load(std::memory_order_acquire) > 0) {
148
2.01k
      completion_flag.TimedWait(1s);
149
2.01k
    }
150
151
4.08k
    while (auto entry = collected.Pop()) {
152
2.04k
      auto it = std::lower_bound(tids.begin(), tids.end(), entry->tid);
153
2.04k
      if (it != tids.end() && *it == entry->tid) {
154
2.04k
        (*out)[it - tids.begin()] = entry->stack;
155
2.04k
      }
156
2.04k
      allocated.Push(entry);
157
2.04k
    }
158
2.04k
  }
159
160
2.04k
  void RecordStackTrace(const StackTrace& stack_trace) {
161
2.04k
    auto* entry = allocated.Pop();
162
2.04k
    if (entry) {
163
      // Not enough allocated entries, don't write log since we are in signal handler.
164
2.04k
      entry->tid = Thread::CurrentThreadIdForStack();
165
2.04k
      entry->stack = stack_trace;
166
2.04k
      collected.Push(entry);
167
2.04k
    }
168
169
2.04k
    if (left_to_collect.fetch_sub(1, std::memory_order_acq_rel) - 1 <= 0) {
170
2.04k
      completion_flag.Signal();
171
2.04k
    }
172
2.04k
  }
173
};
174
175
ThreadStackHelper thread_stack_helper;
176
177
// Signal handler for our stack trace signal.
178
// We expect that the signal is only sent from DumpThreadStack() -- not by a user.
179
180
2.04k
void HandleStackTraceSignal(int signum) {
181
2.04k
  int old_errno = errno;
182
2.04k
  StackTrace stack_trace;
183
2.04k
  stack_trace.Collect(2);
184
185
2.04k
  thread_stack_helper.RecordStackTrace(stack_trace);
186
2.04k
  errno = old_errno;
187
2.04k
}
188
189
// The signal that we'll use to communicate with our other threads.
190
// This can't be in used by other libraries in the process.
191
int g_stack_trace_signum = SIGUSR2;
192
193
19.5k
bool InitSignalHandlerUnlocked(int signum) {
194
19.5k
  enum InitState {
195
19.5k
    UNINITIALIZED,
196
19.5k
    INIT_ERROR,
197
19.5k
    INITIALIZED
198
19.5k
  };
199
19.5k
  static InitState state = UNINITIALIZED;
200
201
  // If we've already registered a handler, but we're being asked to
202
  // change our signal, unregister the old one.
203
19.5k
  if (signum != g_stack_trace_signum && state == INITIALIZED) {
204
3
    struct sigaction old_act;
205
3
    PCHECK(sigaction(g_stack_trace_signum, nullptr, &old_act) == 0);
206
3
    if (old_act.sa_handler == &HandleStackTraceSignal) {
207
3
      signal(g_stack_trace_signum, SIG_DFL);
208
3
    }
209
3
  }
210
211
  // If we'd previously had an error, but the signal number
212
  // is changing, we should mark ourselves uninitialized.
213
19.5k
  if (signum != g_stack_trace_signum) {
214
4
    g_stack_trace_signum = signum;
215
4
    state = UNINITIALIZED;
216
4
  }
217
218
19.5k
  if (state == UNINITIALIZED) {
219
10.8k
    struct sigaction old_act;
220
10.8k
    PCHECK(sigaction(g_stack_trace_signum, nullptr, &old_act) == 0);
221
10.8k
    if (old_act.sa_handler != SIG_DFL &&
222
1
        old_act.sa_handler != SIG_IGN) {
223
1
      state = INIT_ERROR;
224
1
      LOG(WARNING) << "signal handler for stack trace signal "
225
1
                   << g_stack_trace_signum
226
1
                   << " is already in use: "
227
1
                   << "YB will not produce thread stack traces.";
228
10.8k
    } else {
229
      // No one appears to be using the signal. This is racy, but there is no
230
      // atomic swap capability.
231
10.8k
      sighandler_t old_handler = signal(g_stack_trace_signum, HandleStackTraceSignal);
232
10.8k
      if (old_handler != SIG_IGN &&
233
10.8k
          old_handler != SIG_DFL) {
234
0
        LOG(FATAL) << "raced against another thread installing a signal handler";
235
0
      }
236
10.8k
      state = INITIALIZED;
237
10.8k
    }
238
10.8k
  }
239
19.5k
  return state == INITIALIZED;
240
19.5k
}
241
242
} // namespace
243
244
2.04k
Result<StackTrace> ThreadStack(ThreadIdForStack tid) {
245
2.04k
  return ThreadStacks({tid}).front();
246
2.04k
}
247
248
2.04k
std::vector<Result<StackTrace>> ThreadStacks(const std::vector<ThreadIdForStack>& tids) {
249
2.04k
  static const Status status = STATUS(
250
2.04k
      RuntimeError, "Thread did not respond: maybe it is blocking signals");
251
252
2.04k
  std::vector<Result<StackTrace>> result(tids.size(), status);
253
2.04k
  std::lock_guard<std::mutex> execution_lock(thread_stack_helper.mutex);
254
255
  // Ensure that our signal handler is installed. We don't need any fancy GoogleOnce here
256
  // because of the mutex above.
257
2.04k
  if (!InitSignalHandlerUnlocked(g_stack_trace_signum)) {
258
1
    static const Status status = STATUS(
259
1
        RuntimeError, "Unable to take thread stack: signal handler unavailable");
260
1
    std::fill_n(result.begin(), tids.size(), status);
261
1
    return result;
262
1
  }
263
264
2.04k
  thread_stack_helper.left_to_collect.store(tids.size(), std::memory_order_release);
265
2.04k
  thread_stack_helper.SetNumEntries(tids.size());
266
2.04k
  thread_stack_helper.completion_flag.Reset();
267
268
4.08k
  for (size_t i = 0; i != tids.size(); ++i) {
269
    // We use the raw syscall here instead of kill() to ensure that we don't accidentally
270
    // send a signal to some other process in the case that the thread has exited and
271
    // the TID been recycled.
272
#if defined(__linux__)
273
    int res = narrow_cast<int>(syscall(SYS_tgkill, getpid(), tids[i], g_stack_trace_signum));
274
#else
275
2.04k
    int res = pthread_kill(tids[i], g_stack_trace_signum);
276
2.04k
#endif
277
2.04k
    if (res != 0) {
278
1
      static const Status status = STATUS(
279
1
          RuntimeError, "Unable to deliver signal: process may have exited");
280
1
      result[i] = status;
281
1
      thread_stack_helper.left_to_collect.fetch_sub(1, std::memory_order_acq_rel);
282
1
    }
283
2.04k
  }
284
285
2.04k
  thread_stack_helper.StoreResult(tids, &result);
286
287
2.04k
  return result;
288
2.04k
}
289
290
2.04k
std::string DumpThreadStack(ThreadIdForStack tid) {
291
2.04k
  auto stack_trace = ThreadStack(tid);
292
2.04k
  if (!stack_trace.ok()) {
293
2
    return stack_trace.status().message().ToBuffer();
294
2
  }
295
2.04k
  return stack_trace->Symbolize();
296
2.04k
}
297
298
17.5k
Status SetStackTraceSignal(int signum) {
299
17.5k
  std::lock_guard<decltype(thread_stack_helper.mutex)> lock(thread_stack_helper.mutex);
300
17.5k
  if (!InitSignalHandlerUnlocked(signum)) {
301
1
    return STATUS(InvalidArgument, "Unable to install signal handler");
302
1
  }
303
17.5k
  return Status::OK();
304
17.5k
}
305
306
}  // namespace yb