/* * OXT - OS eXtensions for boosT * Provides important functionality necessary for writing robust server software. * * Copyright (c) 2008-2013 Phusion * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include #include #include #include "tracable_exception.hpp" #include "backtrace.hpp" #include "macros.hpp" #include "thread.hpp" #include "spin_lock.hpp" #include "detail/context.hpp" #ifdef __linux__ #include #endif #ifdef OXT_THREAD_LOCAL_KEYWORD_SUPPORTED #include #include #include #endif namespace oxt { using namespace std; using namespace boost; static global_context_t *global_context = NULL; /* * boost::thread_specific_storage is pretty expensive. So we use the __thread * keyword whenever possible - that's almost free. */ #ifdef OXT_THREAD_LOCAL_KEYWORD_SUPPORTED static __thread thread_local_context_ptr *local_context = NULL; __thread void *thread_signature = NULL; static void init_thread_local_context_support() { /* Do nothing. */ } void set_thread_local_context(const thread_local_context_ptr &ctx) { local_context = new thread_local_context_ptr(ctx); } static void free_thread_local_context() { delete local_context; local_context = NULL; } thread_local_context * get_thread_local_context() { if (OXT_LIKELY(local_context != NULL)) { return local_context->get(); } else { return NULL; } } #else /* * This is a *pointer* to a thread_specific_ptr because, once * we've created a thread_specific_ptr, we never want to destroy it * in order to avoid C++'s global variable destruction. All kinds * of cleanup code may depend on local_context and because global * variable destruction order is undefined, we just want to keep * this object alive until the OS cleans it up. */ static thread_specific_ptr *local_context = NULL; static void init_thread_local_context_support() { local_context = new thread_specific_ptr(); } void set_thread_local_context(const thread_local_context_ptr &ctx) { if (local_context != NULL) { local_context->reset(new thread_local_context_ptr(ctx)); } } static void free_thread_local_context() { if (local_context != NULL) { local_context->reset(); } } thread_local_context * get_thread_local_context() { if (OXT_LIKELY(local_context != NULL)) { thread_local_context_ptr *pointer = local_context->get(); if (OXT_LIKELY(pointer != NULL)) { return pointer->get(); } else { return NULL; } } else { return NULL; } } #endif #ifdef OXT_BACKTRACE_IS_ENABLED trace_point::trace_point(const char *_function, const char *_source, unsigned short _line, const char *_data) : function(_function), source(_source), data(_data), line(_line), m_detached(false) { thread_local_context *ctx = get_thread_local_context(); if (OXT_LIKELY(ctx != NULL)) { spin_lock::scoped_lock l(ctx->backtrace_lock); ctx->backtrace_list.push_back(this); } else { m_detached = true; } } trace_point::trace_point(const char *_function, const char *_source, unsigned short _line, const char *_data, const detached &detached_tag) : function(_function), source(_source), data(_data), line(_line), m_detached(true) { } trace_point::~trace_point() { if (OXT_LIKELY(!m_detached)) { thread_local_context *ctx = get_thread_local_context(); if (OXT_LIKELY(ctx != NULL)) { spin_lock::scoped_lock l(ctx->backtrace_lock); assert(!ctx->backtrace_list.empty()); ctx->backtrace_list.pop_back(); } } } void trace_point::update(const char *source, unsigned short line) { this->source = source; this->line = line; } tracable_exception::tracable_exception() { thread_local_context *ctx = get_thread_local_context(); if (OXT_LIKELY(ctx != NULL)) { spin_lock::scoped_lock l(ctx->backtrace_lock); vector::const_iterator it, end = ctx->backtrace_list.end(); backtrace_copy.reserve(ctx->backtrace_list.size()); for (it = ctx->backtrace_list.begin(); it != end; it++) { trace_point *p = new trace_point( (*it)->function, (*it)->source, (*it)->line, (*it)->data, trace_point::detached()); backtrace_copy.push_back(p); } } } tracable_exception::tracable_exception(const tracable_exception &other) : std::exception() { vector::const_iterator it, end = other.backtrace_copy.end(); backtrace_copy.reserve(other.backtrace_copy.size()); for (it = other.backtrace_copy.begin(); it != end; it++) { trace_point *p = new trace_point( (*it)->function, (*it)->source, (*it)->line, (*it)->data, trace_point::detached()); backtrace_copy.push_back(p); } } tracable_exception::tracable_exception(const no_backtrace &tag) : std::exception() { // Do nothing. } tracable_exception::~tracable_exception() throw() { vector::iterator it, end = backtrace_copy.end(); for (it = backtrace_copy.begin(); it != end; it++) { delete *it; } } template static string format_backtrace(const Collection &backtrace_list) { if (backtrace_list.empty()) { return " (empty)"; } else { backtrace_list.rbegin(); stringstream result; typename Collection::const_reverse_iterator it; for (it = backtrace_list.rbegin(); it != backtrace_list.rend(); it++) { const trace_point *p = *it; result << " in '" << p->function << "'"; if (p->source != NULL) { const char *source = strrchr(p->source, '/'); if (source != NULL) { source++; } else { source = p->source; } result << " (" << source << ":" << p->line << ")"; if (p->data != NULL) { result << " -- " << p->data; } } result << endl; } return result.str(); } } string tracable_exception::backtrace() const throw() { return format_backtrace< vector >(backtrace_copy); } const char * tracable_exception::what() const throw() { return "oxt::tracable_exception"; } #endif /* OXT_BACKTRACE_IS_ENABLED */ void initialize() { global_context = new global_context_t(); init_thread_local_context_support(); // For some reason make_shared() crashes here when compiled with clang 3.2 on OS X. // Clang bug? We use 'new' to work around it. thread_local_context_ptr ctx = thread_local_context::make_shared_ptr(); ctx->thread_number = 1; ctx->thread_name = "Main thread"; set_thread_local_context(ctx); ctx->thread = pthread_self(); global_context->registered_threads.push_back(ctx); ctx->iterator = global_context->registered_threads.end(); ctx->iterator--; } global_context_t::global_context_t() : next_thread_number(2) { } thread_local_context_ptr thread_local_context::make_shared_ptr() { // For some reason make_shared() crashes here when compiled with clang 3.2 on OS X. // Clang bug? We use 'new' to work around it. return thread_local_context_ptr(new thread_local_context()); } thread_local_context::thread_local_context() : thread_number(0) { thread = pthread_self(); #ifdef __linux__ tid = syscall(SYS_gettid); #endif syscall_interruption_lock.lock(); #ifdef OXT_BACKTRACE_IS_ENABLED backtrace_list.reserve(50); #endif } string thread::make_thread_name(const string &given_name) { if (given_name.empty()) { if (OXT_LIKELY(global_context != NULL)) { stringstream str; str << "Thread #"; { boost::lock_guard l(global_context->thread_registration_mutex); str << global_context->next_thread_number; } return str.str(); } else { return "(unknown)"; } } else { return given_name; } } void thread::thread_main(const boost::function func, thread_local_context_ptr ctx) { set_thread_local_context(ctx); if (OXT_LIKELY(global_context != NULL)) { boost::lock_guard l(global_context->thread_registration_mutex); ctx->thread = pthread_self(); global_context->next_thread_number++; global_context->registered_threads.push_back(ctx); ctx->iterator = global_context->registered_threads.end(); ctx->iterator--; // Set this after setting 'iterator' to indicate // that push_back() has succeeded. ctx->thread_number = global_context->next_thread_number; } try { func(); } catch (const thread_interrupted &) { // Do nothing. } // We don't care about other exceptions because they'll crash the process anyway. if (OXT_LIKELY(global_context != NULL)) { boost::lock_guard l(global_context->thread_registration_mutex); thread_local_context *ctx = get_thread_local_context(); if (ctx != 0 && ctx->thread_number != 0) { global_context->registered_threads.erase(ctx->iterator); ctx->thread_number = 0; } } free_thread_local_context(); } std::string thread::name() const throw() { return context->thread_name; } std::string thread::backtrace() const throw() { #ifdef OXT_BACKTRACE_IS_ENABLED spin_lock::scoped_lock l(context->backtrace_lock); return format_backtrace(context->backtrace_list); #else return " (backtrace support disabled during compile time)"; #endif } string thread::all_backtraces() throw() { #ifdef OXT_BACKTRACE_IS_ENABLED if (OXT_LIKELY(global_context != NULL)) { boost::lock_guard l(global_context->thread_registration_mutex); list::const_iterator it; std::stringstream result; for (it = global_context->registered_threads.begin(); it != global_context->registered_threads.end(); it++) { thread_local_context_ptr ctx = *it; result << "Thread '" << ctx->thread_name << "' (" << hex << showbase << ctx->thread << dec; #ifdef __linux__ result << ", LWP " << ctx->tid; #endif result << "):" << endl; spin_lock::scoped_lock l(ctx->backtrace_lock); std::string bt = format_backtrace(ctx->backtrace_list); result << bt; if (bt.empty() || bt[bt.size() - 1] != '\n') { result << endl; } result << endl; } return result.str(); } else { return "(OXT not initialized)"; } #else return "(backtrace support disabled during compile time)"; #endif } void thread::interrupt(bool interruptSyscalls) { int ret; boost::thread::interrupt(); if (interruptSyscalls && context->syscall_interruption_lock.try_lock()) { do { ret = pthread_kill(native_handle(), INTERRUPTION_SIGNAL); } while (ret == EINTR); context->syscall_interruption_lock.unlock(); } } } // namespace oxt