]> feistymeow.org Git - feisty_meow.git/commitdiff
build fully working with callstack tracker
authorFred T. Hamster <fred@feistymeow.org>
Wed, 18 Feb 2026 00:39:45 +0000 (19:39 -0500)
committerFred T. Hamster <fred@feistymeow.org>
Wed, 18 Feb 2026 00:39:45 +0000 (19:39 -0500)
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.

nucleus/library/application/callstack_tracker.cpp
nucleus/library/structures/hash_table.h
nucleus/library/structures/int_hash.h
octopi/library/sockets/sequence_tracker.cpp
octopi/library/tests_octopus/makefile
production/feisty_meow_config.ini
scripts/clam/target_runner.sh

index f0c9283cca05f92ea130955911dd0cbceb0e3695..9060226f31cc9f5546b7cf9bbf773702d7c19112 100644 (file)
@@ -26,6 +26,8 @@
 #include <malloc.h>
 #include <stdio.h>
 #include <string.h>
+#include <unistd.h>
+#include <unordered_map>
 
 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<pid_t, callstack_tracker *>
+{
+public:
+  tracker_hashing() : unordered_map<pid_t, callstack_tracker *>() {}
+};
+
+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
index 6423d41381f442b4b2d10ee1e5f94df71f35d629..d6685ac791f920eb6fdbf57c1541257013cc5349 100644 (file)
 
 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 key, class contents> class internal_hash_array;
 
@@ -279,11 +284,11 @@ hash_table<key_type, contents>::~hash_table()
 template <class key_type, class contents>
 int hash_table<key_type, contents>::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;
 }
 
index bd5f3494c87497279ef17619780a8ccf56f54594..d956a3d996c585aefa2a19486ff079cfb6f08e9d 100644 (file)
@@ -35,7 +35,7 @@ template <class contents>
 class int_hash : public hash_table<int, contents>
 {
 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 <class contents>
-int_hash<contents>::int_hash(int max_bits)
-: hash_table<int, contents>(rotating_byte_hasher(), max_bits),
+int_hash<contents>::int_hash(int estimated_elements)
+: hash_table<int, contents>(rotating_byte_hasher(), estimated_elements),
   _ids(new int_set)
 {}
 
index 15172195655ea536440611fb3ee927a612e80e11..a0a6438b65df195b7250e4572fda6bd351b8a04e 100644 (file)
@@ -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()
   {}
 
index 138da6de8e8a4b5daa61ae2f48301a82aae50861..2c1f0028377edccc1f59f0e3e76b1c447b1adc34 100644 (file)
@@ -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
index 1e63a567e621c74e3fc5143543f81ce0a44d5782..21853785536c9498508c85971d4f936243461a7d 100644 (file)
@@ -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
 
index 2cd69bd09cbc0c494674d1982c83deec1f7a39da..399f3fa3ec018f9ea462eda7e0d214dee239d19d 100644 (file)
@@ -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