1 /*****************************************************************************\
4 * Author : Chris Koeritz *
6 *******************************************************************************
7 * Copyright (c) 1994-$now By Author. This program is free software; you can *
8 * redistribute it and/or modify it under the terms of the GNU General Public *
9 * License as published by the Free Software Foundation; either version 2 of *
10 * the License or (at your option) any later version. This is online at: *
11 * http://www.fsf.org/copyleft/gpl.html *
12 * Please send any updates to: fred@gruntose.com *
13 \*****************************************************************************/
15 #include <application/hoople_main.h>
16 #include <basis/astring.h>
17 #include <basis/guards.h>
18 #include <basis/mutex.h>
19 #include <configuration/application_configuration.h>
20 #include <loggers/critical_events.h>
21 #include <loggers/program_wide_logger.h>
22 #include <mathematics/chaos.h>
23 #include <processes/ethread.h>
24 #include <processes/safe_roller.h>
25 #include <structures/amorph.h>
26 #include <structures/static_memory_gremlin.h>
27 #include <timely/time_control.h>
28 #include <timely/time_stamp.h>
29 #include <unit_test/unit_base.h>
35 using namespace application;
36 using namespace basis;
37 using namespace loggers;
38 using namespace mathematics;
39 using namespace timely;
40 using namespace processes;
41 using namespace structures;
42 using namespace unit_test;
45 // uncomment for a verbose test run.
47 const int MAX_MUTEX_TIMING_TEST = 2000000;
48 // the number of times we'll lock and unlock a mutex.
50 const int DEFAULT_FISH = 32;
51 // the number of threads, by default.
53 const int DEFAULT_RUN_TIME = 2 * SECOND_ms;
54 // the length of time to run the program.
56 const int THREAD_PAUSE_LOWEST = 0;
57 const int THREAD_PAUSE_HIGHEST = 48;
58 // this is the range of random sleeps that a thread will take after
59 // performing it's actions.
61 const int MIN_SAME_THREAD_LOCKING_TESTS = 100;
62 const int MAX_SAME_THREAD_LOCKING_TESTS = 1000;
63 // the range of times we'll test recursively locking the mutex.
65 int concurrent_biters = 0;
66 // the number of threads that are currently active.
69 // this is upped whenever a fish obtains access to the mutex.
71 mutex &guard() { static mutex _muttini; return _muttini; }
72 // the guard ensures that the grab lock isn't molested by two fish at
75 astring protected_string;
76 // this string is protected only by the mutex of guard().
78 #define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger::get(), to_print)
79 // our macro for logging with a timestamp.
81 // expects guardian mutex to already be locked once when coming in.
82 void test_recursive_locking(chaos &_rando)
84 int test_attempts = _rando.inclusive(MIN_SAME_THREAD_LOCKING_TESTS,
85 MAX_SAME_THREAD_LOCKING_TESTS);
87 for (int i = 0; i < test_attempts; i++) {
88 bool lock = !!(_rando.inclusive(0, 1));
91 locked++; // one more lock.
94 // must be sure we are not already locally unlocked completely.
100 for (int j = 0; j < locked; j++) {
101 // drop any locks we had left during the test.
106 //hmmm: how are these threads different so far? they seem to do exactly
107 // the same thing. maybe one should eat chars from the string.
109 #undef UNIT_BASE_THIS_OBJECT
110 #define UNIT_BASE_THIS_OBJECT c_testing
112 class piranha : public ethread
115 chaos _rando; // our randomizer.
116 unit_base &c_testing; // provides for test recording.
118 piranha(unit_base &testing) : ethread(0), c_testing(testing) {
119 FUNCDEF("constructor");
120 safe_add(concurrent_biters, 1);
121 ASSERT_TRUE(concurrent_biters >= 1, "the piranha is very noticeable");
122 //LOG(astring(astring::SPRINTF, "there are %d biters.", concurrent_biters));
126 FUNCDEF("destructor");
127 safe_add(concurrent_biters, -1);
128 //LOG("reached piranha destructor.");
131 DEFINE_CLASS_NAME("piranha");
133 void perform_activity(void *formal(data)) {
134 FUNCDEF("perform_activity");
137 auto_synchronizer locked(guard());
138 // in this case, we make use of auto-synchronizer, handily testing it as well.
139 ASSERT_TRUE(&locked != NULL_POINTER, "auto_synchronizer should grab the mutex object's lock");
140 // this is not a real test, but indicates that we did actually increase the number of
141 // unit tests by one, since we're using auto_synchronizer now.
142 safe_add(grab_lock, 1);
143 ASSERT_TRUE(grab_lock <= 1, "grab lock should not already be active");
144 protected_string += char(_rando.inclusive('a', 'z'));
146 test_recursive_locking(_rando);
148 safe_add(grab_lock, -1);
150 // dropped the lock. snooze a bit.
152 time_control::sleep_ms(_rando.inclusive(THREAD_PAUSE_LOWEST, THREAD_PAUSE_HIGHEST));
157 class barracuda : public ethread
160 chaos _rando; // our randomizer.
161 unit_base &c_testing; // provides for test recording.
163 barracuda(unit_base &testing) : ethread(0), c_testing(testing) {
164 FUNCDEF("constructor");
165 safe_add(concurrent_biters, 1);
166 ASSERT_TRUE(concurrent_biters >= 1, "our presence should have been noticed");
167 //LOG(astring(astring::SPRINTF, "there are %d biters.", concurrent_biters));
170 virtual ~barracuda() {
171 FUNCDEF("destructor");
172 safe_add(concurrent_biters, -1);
173 //LOG("reached barracuda destructor.");
176 DEFINE_CLASS_NAME("barracuda");
178 void perform_activity(void *formal(data)) {
179 FUNCDEF("perform_activity");
182 safe_add(grab_lock, 1);
183 ASSERT_TRUE(grab_lock <= 1, "grab lock should not already be active");
185 test_recursive_locking(_rando);
187 protected_string += char(_rando.inclusive('a', 'z'));
188 safe_add(grab_lock, -1);
190 // done with the lock. sleep for a while.
192 time_control::sleep_ms(_rando.inclusive(THREAD_PAUSE_LOWEST, THREAD_PAUSE_HIGHEST));
198 #undef UNIT_BASE_THIS_OBJECT
199 #define UNIT_BASE_THIS_OBJECT (*this)
201 class test_mutex : virtual public unit_base, virtual public application_shell
204 chaos _rando; // our randomizer.
206 test_mutex() : application_shell() {}
208 DEFINE_CLASS_NAME("test_mutex");
213 int test_mutex::execute()
217 // make sure the guard is initialized before the threads run.
222 // we check how long a lock and unlock of a non-locked mutex will take.
223 // this is important to know so that we aren't spending much of our time
224 // locking mutexes just due to the mechanism.
227 for (int qb = 0; qb < MAX_MUTEX_TIMING_TEST; qb++) {
232 double run_count = MAX_MUTEX_TIMING_TEST;
233 double full_run_time = (mutt_out.value() - mutt_in.value()) / SECOND_ms;
234 double time_per_lock = (mutt_out.value() - mutt_in.value()) / run_count;
235 log(a_sprintf("%.0f mutex lock & unlock pairs took %.3f seconds,",
236 run_count, full_run_time));
237 log(a_sprintf("or %f ms per (lock+unlock).", time_per_lock));
238 ASSERT_TRUE(time_per_lock < 1.0, "mutex lock timing should be super fast");
241 amorph<ethread> thread_list;
243 for (int i = 0; i < DEFAULT_FISH; i++) {
244 ethread *t = NULL_POINTER;
245 if (i % 2) t = new piranha(*this);
246 else t = new barracuda(*this);
247 thread_list.append(t);
248 ethread *q = thread_list[thread_list.elements() - 1];
249 ASSERT_EQUAL(q, t, "amorph pointer equivalence is required");
250 // start the thread we added.
251 q->start(NULL_POINTER);
254 time_stamp when_to_leave(DEFAULT_RUN_TIME);
255 while (when_to_leave > time_stamp()) {
256 time_control::sleep_ms(100);
259 //hmmm: try just resetting the amorph;
260 // that should work fine.
263 LOG("now cancelling all threads....");
266 for (int j = 0; j < thread_list.elements(); j++) thread_list[j]->cancel();
269 LOG("now stopping all threads....");
272 for (int k = 0; k < thread_list.elements(); k++) thread_list[k]->stop();
274 int threads_active = 0;
275 for (int k = 0; k < thread_list.elements(); k++) {
276 if (thread_list[k]->thread_active()) threads_active++;
278 ASSERT_EQUAL(threads_active, 0, "threads should actually have stopped by now");
281 LOG("resetting thread list....");
284 thread_list.reset(); // should whack all threads.
286 ASSERT_EQUAL(concurrent_biters, 0, "threads should all be gone by now");
289 LOG("done exiting from all threads....");
291 LOG(astring(astring::SPRINTF, "the accumulated string had %d characters "
292 "which means\nthere were %d thread activations from %d threads.",
293 protected_string.length(), protected_string.length(),
297 return final_report();
300 HOOPLE_MAIN(test_mutex, )