Commit 20bd643d authored by Dmitry Pleshkov's avatar Dmitry Pleshkov Committed by facebook-github-bot-4

Implementing callback functionality for exception routines

Summary: Depends on D2865911

Reviewed By: luciang

Differential Revision: D2742158

fb-gh-sync-id: 3e7866a742575ee4f7501cff0abbd5c21e26a46e
parent c046205c
/*
* Copyright 2016 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <folly/experimental/exception_tracer/ExceptionCounterLib.h>
#include <iosfwd>
#include <unordered_map>
#include <folly/RWSpinLock.h>
#include <folly/Synchronized.h>
#include <folly/ThreadLocal.h>
#include <folly/experimental/exception_tracer/ExceptionTracerLib.h>
#include <folly/experimental/exception_tracer/StackTrace.h>
#include <folly/experimental/symbolizer/Symbolizer.h>
using namespace folly::exception_tracer;
namespace {
// We are using hash of the stack trace to uniquely identify the exception
using ExceptionId = uintptr_t;
using ExceptionStatsHolderType =
std::unordered_map<ExceptionId, ExceptionStats>;
struct ExceptionStatsStorage {
void appendTo(ExceptionStatsHolderType& data) {
ExceptionStatsHolderType tempHolder;
SYNCHRONIZED(statsHolder) {
using std::swap;
swap(statsHolder, tempHolder);
}
for (const auto& myData : tempHolder) {
const auto& myStat = myData.second;
auto it = data.find(myData.first);
if (it != data.end()) {
it->second.count += myStat.count;
} else {
data.insert(myData);
}
}
}
folly::Synchronized<ExceptionStatsHolderType, folly::RWSpinLock> statsHolder;
};
class Tag {};
folly::ThreadLocal<ExceptionStatsStorage, Tag> gExceptionStats;
} // namespace
namespace folly {
namespace exception_tracer {
std::vector<ExceptionStats> getExceptionStatistics() {
ExceptionStatsHolderType accumulator;
for (auto& threadStats : gExceptionStats.accessAllThreads()) {
threadStats.appendTo(accumulator);
}
std::vector<ExceptionStats> result;
result.reserve(accumulator.size());
for (const auto& item : accumulator) {
result.push_back(item.second);
}
std::sort(result.begin(),
result.end(),
[](const ExceptionStats& lhs, const ExceptionStats& rhs) {
return (lhs.count > rhs.count);
});
return result;
}
std::ostream& operator<<(std::ostream& out, const ExceptionStats& stats) {
out << "Exception report: " << std::endl;
out << "Exception count: " << stats.count << std::endl;
out << stats.info;
return out;
}
} // namespace exception_tracer
} // namespace folly
namespace {
/*
* This handler gathers statistics on all exceptions thrown by the program
* Information is being stored in thread local storage.
*/
void throwHandler(void*, std::type_info* exType, void (*)(void*)) noexcept {
ExceptionInfo info;
info.type = exType;
auto& frames = info.frames;
frames.resize(kMaxFrames);
auto n = folly::symbolizer::getStackTrace(frames.data(), kMaxFrames);
if (n == -1) {
LOG(ERROR) << "Invalid stack frame";
return;
}
frames.resize(n);
auto exceptionId = folly::hash::hash_range(frames.begin(), frames.end());
SYNCHRONIZED(holder, gExceptionStats->statsHolder) {
auto it = holder.find(exceptionId);
if (it != holder.end()) {
++it->second.count;
} else {
holder.emplace(exceptionId, ExceptionStats{1, std::move(info)});
}
}
}
struct Initializer {
Initializer() { registerCxaThrowCallback(throwHandler); }
};
Initializer initializer;
} // namespace
/*
* Copyright 2016 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <vector>
#include <ostream>
#include <folly/experimental/exception_tracer/ExceptionTracer.h>
namespace folly {
namespace exception_tracer {
struct ExceptionStats {
uint64_t count;
ExceptionInfo info;
};
/**
* This function accumulates exception throwing statistics across all threads.
* Please note, that during call to this function, other threads might block
* on exception throws, so it should be called seldomly.
* All pef-thread statistics is being reset by the call.
*/
std::vector<ExceptionStats> getExceptionStatistics();
std::ostream& operator<<(std::ostream& out, const ExceptionStats& data);
} // namespace exception_tracer
} // namespace folly
/*
* Copyright 2016 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <exception>
#include <folly/experimental/exception_tracer/ExceptionAbi.h>
#include <folly/experimental/exception_tracer/StackTrace.h>
#include <folly/experimental/exception_tracer/ExceptionTracer.h>
#include <folly/experimental/exception_tracer/ExceptionTracerLib.h>
#include <folly/experimental/symbolizer/Symbolizer.h>
using namespace folly::exception_tracer;
namespace {
// If we somehow ended up in an invalid state, we don't want to print any stack
// trace at all because in could be bogus
FOLLY_TLS bool invalid;
FOLLY_TLS StackTraceStack activeExceptions;
FOLLY_TLS StackTraceStack caughtExceptions;
} // namespace
// This function is exported and may be found via dlsym(RTLD_NEXT, ...)
extern "C" StackTraceStack* getExceptionStackTraceStack() {
return invalid ? nullptr : &caughtExceptions;
}
namespace {
void addActiveException() {
// Capture stack trace
if (!invalid) {
if (!activeExceptions.pushCurrent()) {
activeExceptions.clear();
caughtExceptions.clear();
invalid = true;
}
}
}
void moveTopException(StackTraceStack& from, StackTraceStack& to) {
if (invalid) {
return;
}
if (!to.moveTopFrom(from)) {
from.clear();
to.clear();
invalid = true;
}
}
struct Initializer {
Initializer() {
registerCxaThrowCallback(
[](void*, std::type_info*, void (*)(void*)) { addActiveException(); });
registerCxaBeginCatchCallback(
[](void*) { moveTopException(activeExceptions, caughtExceptions); });
registerCxaRethrowCallback(
[]() { moveTopException(caughtExceptions, activeExceptions); });
registerCxaEndCatchCallback([]() {
if (invalid) {
return;
}
__cxxabiv1::__cxa_exception* top =
__cxxabiv1::__cxa_get_globals_fast()->caughtExceptions;
// This is gcc specific and not specified in the ABI:
// abs(handlerCount) is the number of active handlers, it's negative
// for rethrown exceptions and positive (always 1) for regular
// exceptions.
// In the rethrow case, we've already popped the exception off the
// caught stack, so we don't do anything here.
if (top->handlerCount == 1) {
if (!caughtExceptions.pop()) {
activeExceptions.clear();
invalid = true;
}
}
});
registerRethrowExceptionCallback(
[](std::exception_ptr) { addActiveException(); });
try {
::folly::exception_tracer::installHandlers();
} catch (...) {
}
}
};
Initializer initializer;
} // namespace
......@@ -55,12 +55,12 @@ std::ostream& operator<<(std::ostream& out, const ExceptionInfo& info) {
<< ")\n";
try {
size_t frameCount = info.frames.size();
// Skip our own internal frames
static constexpr size_t skip = 3;
if (frameCount > skip) {
auto addresses = info.frames.data() + skip;
frameCount -= skip;
// Skip our own internal frames
static constexpr size_t kInternalFramesNumber = 3;
if (frameCount > kInternalFramesNumber) {
auto addresses = info.frames.data() + kInternalFramesNumber;
frameCount -= kInternalFramesNumber;
std::vector<SymbolizedFrame> frames;
frames.resize(frameCount);
......
......@@ -14,17 +14,15 @@
* limitations under the License.
*/
#include <folly/experimental/exception_tracer/ExceptionTracerLib.h>
#include <dlfcn.h>
#include <pthread.h>
#include <stdlib.h>
#include <glog/logging.h>
#include <vector>
#include <folly/Portability.h>
#include <folly/experimental/exception_tracer/StackTrace.h>
#include <folly/experimental/exception_tracer/ExceptionAbi.h>
#include <folly/experimental/exception_tracer/ExceptionTracer.h>
#include <folly/experimental/symbolizer/Symbolizer.h>
#include <folly/SharedMutex.h>
#include <folly/Synchronized.h>
namespace __cxxabiv1 {
......@@ -34,6 +32,7 @@ FOLLY_NORETURN void __cxa_throw(void* thrownException,
void (*destructor)(void*));
void* __cxa_begin_catch(void* excObj) throw();
FOLLY_NORETURN void __cxa_rethrow(void);
void __cxa_rethrow(void);
void __cxa_end_catch(void);
}
......@@ -43,93 +42,60 @@ using namespace folly::exception_tracer;
namespace {
FOLLY_TLS bool invalid;
FOLLY_TLS StackTraceStack activeExceptions;
FOLLY_TLS StackTraceStack caughtExceptions;
pthread_once_t initialized = PTHREAD_ONCE_INIT;
template <typename Function>
class CallbackHolder {
public:
void registerCallback(Function f) {
SYNCHRONIZED(callbacks_) { callbacks_.push_back(std::move(f)); }
}
extern "C" {
FOLLY_NORETURN typedef void (*CxaThrowType)(void*,
std::type_info*,
void (*)(void*));
typedef void* (*CxaBeginCatchType)(void*);
FOLLY_NORETURN typedef void (*CxaRethrowType)(void);
typedef void (*CxaEndCatchType)(void);
CxaThrowType orig_cxa_throw;
CxaBeginCatchType orig_cxa_begin_catch;
CxaRethrowType orig_cxa_rethrow;
CxaEndCatchType orig_cxa_end_catch;
} // extern "C"
FOLLY_NORETURN typedef void (*RethrowExceptionType)(std::exception_ptr);
RethrowExceptionType orig_rethrow_exception;
void initialize() {
orig_cxa_throw = (CxaThrowType)dlsym(RTLD_NEXT, "__cxa_throw");
orig_cxa_begin_catch =
(CxaBeginCatchType)dlsym(RTLD_NEXT, "__cxa_begin_catch");
orig_cxa_rethrow =
(CxaRethrowType)dlsym(RTLD_NEXT, "__cxa_rethrow");
orig_cxa_end_catch = (CxaEndCatchType)dlsym(RTLD_NEXT, "__cxa_end_catch");
// Mangled name for std::rethrow_exception
// TODO(tudorb): Dicey, as it relies on the fact that std::exception_ptr
// is typedef'ed to a type in namespace __exception_ptr
orig_rethrow_exception =
(RethrowExceptionType)dlsym(
RTLD_NEXT,
"_ZSt17rethrow_exceptionNSt15__exception_ptr13exception_ptrE");
if (!orig_cxa_throw || !orig_cxa_begin_catch || !orig_cxa_rethrow ||
!orig_cxa_end_catch || !orig_rethrow_exception) {
abort(); // what else can we do?
// always inline to enforce kInternalFramesNumber
template <typename... Args>
FOLLY_ALWAYS_INLINE void invoke(Args... args) {
SYNCHRONIZED_CONST(callbacks_) {
for (auto& cb : callbacks_) {
cb(args...);
}
}
}
}
} // namespace
private:
folly::Synchronized<std::vector<Function>> callbacks_;
};
// This function is exported and may be found via dlsym(RTLD_NEXT, ...)
extern "C" StackTraceStack* getExceptionStackTraceStack() {
return invalid ? nullptr : &caughtExceptions;
}
} // namespace
namespace {
namespace folly {
namespace exception_tracer {
// Make sure we're counting stack frames correctly, don't inline.
FOLLY_NOINLINE void addActiveException();
void addActiveException() {
pthread_once(&initialized, initialize);
// Capture stack trace
if (!invalid) {
if (!activeExceptions.pushCurrent()) {
activeExceptions.clear();
caughtExceptions.clear();
invalid = true;
#define DECLARE_CALLBACK(NAME) \
CallbackHolder<NAME##Type>& get##NAME##Callbacks() { \
static CallbackHolder<NAME##Type> Callbacks; \
return Callbacks; \
} \
void register##NAME##Callback(NAME##Type callback) { \
get##NAME##Callbacks().registerCallback(callback); \
}
}
}
void moveTopException(StackTraceStack& from, StackTraceStack& to) {
if (invalid) {
return;
}
if (!to.moveTopFrom(from)) {
from.clear();
to.clear();
invalid = true;
}
}
DECLARE_CALLBACK(CxaThrow);
DECLARE_CALLBACK(CxaBeginCatch);
DECLARE_CALLBACK(CxaRethrow);
DECLARE_CALLBACK(CxaEndCatch);
DECLARE_CALLBACK(RethrowException);
} // namespace
} // exception_tracer
} // folly
namespace __cxxabiv1 {
void __cxa_throw(void* thrownException,
std::type_info* type,
void (*destructor)(void*)) {
addActiveException();
static auto orig_cxa_throw =
reinterpret_cast<decltype(&__cxa_throw)>(dlsym(RTLD_NEXT, "__cxa_throw"));
getCxaThrowCallbacks().invoke(thrownException, type, destructor);
orig_cxa_throw(thrownException, type, destructor);
__builtin_unreachable(); // orig_cxa_throw never returns
}
void __cxa_rethrow() {
......@@ -138,31 +104,26 @@ void __cxa_rethrow() {
// we'll implement something simpler (and slower): we pop the exception from
// the caught stack, and push it back onto the active stack; this way, our
// implementation of __cxa_begin_catch doesn't have to do anything special.
moveTopException(caughtExceptions, activeExceptions);
static auto orig_cxa_rethrow = reinterpret_cast<decltype(&__cxa_rethrow)>(
dlsym(RTLD_NEXT, "__cxa_rethrow"));
getCxaRethrowCallbacks().invoke();
orig_cxa_rethrow();
__builtin_unreachable(); // orig_cxa_rethrow never returns
}
void* __cxa_begin_catch(void *excObj) throw() {
void* __cxa_begin_catch(void* excObj) throw() {
// excObj is a pointer to the unwindHeader in __cxa_exception
moveTopException(activeExceptions, caughtExceptions);
static auto orig_cxa_begin_catch =
reinterpret_cast<decltype(&__cxa_begin_catch)>(
dlsym(RTLD_NEXT, "__cxa_begin_catch"));
getCxaBeginCatchCallbacks().invoke(excObj);
return orig_cxa_begin_catch(excObj);
}
void __cxa_end_catch() {
if (!invalid) {
__cxa_exception* top = __cxa_get_globals_fast()->caughtExceptions;
// This is gcc specific and not specified in the ABI:
// abs(handlerCount) is the number of active handlers, it's negative
// for rethrown exceptions and positive (always 1) for regular exceptions.
// In the rethrow case, we've already popped the exception off the
// caught stack, so we don't do anything here.
if (top->handlerCount == 1) {
if (!caughtExceptions.pop()) {
activeExceptions.clear();
invalid = true;
}
}
}
static auto orig_cxa_end_catch = reinterpret_cast<decltype(&__cxa_end_catch)>(
dlsym(RTLD_NEXT, "__cxa_end_catch"));
getCxaEndCatchCallbacks().invoke();
orig_cxa_end_catch();
}
......@@ -171,24 +132,16 @@ void __cxa_end_catch() {
namespace std {
void rethrow_exception(std::exception_ptr ep) {
addActiveException();
// Mangled name for std::rethrow_exception
// TODO(tudorb): Dicey, as it relies on the fact that std::exception_ptr
// is typedef'ed to a type in namespace __exception_ptr
static auto orig_rethrow_exception =
reinterpret_cast<decltype(&rethrow_exception)>(
dlsym(RTLD_NEXT,
"_ZSt17rethrow_exceptionNSt15__exception_ptr13exception_ptrE"));
getRethrowExceptionCallbacks().invoke(ep);
orig_rethrow_exception(ep);
__builtin_unreachable(); // orig_rethrow_exception never returns
}
} // namespace std
namespace {
struct Initializer {
Initializer() {
try {
::folly::exception_tracer::installHandlers();
} catch (...) {
}
}
};
Initializer initializer;
} // namespace
/*
* Copyright 2016 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FOLLY_EXPERIMENTAL_EXCEPTION_TRACER_EXCEPTIONTRACERLIB_H_
#define FOLLY_EXPERIMENTAL_EXCEPTION_TRACER_EXCEPTIONTRACERLIB_H_
#include <typeinfo>
#include <exception>
namespace folly {
namespace exception_tracer {
namespace detail {
/*
* Unfortunately due to ambiguous nature of exception specifiers,
* standard does not allow them to appear in typedefs or alias-declarations.
* We, however, want callbacks to be exception safe.
* This dummies are an ugly workaround that problem.
*/
void dummyCxaThrow(void*, std::type_info*, void (*)(void*)) noexcept;
void dummyCxaBeginCatch(void*) noexcept;
void dummyCxaRethrow(void) noexcept;
void dummyCxaEndCatch(void) noexcept;
void dummyRethrowException(std::exception_ptr) noexcept;
}
using CxaThrowType = decltype(&detail::dummyCxaThrow);
using CxaBeginCatchType = decltype(&detail::dummyCxaBeginCatch);
using CxaRethrowType = decltype(&detail::dummyCxaRethrow);
using CxaEndCatchType = decltype(&detail::dummyCxaEndCatch);
using RethrowExceptionType = decltype(&detail::dummyRethrowException);
void registerCxaThrowCallback(CxaThrowType callback);
void registerCxaBeginCatchCallback(CxaBeginCatchType callback);
void registerCxaRethrowCallback(CxaRethrowType callback);
void registerCxaEndCatchCallback(CxaEndCatchType callback);
void registerRethrowExceptionCallback(RethrowExceptionType callback);
}
}
#endif /* FOLLY_EXPERIMENTAL_EXCEPTION_TRACER_EXCEPTIONTRACERLIB_H_ */
/*
* Copyright 2016 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdexcept>
#include <typeinfo>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <sstream>
#include <gtest/gtest.h>
#include <folly/experimental/exception_tracer/ExceptionCounterLib.h>
struct MyException {};
void bar() { throw std::runtime_error("hello"); }
void foo() { throw MyException(); }
void baz() { foo(); }
using namespace folly::exception_tracer;
template <typename F>
void throwAndCatch(F f) {
try {
f();
} catch (...) {
// ignore
}
}
TEST(ExceptionCounter, oneThread) {
throwAndCatch(foo);
for (int i = 0; i < 10; ++i) {
throwAndCatch(bar);
}
auto stats = getExceptionStatistics();
EXPECT_EQ(stats.size(), 2);
EXPECT_EQ(stats[0].count, 10);
EXPECT_EQ(stats[1].count, 1);
EXPECT_EQ(*(stats[0].info.type), typeid(std::runtime_error));
EXPECT_EQ(*(stats[1].info.type), typeid(MyException));
}
TEST(ExceptionCounter, testClearExceptionStatistics) {
throwAndCatch(foo);
auto stats = getExceptionStatistics();
EXPECT_EQ(stats.size(), 1);
stats = getExceptionStatistics();
EXPECT_EQ(stats.size(), 0);
}
TEST(ExceptionCounter, testDifferentStacks) {
throwAndCatch(foo);
throwAndCatch(baz);
auto stats = getExceptionStatistics();
EXPECT_EQ(stats.size(), 2);
}
TEST(ExceptionCounter, multyThreads) {
constexpr size_t kNumIterations = 10000;
constexpr size_t kNumThreads = 10;
std::vector<std::thread> threads;
threads.resize(kNumThreads);
std::mutex preparedMutex;
std::mutex finishedMutex;
std::condition_variable preparedBarrier;
std::condition_variable finishedBarrier;
int preparedThreads = 0;
bool finished = false;
for (auto& t : threads) {
t = std::thread([&]() {
for (size_t i = 0; i < kNumIterations; ++i) {
throwAndCatch(foo);
}
{
std::unique_lock<std::mutex> lock(preparedMutex);
++preparedThreads;
preparedBarrier.notify_one();
}
std::unique_lock<std::mutex> lock(finishedMutex);
finishedBarrier.wait(lock, [&]() { return finished; });
});
}
{
std::unique_lock<std::mutex> lock(preparedMutex);
preparedBarrier.wait(lock,
[&]() { return preparedThreads == kNumThreads; });
}
auto stats = getExceptionStatistics();
EXPECT_EQ(stats.size(), 1);
EXPECT_EQ(stats[0].count, kNumIterations * kNumThreads);
{
std::unique_lock<std::mutex> lock(finishedMutex);
finished = true;
finishedBarrier.notify_all();
}
for (auto& t : threads) {
t.join();
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment