test_mutex.cpp

Go to the documentation of this file.
00001 /*****************************************************************************\
00002 *                                                                             *
00003 *  Name   : test_mutex                                                        *
00004 *  Author : Chris Koeritz                                                     *
00005 *                                                                             *
00006 *******************************************************************************
00007 * Copyright (c) 1994-$now By Author.  This program is free software; you can  *
00008 * redistribute it and/or modify it under the terms of the GNU General Public  *
00009 * License as published by the Free Software Foundation; either version 2 of   *
00010 * the License or (at your option) any later version.  This is online at:      *
00011 *     http://www.fsf.org/copyleft/gpl.html                                    *
00012 * Please send any updates to: fred@gruntose.com                               *
00013 \*****************************************************************************/
00014 
00015 #include <application/hoople_main.h>
00016 #include <basis/astring.h>
00017 #include <basis/guards.h>
00018 #include <basis/mutex.h>
00019 #include <configuration/application_configuration.h>
00020 #include <loggers/critical_events.h>
00021 #include <loggers/program_wide_logger.h>
00022 #include <mathematics/chaos.h>
00023 #include <processes/ethread.h>
00024 #include <processes/safe_roller.h>
00025 #include <structures/amorph.h>
00026 #include <structures/static_memory_gremlin.h>
00027 #include <timely/time_control.h>
00028 #include <timely/time_stamp.h>
00029 #include <unit_test/unit_base.h>
00030 
00031 #ifdef __WIN32__
00032   #include <process.h>
00033 #endif
00034 
00035 using namespace application;
00036 using namespace basis;
00037 using namespace loggers;
00038 using namespace mathematics;
00039 using namespace timely;
00040 using namespace processes;
00041 using namespace structures;
00042 using namespace unit_test;
00043 
00044 //#define DEBUG_MUTEX
00045   // uncomment for a verbose test run.
00046 
00047 const int MAX_MUTEX_TIMING_TEST = 2000000;
00048   // the number of times we'll lock and unlock a mutex.
00049 
00050 const int DEFAULT_FISH = 32;
00051   // the number of threads, by default.
00052 
00053 const int DEFAULT_RUN_TIME = 2 * SECOND_ms;
00054   // the length of time to run the program.
00055 
00056 const int THREAD_PAUSE_LOWEST = 0;
00057 const int THREAD_PAUSE_HIGHEST = 48;
00058   // this is the range of random sleeps that a thread will take after
00059   // performing it's actions.
00060 
00061 const int MIN_SAME_THREAD_LOCKING_TESTS = 100;
00062 const int MAX_SAME_THREAD_LOCKING_TESTS = 1000;
00063   // the range of times we'll test recursively locking the mutex.
00064 
00065 int concurrent_biters = 0;
00066   // the number of threads that are currently active.
00067 
00068 int grab_lock = 0;
00069   // this is upped whenever a fish obtains access to the mutex.
00070 
00071 mutex &guard() { static mutex _muttini; return _muttini; }
00072   // the guard ensures that the grab lock isn't molested by two fish at
00073   // once... hopefully.
00074 
00075 astring protected_string;
00076   // this string is protected only by the mutex of guard().
00077 
00078 #define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger::get(), to_print)
00079   // our macro for logging with a timestamp.
00080 
00081 // expects guardian mutex to already be locked once when coming in.
00082 void test_recursive_locking(chaos &_rando)
00083 {
00084   int test_attempts = _rando.inclusive(MIN_SAME_THREAD_LOCKING_TESTS,
00085       MAX_SAME_THREAD_LOCKING_TESTS);
00086   int locked = 0;
00087   for (int i = 0; i < test_attempts; i++) {
00088     bool lock = !!(_rando.inclusive(0, 1));
00089     if (lock) {
00090       guard().lock();
00091       locked++;  // one more lock.
00092     } else {
00093       if (locked > 0) {
00094         // must be sure we are not already locally unlocked completely.
00095         guard().unlock();
00096         locked--;
00097       }
00098     }
00099   }
00100   for (int j = 0; j < locked; j++) {
00101     // drop any locks we had left during the test.
00102     guard().unlock();
00103   }
00104 }
00105 
00106 //hmmm: how are these threads different so far?  they seem to do exactly
00107 //      the same thing.  maybe one should eat chars from the string.
00108 
00109 #undef UNIT_BASE_THIS_OBJECT
00110 #define UNIT_BASE_THIS_OBJECT c_testing
00111 
00112 class piranha : public ethread
00113 {
00114 public:
00115   chaos _rando;  // our randomizer.
00116   unit_base &c_testing;  // provides for test recording.
00117 
00118   piranha(unit_base &testing) : ethread(0), c_testing(testing) {
00119     FUNCDEF("constructor");
00120     safe_add(concurrent_biters, 1);
00121     ASSERT_TRUE(concurrent_biters >= 1, "the piranha is very noticeable");
00122 //LOG(astring(astring::SPRINTF, "there are %d biters.", concurrent_biters));
00123   }
00124 
00125   virtual ~piranha() {
00126     FUNCDEF("destructor");
00127     safe_add(concurrent_biters, -1); 
00128 //LOG("reached piranha destructor.");
00129   }
00130 
00131   DEFINE_CLASS_NAME("piranha");
00132 
00133   void perform_activity(void *formal(data)) {
00134     FUNCDEF("perform_activity");
00135     {
00136       // we grab the lock.
00137       auto_synchronizer locked(guard());
00138         // in this case, we make use of auto-synchronizer, handily testing it as well.
00139       ASSERT_TRUE(&locked != NIL, "auto_synchronizer should grab the mutex object's lock");
00140         // this is not a real test, but indicates that we did actually increase the number of
00141         // unit tests by one, since we're using auto_synchronizer now.
00142       safe_add(grab_lock, 1);
00143       ASSERT_TRUE(grab_lock <= 1, "grab lock should not already be active");
00144       protected_string += char(_rando.inclusive('a', 'z'));
00145 
00146       test_recursive_locking(_rando);
00147 
00148       safe_add(grab_lock, -1);
00149     }
00150     // dropped the lock.  snooze a bit.
00151     if (!should_stop())
00152       time_control::sleep_ms(_rando.inclusive(THREAD_PAUSE_LOWEST, THREAD_PAUSE_HIGHEST));
00153   }
00154 
00155 };
00156 
00157 class barracuda : public ethread
00158 {
00159 public:
00160   chaos _rando;  // our randomizer.
00161   unit_base &c_testing;  // provides for test recording.
00162 
00163   barracuda(unit_base &testing) : ethread(0), c_testing(testing) {
00164     FUNCDEF("constructor");
00165     safe_add(concurrent_biters, 1);
00166     ASSERT_TRUE(concurrent_biters >= 1, "our presence should have been noticed");
00167 //LOG(astring(astring::SPRINTF, "there are %d biters.", concurrent_biters));
00168   }
00169 
00170   virtual ~barracuda() {
00171     FUNCDEF("destructor");
00172     safe_add(concurrent_biters, -1);
00173 //LOG("reached barracuda destructor.");
00174   }
00175 
00176   DEFINE_CLASS_NAME("barracuda");
00177 
00178   void perform_activity(void *formal(data)) {
00179     FUNCDEF("perform_activity");
00180     // we grab the lock.
00181     guard().lock();
00182     safe_add(grab_lock, 1);
00183     ASSERT_TRUE(grab_lock <= 1, "grab lock should not already be active");
00184 
00185     test_recursive_locking(_rando);
00186 
00187     protected_string += char(_rando.inclusive('a', 'z'));
00188     safe_add(grab_lock, -1);
00189     guard().unlock();
00190     // done with the lock.  sleep for a while.
00191     if (!should_stop())
00192       time_control::sleep_ms(_rando.inclusive(THREAD_PAUSE_LOWEST, THREAD_PAUSE_HIGHEST));
00193   }
00194 };
00195 
00197 
00198 #undef UNIT_BASE_THIS_OBJECT
00199 #define UNIT_BASE_THIS_OBJECT (*this)
00200 
00201 class test_mutex : virtual public unit_base, virtual public application_shell
00202 {
00203 public:
00204   chaos _rando;  // our randomizer.
00205 
00206   test_mutex() : application_shell() {}
00207 
00208   DEFINE_CLASS_NAME("test_mutex");
00209 
00210   int execute();
00211 };
00212 
00213 int test_mutex::execute()
00214 {
00215   FUNCDEF("execute");
00216 
00217   {
00218     // we check how long a lock and unlock of a non-locked mutex will take.
00219     // this is important to know so that we aren't spending much of our time
00220     // locking mutexes just due to the mechanism.
00221     mutex ted;
00222     time_stamp mutt_in;
00223     for (int qb = 0; qb < MAX_MUTEX_TIMING_TEST; qb++) {
00224       ted.lock();
00225       ted.unlock();
00226     }
00227     time_stamp mutt_out;
00228 #ifdef DEBUG_MUTEX
00229     log(a_sprintf("%d mutex lock & unlock pairs took %f seconds,",
00230         MAX_MUTEX_TIMING_TEST,
00231         (mutt_out.value() - mutt_in.value()) / SECOND_ms));
00232     log(a_sprintf("or %f ms per (lock+unlock).",
00233         (mutt_out.value() - mutt_in.value()) / MAX_MUTEX_TIMING_TEST));
00234 #endif
00235   }
00236 
00237   // make sure the guard is initialized before the threads run.
00238   guard().lock();
00239   guard().unlock();
00240 
00241   amorph<ethread> thread_list;
00242 
00243   for (int i = 0; i < DEFAULT_FISH; i++) {
00244     ethread *t = NIL;
00245     if (i % 2) t = new piranha(*this);
00246     else t = new barracuda(*this);
00247     thread_list.append(t);
00248     ethread *q = thread_list[thread_list.elements() - 1];
00249     ASSERT_EQUAL(q, t, "amorph pointer equivalence is required");
00250     // start the thread we added.
00251     thread_list[thread_list.elements() - 1]->start(NIL);
00252   }
00253 
00254   time_stamp when_to_leave(DEFAULT_RUN_TIME);
00255   while (when_to_leave > time_stamp()) {
00256     time_control::sleep_ms(100);
00257   }
00258 
00259 //hmmm: try just resetting the amorph;
00260 //      that should work fine.
00261 
00262 #ifdef DEBUG_MUTEX
00263   LOG("now cancelling all threads....");
00264 #endif
00265 
00266   for (int j = 0; j < thread_list.elements(); j++) thread_list[j]->cancel();
00267 
00268 #ifdef DEBUG_MUTEX
00269   LOG("now stopping all threads....");
00270 #endif
00271 
00272   for (int k = 0; k < thread_list.elements(); k++) thread_list[k]->stop();
00273 
00274   int threads_active = 0;
00275   for (int k = 0; k < thread_list.elements(); k++) {
00276     if (thread_list[k]->thread_active()) threads_active++;
00277   }
00278   ASSERT_EQUAL(threads_active, 0, "threads should actually have stopped by now");
00279 
00280 #ifdef DEBUG_MUTEX
00281   LOG("resetting thread list....");
00282 #endif
00283 
00284   thread_list.reset();  // should whack all threads.
00285 
00286   ASSERT_EQUAL(concurrent_biters, 0, "threads should all be gone by now");
00287 
00288 #ifdef DEBUG_MUTEX
00289   LOG("done exiting from all threads....");
00290 
00291   LOG(astring(astring::SPRINTF, "the accumulated string had %d characters "
00292       "which means\nthere were %d thread activations from %d threads.",
00293       protected_string.length(), protected_string.length(),
00294       DEFAULT_FISH));
00295 #endif
00296 
00297   return final_report();
00298 }
00299 
00300 HOOPLE_MAIN(test_mutex, )
00301 
Generated on Sat Jan 28 04:22:29 2012 for hoople2 project by  doxygen 1.6.3