From: Fred T. Hamster Date: Wed, 18 Feb 2026 00:39:45 +0000 (-0500) Subject: build fully working with callstack tracker X-Git-Url: https://feistymeow.org/gitweb/?a=commitdiff_plain;h=refs%2Fheads%2Fdev;p=feisty_meow.git build fully working with callstack tracker just had a totally successful build for the first time in a while, and this is with the callstack tracker enabled. it does seem that our threaded woes with the callstack code were due to our attempting to use thread local for the tracker; it needs to persist past the end of the thread to avoid gnarly shutdown issues with the static objects, like the synchronizer, which was also thread local for some reason. now we just use a program wide object to list the thread-wide callstack trackers, and it's nice and fast due to using the standard library unordered_map. was using our own hash table, but there are too many intertwined things with the callstack tracker for that to be a good option, so standard template library it is. --- diff --git a/nucleus/library/application/callstack_tracker.cpp b/nucleus/library/application/callstack_tracker.cpp index f0c9283c..9060226f 100644 --- a/nucleus/library/application/callstack_tracker.cpp +++ b/nucleus/library/application/callstack_tracker.cpp @@ -26,6 +26,8 @@ #include #include #include +#include +#include using namespace basis; @@ -40,43 +42,78 @@ const int MAX_TEXT_FIELD = 1024; const char *emptiness_note = "Empty Stack\n"; //!< what we show when the stack is empty. +const int STACK_TRACKER_ELEMS = 200; + //!< fairly generous thread allotment for callstacks, before we start bucketing. + +// uncomment for noisier debugging logging. +//#define DEBUG_CALLSTACK_TRACKER + ////////////// basis::mutex &callstack_tracker::__callstack_tracker_synchronizer() { - thread_local basis::mutex __global_synch_callstacks; + static basis::mutex __global_synch_callstacks; return __global_synch_callstacks; } ////////////// -//! the single instance of callstack_tracker. +class tracker_hashing : public std::unordered_map +{ +public: + tracker_hashing() : unordered_map() {} +}; + +tracker_hashing &__tracker_hashing() +{ + static tracker_hashing __global_tracker; + return __global_tracker; +} + +//! the provider of thread-wide (single instance per thread) callstack_trackers. /*! - this is also an ultra low-level object, although it's not as far down - as the memory checker. it can allocate c++ objects and that kind of thing - just fine. + this code is ultra low-level in our code stack, although it's not as far down as + the memory checker. it can allocate c++ objects and that kind of thing just fine. */ callstack_tracker &thread_wide_stack_trace() { auto_synchronizer l(callstack_tracker::__callstack_tracker_synchronizer()); - thread_local callstack_tracker *_hidden_trace = NULL_POINTER; - if (!_hidden_trace) { + tracker_hashing &thc = __tracker_hashing(); + callstack_tracker *pointy = NULL_POINTER; + pid_t my_thread_id = gettid(); +#ifdef DEBUG_CALLSTACK_TRACKER + printf("looking for thread id %ld in callstack lists...\n", (long)my_thread_id); +#endif + if (auto seeker = thc.find(my_thread_id); seeker != thc.end()) { + my_thread_id = seeker->first; + pointy = seeker->second; +#ifdef DEBUG_CALLSTACK_TRACKER + printf("found existing callstack_tracker for thread %ld\n", (long)my_thread_id); +#endif + } else { #ifdef ENABLE_MEMORY_HOOK program_wide_memories().disable(); /* we don't want infinite loops tracking the call stack during this object's construction. */ - //hmmm: does that disable the progwide memories for the whole program or just for this thread? // and what does that entail exactly? // did it actually fix the problem we saw? - #endif - _hidden_trace = new callstack_tracker; +#ifdef DEBUG_CALLSTACK_TRACKER + printf("adding new callstack_tracker for thread %ld\n", (long)my_thread_id); +#endif + pointy = new callstack_tracker; + thc.insert({my_thread_id, pointy}); #ifdef ENABLE_MEMORY_HOOK program_wide_memories().enable(); #endif } - return *_hidden_trace; + +//hmmm: now scan for dead threads here! +// costly though! have to iterate across all the ids. +// how about doing it on a timed basis? + + return *pointy; } ////////////// @@ -118,8 +155,9 @@ bool callstack_tracker::push_frame(const char *class_name, const char *func, const char *file, int line) { auto_synchronizer l(callstack_tracker::__callstack_tracker_synchronizer()); - -//printf("callstack pushframe depth=%d in\n", _depth); +#ifdef DEBUG_CALLSTACK_TRACKER + printf(">> in, callstack pushframe depth=%d\n", _depth); +#endif if (_unusable) return false; if (_depth >= MAX_STACK_DEPTH) { // too many frames already. @@ -131,15 +169,18 @@ bool callstack_tracker::push_frame(const char *class_name, const char *func, if (_depth > _highest) _highest = _depth; _frames_in += 1; _bt->_records[_depth].assign(class_name, func, file, line); -//printf("callstack pushframe depth=%d out\n", _depth); +#ifdef DEBUG_CALLSTACK_TRACKER + printf("<< out, callstack pushframe depth=%d\n", _depth); +#endif return true; } bool callstack_tracker::pop_frame() { auto_synchronizer l(callstack_tracker::__callstack_tracker_synchronizer()); - -//printf("callstack popframe depth=%d in\n", _depth); +#ifdef DEBUG_CALLSTACK_TRACKER + printf(">> in, callstack popframe depth=%d\n", _depth); +#endif if (_unusable) return false; if (_depth <= 0) { // how inappropriate of them; we have no frames. @@ -150,7 +191,9 @@ bool callstack_tracker::pop_frame() _bt->_records[_depth].clean(); _depth--; _frames_out += 1; -//printf("callstack popframe depth=%d out\n", _depth); +#ifdef DEBUG_CALLSTACK_TRACKER + printf("<< out, callstack popframe depth=%d\n", _depth); +#endif return true; } @@ -181,7 +224,9 @@ char *callstack_tracker::full_trace() const if (_unusable) return strdup(""); int full_size_needed = full_trace_size(); char *to_return = (char *)malloc(full_size_needed); -//printf("fulltrace allocated %d bytes for trace.\n", full_size_needed); +#ifdef DEBUG_CALLSTACK_TRACKER + printf("fulltrace allocated %d bytes for trace.\n", full_size_needed); +#endif to_return[0] = '\0'; if (!_depth) { strcat(to_return, emptiness_note); @@ -277,9 +322,13 @@ frame_tracking_instance::frame_tracking_instance(const char *class_name, _line(line) { if (_frame_involved) { -//printf("frametrackinst ctor in class=%s func=%s\n", class_name, func); +#ifdef DEBUG_CALLSTACK_TRACKER + printf(">> in, frame_tracking_instance ctor, class=%s func=%s\n", class_name, func); +#endif thread_wide_stack_trace().push_frame(class_name, func, file, line); -//printf("frametrackinst ctor out\n"); +#ifdef DEBUG_CALLSTACK_TRACKER + printf("<< out, frame_tracking_instance ctor\n"); +#endif } } @@ -298,7 +347,9 @@ frame_tracking_instance::~frame_tracking_instance() { clean(); } void frame_tracking_instance::clean() { if (_frame_involved) { -//printf("frametrackinst clean\n"); +#ifdef DEBUG_CALLSTACK_TRACKER + printf("frame_tracking_instance clean\n"); +#endif thread_wide_stack_trace().pop_frame(); } _frame_involved = false; @@ -311,10 +362,14 @@ void frame_tracking_instance::clean() frame_tracking_instance &frame_tracking_instance::operator = (const frame_tracking_instance &to_copy) { -//printf("frametrackinst tor = in\n"); +#ifdef DEBUG_CALLSTACK_TRACKER + printf(">> in, frametrackinst assignment\n"); +#endif if (this == &to_copy) return *this; assign(to_copy._class, to_copy._func, to_copy._file, to_copy._line); -//printf("frametrackinst tor = out\n"); +#ifdef DEBUG_CALLSTACK_TRACKER + printf("<< out, frametrackinst assignment\n"); +#endif return *this; } @@ -331,9 +386,13 @@ void frame_tracking_instance::assign(const char *class_name, const char *func, void update_current_stack_frame_line_number(int line) { -//printf("frametrackinst updatelinenum in\n"); +#ifdef DEBUG_CALLSTACK_TRACKER + printf(">> in, frame_tracking_instance update line num\n"); +#endif thread_wide_stack_trace().update_line(line); -//printf("frametrackinst updatelinenum out\n"); +#ifdef DEBUG_CALLSTACK_TRACKER + printf("<< out, frame_tracking_instance update line num\n"); +#endif } } // namespace diff --git a/nucleus/library/structures/hash_table.h b/nucleus/library/structures/hash_table.h index 6423d413..d6685ac7 100644 --- a/nucleus/library/structures/hash_table.h +++ b/nucleus/library/structures/hash_table.h @@ -27,6 +27,11 @@ namespace structures { +const int HASH_FUDGE_FACTOR = 10; + // we fudge the number of elements requested by adding a little chunk, just in case. + // since we're going to take the power of 2 that comes closest, we can afford to be + // a little generous, rather than being too miserly. + // forward. template class internal_hash_array; @@ -279,11 +284,11 @@ hash_table::~hash_table() template int hash_table::calculate_num_slots(int estimated_elements) { -//printf("elems wanted = %d\n", estimated_elements); - int log_2_truncated = int(log(float(estimated_elements)) / log(2.0)); -//printf("log 2 of elems, truncated = %d\n", log_2_truncated); +//printf("[ elems wanted = %d, fudge adjusted = %d\n", estimated_elements, estimated_elements + HASH_FUDGE_FACTOR); + int log_2_truncated = int(log(float(estimated_elements + HASH_FUDGE_FACTOR)) / log(2.0)); +//printf(" log 2 of elems, truncated = %d\n", log_2_truncated); int slots_needed_for_elems = (int)pow(2.0, double(log_2_truncated + 1)); -//printf("slots needed = %d\n", slots_needed_for_elems ); +//printf(" slots needed = %d]\n", slots_needed_for_elems ); return slots_needed_for_elems; } diff --git a/nucleus/library/structures/int_hash.h b/nucleus/library/structures/int_hash.h index bd5f3494..d956a3d9 100644 --- a/nucleus/library/structures/int_hash.h +++ b/nucleus/library/structures/int_hash.h @@ -35,7 +35,7 @@ template class int_hash : public hash_table { public: - int_hash(int max_bits); + int_hash(int estimated_elements); ~int_hash(); const int_set &ids() const; @@ -69,8 +69,8 @@ private: // implementations below... template -int_hash::int_hash(int max_bits) -: hash_table(rotating_byte_hasher(), max_bits), +int_hash::int_hash(int estimated_elements) +: hash_table(rotating_byte_hasher(), estimated_elements), _ids(new int_set) {} diff --git a/octopi/library/sockets/sequence_tracker.cpp b/octopi/library/sockets/sequence_tracker.cpp index 15172195..a0a6438b 100644 --- a/octopi/library/sockets/sequence_tracker.cpp +++ b/octopi/library/sockets/sequence_tracker.cpp @@ -33,8 +33,8 @@ using namespace timely; namespace sockets { -const int MAX_BITS_FOR_SEQ_HASH = 10; - // the number of bits in the hash table of sequences, allowing 2^max buckets. +const int MAX_ELEMENTS_FOR_SEQ_HASH = 500; + // the estimated elements in the hash table of sequences, before we start needing buckets. const int CLEANING_SPAN = 20000; // if the sequence number is this far off from the one received, we will @@ -84,7 +84,7 @@ public: // we could piece this together from the sequences but we prefer not to. host_record(const machine_uid &host) - : _received_to(0), _host(host), _sequences(MAX_BITS_FOR_SEQ_HASH), + : _received_to(0), _host(host), _sequences(MAX_ELEMENTS_FOR_SEQ_HASH), _last_active() {} diff --git a/octopi/library/tests_octopus/makefile b/octopi/library/tests_octopus/makefile index 138da6de..2c1f0028 100644 --- a/octopi/library/tests_octopus/makefile +++ b/octopi/library/tests_octopus/makefile @@ -8,7 +8,6 @@ TARGETS = test_bin.exe test_bin_threaded.exe test_entity.exe test_identity.exe \ test_security.exe test_unpacker.exe test_file_transfer.exe LOCAL_LIBS_USED = tentacles octopus sockets unit_test application configuration loggers \ textual octopus timely configuration nodes processes filesystem structures basis -#VCPP_USE_SOCK = t RUN_TARGETS = $(ACTUAL_TARGETS) include cpp/rules.def diff --git a/production/feisty_meow_config.ini b/production/feisty_meow_config.ini index 1e63a567..21853785 100644 --- a/production/feisty_meow_config.ini +++ b/production/feisty_meow_config.ini @@ -84,6 +84,6 @@ endif # methods with a way to know how they were invoked. to get this information. # the invoker must be fitted with FUNCDEF macros. ifeq "$(BOOT_STRAPPING)" "" -# DEFINITIONS += ENABLE_CALLSTACK_TRACKING + DEFINITIONS += ENABLE_CALLSTACK_TRACKING endif diff --git a/scripts/clam/target_runner.sh b/scripts/clam/target_runner.sh index 2cd69bd0..399f3fa3 100644 --- a/scripts/clam/target_runner.sh +++ b/scripts/clam/target_runner.sh @@ -8,44 +8,54 @@ # of the GNU General Public License. [ http://www.gnu.org/licenses/gpl.html ] # Feel free to send updates to: [ fred@gruntose.com ] ############## -# + # Runs the programs listed in clam's RUN_TARGETS and reports any errors seen. -#echo entering target_runner with variables: -#echo -e "\tRUN_TARGETS=${RUN_TARGETS}" -#echo -e "\tDIRTY_FILE=${DIRTY_FILE}" -#echo -e "\tSUBMAKE_FLAG=${SUBMAKE_FLAG}" -#echo -e "\tFAILURE_FILE=${FAILURE_FILE}" -#echo -e "\tRUN_ALL_TESTS=${RUN_ALL_TESTS}" +function run_the_targets() +{ + +# echo entering target_runner with variables: +# echo -e "\tRUN_TARGETS=${RUN_TARGETS}" +# echo -e "\tDIRTY_FILE=${DIRTY_FILE}" +# echo -e "\tSUBMAKE_FLAG=${SUBMAKE_FLAG}" +# echo -e "\tFAILURE_FILE=${FAILURE_FILE}" +# echo -e "\tRUN_ALL_TESTS=${RUN_ALL_TESTS}" -if [ ! -z "${RUN_TARGETS}" -a ! -z "${RUN_ALL_TESTS}" ]; then - if [ -f "${DIRTY_FILE}" -o -f "${SUBMAKE_FLAG}" ]; then - total_exitval=0; - for program_name in ${RUN_TARGETS}; do - base=$(basename $program_name); - if [ "$OPERATING_SYSTEM" == "WIN32" ]; then - # extra step to force win32 applications to stay held in our grip, - # since they will float off and appear to have stopped when - # run by cygwin. but by grabbing the i/o stream, we know it's - # running until it's done. - "$program_name" 2>&1 | cat - # we care about the exit status of the first process in the pipe, - # which is the app being run. - exitval=${PIPESTATUS[0]} - else - "$program_name" - exitval=$?; + local total_exitval=0 + local exitval=0 + + if [ ! -z "${RUN_TARGETS}" -a ! -z "${RUN_ALL_TESTS}" ]; then + if [ -f "${DIRTY_FILE}" -o -f "${SUBMAKE_FLAG}" ]; then + for program_name in ${RUN_TARGETS}; do +echo "about to run target: $program_name" + local base=$(basename $program_name) + if [ "$OPERATING_SYSTEM" == "WIN32" ]; then + # extra step to force win32 applications to stay held in our grip, + # since they will float off and appear to have stopped when + # run by cygwin. but by grabbing the i/o stream, we know it's + # running until it's done. + "$program_name" 2>&1 | cat + # we care about the exit status of the first process in the pipe, + # which is the app being run. + exitval=${PIPESTATUS[0]} + else + "$program_name" + exitval=$? + fi + if [ $exitval -ne 0 ]; then + echo "ERROR: $program_name exits with $exitval at $(date)" + total_exitval=$(($total_exitval + 1)) + fi + done + if [ $total_exitval -ne 0 ]; then + echo "FAILURE: $total_exitval errors occurred in RUN_TARGETS." + echo yep >"${FAILURE_FILE}" + exit 1 fi - if [ $exitval -ne 0 ]; then - echo "ERROR: $program_name exits with $exitval at $(date)"; - total_exitval=$(($total_exitval + 1)); - fi; - done; - if [ $total_exitval -ne 0 ]; then - echo "FAILURE: $total_exitval errors occurred in RUN_TARGETS."; - echo yep >"${FAILURE_FILE}"; - exit 1; - fi; - fi; -fi + fi + fi +} + +# now the main "program" fires up. +run_the_targets