--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
<< ")\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);
* 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 {
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);
}
-} // namespace __cxxabiv1
+} // namespace __cxxabiv1
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() {
// 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();
}
-} // namespace __cxxabiv1
+} // namespace __cxxabiv1
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
+} // namespace std
--- /dev/null
+/*
+ * 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_ */
--- /dev/null
+/*
+ * 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();
+ }
+}