breaking change with callstack tracker enabled
authorFred T. Hamster <fred@feistymeow.org>
Tue, 10 Feb 2026 04:25:36 +0000 (23:25 -0500)
committerFred T. Hamster <fred@feistymeow.org>
Tue, 10 Feb 2026 04:25:36 +0000 (23:25 -0500)
this doesn't compile right now, but is getting closer.
currently trying to resolve the problems in buildor_gen_deps, but may have just found the smoking gun.  or bug.  bad bug.

nucleus/library/application/callstack_tracker.cpp
nucleus/library/application/callstack_tracker.h
nucleus/library/structures/static_memory_gremlin.cpp
nucleus/library/tests_application/test_path_configuration.cpp
nucleus/library/tests_basis/test_mutex.cpp
nucleus/library/tests_basis/test_string.cpp
nucleus/tools/clam_tools/value_tagger.cpp
production/feisty_meow_config.ini
scripts/clam/cpp/buildor_gen_deps.sh

index 5f57dd36d2226e13378835be41a8f07baa993a1d..00e5a43254ac18d70d2c506bd40b86c4aaee5ead 100644 (file)
@@ -54,23 +54,42 @@ const char *emptiness_note = "Empty Stack\n";
 
 //////////////
 
+basis::mutex &callstack_tracker::__callstack_tracker_synchronizer()
+{
+  static basis::mutex __global_synch_callstacks;
+  return __global_synch_callstacks;
+}
+
+//////////////
+
 //! the single instance of callstack_tracker.
-/*! this is also an ultra low-level object, although it's not as far down
+/*!
+  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.  the object must be stored here rather than in the static basis
 library due to issues in windows dlls.
-NOTE: this is also not thread safe; it must be initialized before any threads
-have started. */
+  NOTE: the construction process for this is not thread-safe; the static
+program-wide object must be initialized before any threads have started.
+that is normally done in...
+uhhh....
+
+beuller?
+...
+
 
-//hmmm: why is this here?  because it needs to interact with the progwide memories?
+*/
 callstack_tracker &program_wide_stack_trace()
 {
+  auto_synchronizer l(callstack_tracker::__callstack_tracker_synchronizer());
+
   static callstack_tracker *_hidden_trace = NULL_POINTER;
   if (!_hidden_trace) {
 #ifdef ENABLE_MEMORY_HOOK
     program_wide_memories().disable();
-      // we don't want infinite loops tracking the call stack during this
-      // object's construction.
+      /* 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 ENABLE_MEMORY_HOOK
@@ -119,6 +138,8 @@ callstack_tracker::~callstack_tracker()
 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);
   if (_unusable) return false;
   if (_depth >= MAX_STACK_DEPTH) {
@@ -137,6 +158,8 @@ bool callstack_tracker::push_frame(const char *class_name, const char *func,
 
 bool callstack_tracker::pop_frame()
 {
+  auto_synchronizer l(callstack_tracker::__callstack_tracker_synchronizer());
+
 //printf("callstack popframe depth=%d in\n", _depth);
   if (_unusable) return false;
   if (_depth <= 0) {
@@ -154,6 +177,8 @@ bool callstack_tracker::pop_frame()
 
 bool callstack_tracker::update_line(int line)
 {
+  auto_synchronizer l(callstack_tracker::__callstack_tracker_synchronizer());
+
   if (_unusable) return false;
   if (!_depth) return false;  // not as serious, but pretty weird.
   _bt->_records[_depth]._line = line;
@@ -162,6 +187,8 @@ bool callstack_tracker::update_line(int line)
 
 char *callstack_tracker::full_trace() const
 {
+  auto_synchronizer l(callstack_tracker::__callstack_tracker_synchronizer());
+
   if (_unusable) return strdup("");
 //printf("fulltrace in\n");
   char *to_return = (char *)malloc(full_trace_size());
@@ -212,6 +239,8 @@ char *callstack_tracker::full_trace() const
 
 int callstack_tracker::full_trace_size() const
 {
+  auto_synchronizer l(callstack_tracker::__callstack_tracker_synchronizer());
+
   if (_unusable) return 0;
   if (!_depth) return strlen(emptiness_note) + 14;  // liberal allocation.
   int to_return = 28;  // another hollywood style excess.
index a8e83169b95b6530de68540fb1730ab27c3231f7..e6392e3f0decb3bbbffbf2d8a4ddbd8ccd32078a 100644 (file)
@@ -18,6 +18,7 @@
 #include <application/build_configuration.h>
 #include <basis/contracts.h>
 #include <basis/definitions.h>
+#include <basis/mutex.h>
 
 namespace application {
 
@@ -32,6 +33,8 @@ class callstack_tracker;
 callstack_tracker &program_wide_stack_trace();
   //!< a global object that can be used to track the runtime callstack.
 
+//hmmm: maybe borked on basis of the conflict between a global stack tracker and the fact that each thread has its own callstack!  argh!
+
 //////////////
 
 //! This object can provide a backtrace at runtime of the invoking methods.
@@ -87,6 +90,9 @@ public:
   double highest() const { return _highest; }
     //!< reports the maximum stack depth seen during the runtime so far.
 
+  static basis::mutex &__callstack_tracker_synchronizer();
+    //!< protects concurrent access.
+
 private:
   callstack_records *_bt;  //!< the backtrace records for current program.
   int _depth;  //!< the current number of frames we know of.
index fc95d75f24031205c682480592843fb65953e112..88ba32d447a770f71abb8bb774d4a707d06d1fc6 100644 (file)
@@ -142,8 +142,8 @@ bool static_memory_gremlin::__program_is_dying() { return __global_program_is_dy
 
 mutex &static_memory_gremlin::__memory_gremlin_synchronizer()
 {
-  static mutex __globabl_synch_mem;
-  return __globabl_synch_mem;
+  static mutex __global_synch_mem;
+  return __global_synch_mem;
 }
 
 int static_memory_gremlin::locate(const char *unique_name)
@@ -256,6 +256,7 @@ static_memory_gremlin &static_memory_gremlin::__hoople_globals()
     application::program_wide_stack_trace().full_trace_size();
       // invoke now to get callback tracking instantiated.
 #endif
+
     FUNCDEF("HOOPLE_GLOBALS remainder");
       // this definition must be postponed until after the objects that would
       // track it actually exist.
index a0ce8c9af2e8ebab61f9ec63abf5cefa14e87b11..0ab750b500b6f6f8c15fa123b14aa0c4da54e826 100644 (file)
@@ -34,7 +34,7 @@ using namespace loggers;
 #define LOG(s) CLASS_EMERGENCY_LOG(program_wide_logger::get(), s)
 
 //hmmm: ugly old main() without using the hoople machinery.  ack.
-astring static_class_name() { return "test_path_configuration"; }
+const char *static_class_name() { return "test_path_configuration"; }
 
 HOOPLE_STARTUP_CODE;
 
index 86175c4ed0b4213e4bb8bd4aa69744c11e930dd1..1783de12734fa7e7140436b04c79bbe5e7c04060 100644 (file)
@@ -41,7 +41,7 @@ using namespace processes;
 using namespace structures;
 using namespace unit_test;
 
-//#define DEBUG_MUTEX
+#define DEBUG_MUTEX
   // uncomment for a verbose test run.
 
 const int MAX_MUTEX_TIMING_TEST = 2000000;
@@ -214,6 +214,10 @@ int test_mutex::execute()
 {
   FUNCDEF("execute");
 
+#ifdef DEBUG_MUTEX
+  LOG("entering execute method.");
+#endif
+
   // make sure the guard is initialized before the threads run.
   guard().lock();
   guard().unlock();
@@ -236,21 +240,47 @@ int test_mutex::execute()
         run_count, full_run_time));
     log(a_sprintf("or %f ms per (lock+unlock).", time_per_lock));
     ASSERT_TRUE(time_per_lock < 1.0, "mutex lock timing should be super fast");
+#ifdef DEBUG_MUTEX
+    LOG("about to exit scope and dump automatic objects.");
+#endif
   }
 
+#ifdef DEBUG_MUTEX
+  LOG("succeeded in exiting scope.");
+#endif
+
   amorph<ethread> thread_list;
 
   for (int i = 0; i < DEFAULT_FISH; i++) {
     ethread *t = NULL_POINTER;
-    if (i % 2) t = new piranha(*this);
-    else t = new barracuda(*this);
+    if (i % 2) {
+      t = new piranha(*this);
+#ifdef DEBUG_MUTEX
+      LOG(a_sprintf("indy %i: adding new piranha now.", i));
+#endif
+    } else {
+      t = new barracuda(*this);
+#ifdef DEBUG_MUTEX
+      LOG(a_sprintf("indy %i: adding new piranha now.", i));
+#endif
+    }
     thread_list.append(t);
     ethread *q = thread_list[thread_list.elements() - 1];
     ASSERT_EQUAL(q, t, "amorph pointer equivalence is required");
+#ifdef DEBUG_MUTEX
+    LOG(a_sprintf("indy %i: about to start new thread.", i));
+#endif
     // start the thread we added.
     q->start(NULL_POINTER);
+#ifdef DEBUG_MUTEX
+    LOG(a_sprintf("indy %i: after new thread started.", i));
+#endif
   }
 
+#ifdef DEBUG_MUTEX
+  LOG("about to begin snoozing.");
+#endif
+
   time_stamp when_to_leave(DEFAULT_RUN_TIME);
   while (when_to_leave > time_stamp()) {
     time_control::sleep_ms(100);
@@ -260,13 +290,13 @@ int test_mutex::execute()
 //      that should work fine.
 
 #ifdef DEBUG_MUTEX
-  LOG("now cancelling all threads....");
+  LOG("now cancelling all threads.");
 #endif
 
   for (int j = 0; j < thread_list.elements(); j++) thread_list[j]->cancel();
 
 #ifdef DEBUG_MUTEX
-  LOG("now stopping all threads....");
+  LOG("now stopping all threads.");
 #endif
 
   for (int k = 0; k < thread_list.elements(); k++) thread_list[k]->stop();
@@ -278,7 +308,7 @@ int test_mutex::execute()
   ASSERT_EQUAL(threads_active, 0, "threads should actually have stopped by now");
 
 #ifdef DEBUG_MUTEX
-  LOG("resetting thread list....");
+  LOG("resetting thread list.");
 #endif
 
   thread_list.reset();  // should whack all threads.
@@ -286,7 +316,7 @@ int test_mutex::execute()
   ASSERT_EQUAL(concurrent_biters, 0, "threads should all be gone by now");
 
 #ifdef DEBUG_MUTEX
-  LOG("done exiting from all threads....");
+  LOG("done exiting from all threads.");
 
   LOG(astring(astring::SPRINTF, "the accumulated string had %d characters "
       "which means\nthere were %d thread activations from %d threads.",
index 0eddf073e406a7c48f94b37c52eb76b544e92046..922acc3ef06480506646ce3b83bde021eec1a9f2 100644 (file)
@@ -49,8 +49,6 @@ using namespace textual;
 using namespace timely;
 using namespace unit_test;
 
-//HOOPLE_STARTUP_CODE;
-
 //#define DEBUG_STRING_TEST
   // uncomment for testing version.
 
@@ -859,6 +857,8 @@ void test_string::run_test_29()
   ASSERT_EQUAL(b, a, "second comparison failed");
 }
 
+#define static_class_name() "test_string"
+
 void standard_sprintf_test(const char *parm_string)
 {
   FUNCDEF("standard_sprintf_test");
@@ -875,6 +875,8 @@ void standard_sprintf_test(const char *parm_string)
       parm_string, parm_string, basis::un_long(rando.inclusive(0, 2998238)));
 }
 
+#undef static_class_name
+
 void test_string::run_test_30()
 {
   // 30th test group checks astring sprintf.
@@ -1278,4 +1280,3 @@ int test_string::execute()
 
 HOOPLE_MAIN(test_string, )
 
-
index 90b6a0531a4187225f4cb67ab8947763d405de62..bd90b9106884040e04bf0f7178db6cc0d66108ba 100644 (file)
@@ -24,6 +24,7 @@
 * Please send any updates to: fred@gruntose.com                               *
 \*****************************************************************************/
 
+#include <algorithms/sorts.h>
 #include <application/application_shell.h>
 #include <application/command_line.h>
 #include <application/hoople_main.h>
@@ -46,7 +47,6 @@
 
 #include <sys/stat.h>
 
-#include "../../library/algorithms/sorts.h"
 #ifdef __WIN32__
   #include <io.h>
 #endif
index b4591ed401c5343cf1b43256cab3866b766d8d91..2760628736d80b71c2b19371d051afb2f5753f36 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 e865bc267df31e73fc2c1991dc4a9b83dcedfd58..8b463962ddcad5f8e72c82077619d066718d6602 100644 (file)
@@ -62,15 +62,15 @@ function add_new_dep {
   # make sure we haven't already processed this.
   local dep="$1"
   if seen_already "$dep"; then
-#echo bailing since seen: $dep
+echo bailing since seen: $dep
     return 1
   fi
-#echo had not seen before: $dep
+echo had not seen before: $dep
 
 #  if existing_dep $dep; then return 1; fi  # added it to list already.
 #  if bad_file $dep; then return 1; fi  # known to suck.
 #  if boring_file $dep; then return 1; fi  # we already saw it.
-##echo new dep: $dep
+echo new dep: $dep
 
   dependency_accumulator+=($dep)
   return 0
@@ -166,39 +166,39 @@ declare -a resolve_matches_dest=()
 # tries to find a filename in the library hierarchy.
 function resolve_filename {
   local code_file=$1
-#echo resolving: $code_file
+echo "resolving: $code_file"
   if [ -f "$code_file" ]; then
     # that was pretty easy.
     resolve_target_array=($code_file)
     return 0
   fi
-#echo "MUST seek: $code_file"
+echo "MUST seek: $code_file"
 
   local dir=$(dirname "$code_file")
   local base=$(basename "$code_file")
   local src_key="$dir/$base"
-#echo "src_key: $src_key"
+echo "src_key: $src_key"
 
   # see if we can find that element in the previously resolved items.
   if find_in_array "$src_key" ${resolve_matches_src[*]}; then
     local found_indy=$__finders_indy
     resolve_target_array=(${resolve_matches_dest[$found_indy]})
-#echo "FOUND \"$src_key\" AT ${resolve_matches_dest[$found_indy]}"
+echo "FOUND \"$src_key\" AT ${resolve_matches_dest[$found_indy]}"
     return 0
   fi
 
   # reset our global list.
   resolve_target_array=()
-#echo "HAVING TO FIND: $dir and $base"
+echo "HAVING TO FIND: $dir and $base"
   if [ -z "$dir" ]; then
     resolve_target_array=($(find "$BUILD_TOP" -iname "$base"))
   else
     resolve_target_array=($(find "$BUILD_TOP" -iname "$base" | grep "$dir.$base"))
   fi
-#echo resolved to: ${resolve_target_array[*]}
-#echo size of resolve array=${#resolve_target_array[*]}
+echo resolved to: ${resolve_target_array[*]}
+echo size of resolve array=${#resolve_target_array[*]}
   if [ ${#resolve_target_array[*]} -eq 1 ]; then
-#echo ADDING a match: $src_key ${resolve_target_array[0]}
+echo ADDING a match: $src_key ${resolve_target_array[0]}
     # for unique matches, we will store the correspondence so we can look
     # it up very quickly later.
     resolve_matches_src+=($src_key)
@@ -261,7 +261,7 @@ function recurse_on_deps {
 #hold
   #rm "$partial_file"
 
-#echo "grabbing includes from: $to_examine"
+echo "grabbing includes from: $to_examine"
 
 #hmmm: could separate the find deps on this file stuff below.
 
@@ -272,8 +272,9 @@ function recurse_on_deps {
   # we haven't already.
   while read -r line_found; do
     local chew_toy=$(echo $line_found | sed -e 's/^[ \t]*#include *<\(.*\)>.*$/\1/')
+    local original_value="$chew_toy"
     # we want to add the file to the active list before we forgot about it.
-#echo A: chew_toy=$chew_toy
+echo A: chew_toy=$chew_toy
 
     # check whether the dependency looks like one of our style of includes.
     # if it doesn't have a slash in it, then we need to give it the same
@@ -285,7 +286,7 @@ function recurse_on_deps {
     if [ ! -z "$(echo $chew_toy | sed -n -e 's/#include/crud/p')" ]; then
       # try again with a simpler pattern.
       chew_toy=$(echo $line_found | sed -e 's/^[ \t]*#include *[">]\(.*\)[">].*$/\1/') 
-#echo B: chew_toy=$chew_toy
+echo B: chew_toy=$chew_toy
 
       # if it still has an #include or if it's not really a file, we can't
       # use it for anything.
@@ -304,13 +305,13 @@ function recurse_on_deps {
         else
           # cool, we can rely on the existing directory.
           chew_toy="$fp_dir/$chew_toy"
-#echo patched dir: $chew_toy
+echo patched dir: $chew_toy
         fi
       fi
     fi
 
     if bad_file $chew_toy; then
-#echo C: skipping because on bad list: $chew_toy
+echo C: skipping because on bad list: $chew_toy
       continue
     fi
 
@@ -331,13 +332,13 @@ function recurse_on_deps {
 #echo odd len is $odd_len
       if [ $odd_len -eq 0 ]; then
         # whoops.  we couldn't find it.  probably a system header, so toss it.
-#echo "** ignoring: $chew_toy"
+echo "** ignoring: $chew_toy"
         bad_files+=($chew_toy)
         chew_toy=""
       elif [ $odd_len -eq 1 ]; then
         # there's exactly one match, which is very good.
         chew_toy="${found_odd[0]}"
-#echo C: chew_toy=$chew_toy
+echo "C: chew_toy=$chew_toy"
       else
         # this is really wrong.  there are multiple files with the same name?
         # that kind of things makes debugger tools angry or stupid.
@@ -365,12 +366,15 @@ function recurse_on_deps {
           active_deps+=($chew_toy)
         fi
       fi
+    else
+      echo "** chew_toy was empty!  original value was '$original_value'"
     fi
 
     # now compute the path as if it was the implementation file (x.cpp)
     # instead of being a header.  does that file exist?  if so, we'd like
     # its dependencies also.
-    local cpp_toy=$(echo $chew_toy | sed -e 's/^\([^\.]*\)\.h$/\1.cpp/')
+    local cpp_toy=$(echo -n $chew_toy | sed -e 's/^\([^\.]*\)\.h$/\1.cpp/')
+echo "cpp_toy is '$cpp_toy' as derived from chew_toy '$chew_toy'"
 
     # there's no point in adding it if the name didn't change.
     if [ "$cpp_toy" != "$chew_toy" ]; then
@@ -462,8 +466,8 @@ echo "skipping header file: $chewed_line"
     fi
 
     local new_include="  #include <$chewed_line>"
-    echo "$new_include" >>"$pending_deps"
 echo "adding '$new_include'"
+    echo "$new_include" >>"$pending_deps"
   done
 
   # check that our dependencies file is not empty still.