Merge branch 'release-2.140.101'
[feisty_meow.git] / nucleus / library / tests_basis / test_mutex.cpp
1 /*****************************************************************************\
2 *                                                                             *
3 *  Name   : test_mutex                                                        *
4 *  Author : Chris Koeritz                                                     *
5 *                                                                             *
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 \*****************************************************************************/
14
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>
30
31 #ifdef __WIN32__
32   #include <process.h>
33 #endif
34
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;
43
44 //#define DEBUG_MUTEX
45   // uncomment for a verbose test run.
46
47 const int MAX_MUTEX_TIMING_TEST = 2000000;
48   // the number of times we'll lock and unlock a mutex.
49
50 const int DEFAULT_FISH = 32;
51   // the number of threads, by default.
52
53 const int DEFAULT_RUN_TIME = 2 * SECOND_ms;
54   // the length of time to run the program.
55
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.
60
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.
64
65 int concurrent_biters = 0;
66   // the number of threads that are currently active.
67
68 int grab_lock = 0;
69   // this is upped whenever a fish obtains access to the mutex.
70
71 mutex &guard() { static mutex _muttini; return _muttini; }
72   // the guard ensures that the grab lock isn't molested by two fish at
73   // once... hopefully.
74
75 astring protected_string;
76   // this string is protected only by the mutex of guard().
77
78 #define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger::get(), to_print)
79   // our macro for logging with a timestamp.
80
81 // expects guardian mutex to already be locked once when coming in.
82 void test_recursive_locking(chaos &_rando)
83 {
84   int test_attempts = _rando.inclusive(MIN_SAME_THREAD_LOCKING_TESTS,
85       MAX_SAME_THREAD_LOCKING_TESTS);
86   int locked = 0;
87   for (int i = 0; i < test_attempts; i++) {
88     bool lock = !!(_rando.inclusive(0, 1));
89     if (lock) {
90       guard().lock();
91       locked++;  // one more lock.
92     } else {
93       if (locked > 0) {
94         // must be sure we are not already locally unlocked completely.
95         guard().unlock();
96         locked--;
97       }
98     }
99   }
100   for (int j = 0; j < locked; j++) {
101     // drop any locks we had left during the test.
102     guard().unlock();
103   }
104 }
105
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.
108
109 #undef UNIT_BASE_THIS_OBJECT
110 #define UNIT_BASE_THIS_OBJECT c_testing
111
112 class piranha : public ethread
113 {
114 public:
115   chaos _rando;  // our randomizer.
116   unit_base &c_testing;  // provides for test recording.
117
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));
123   }
124
125   virtual ~piranha() {
126     FUNCDEF("destructor");
127     safe_add(concurrent_biters, -1); 
128 //LOG("reached piranha destructor.");
129   }
130
131   DEFINE_CLASS_NAME("piranha");
132
133   void perform_activity(void *formal(data)) {
134     FUNCDEF("perform_activity");
135     {
136       // we grab the lock.
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'));
145
146       test_recursive_locking(_rando);
147
148       safe_add(grab_lock, -1);
149     }
150     // dropped the lock.  snooze a bit.
151     if (!should_stop())
152       time_control::sleep_ms(_rando.inclusive(THREAD_PAUSE_LOWEST, THREAD_PAUSE_HIGHEST));
153   }
154
155 };
156
157 class barracuda : public ethread
158 {
159 public:
160   chaos _rando;  // our randomizer.
161   unit_base &c_testing;  // provides for test recording.
162
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));
168   }
169
170   virtual ~barracuda() {
171     FUNCDEF("destructor");
172     safe_add(concurrent_biters, -1);
173 //LOG("reached barracuda destructor.");
174   }
175
176   DEFINE_CLASS_NAME("barracuda");
177
178   void perform_activity(void *formal(data)) {
179     FUNCDEF("perform_activity");
180     // we grab the lock.
181     guard().lock();
182     safe_add(grab_lock, 1);
183     ASSERT_TRUE(grab_lock <= 1, "grab lock should not already be active");
184
185     test_recursive_locking(_rando);
186
187     protected_string += char(_rando.inclusive('a', 'z'));
188     safe_add(grab_lock, -1);
189     guard().unlock();
190     // done with the lock.  sleep for a while.
191     if (!should_stop())
192       time_control::sleep_ms(_rando.inclusive(THREAD_PAUSE_LOWEST, THREAD_PAUSE_HIGHEST));
193   }
194 };
195
196 //////////////
197
198 #undef UNIT_BASE_THIS_OBJECT
199 #define UNIT_BASE_THIS_OBJECT (*this)
200
201 class test_mutex : virtual public unit_base, virtual public application_shell
202 {
203 public:
204   chaos _rando;  // our randomizer.
205
206   test_mutex() : application_shell() {}
207
208   DEFINE_CLASS_NAME("test_mutex");
209
210   int execute();
211 };
212
213 int test_mutex::execute()
214 {
215   FUNCDEF("execute");
216
217   // make sure the guard is initialized before the threads run.
218   guard().lock();
219   guard().unlock();
220
221   {
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.
225     mutex ted;
226     time_stamp mutt_in;
227     for (int qb = 0; qb < MAX_MUTEX_TIMING_TEST; qb++) {
228       ted.lock();
229       ted.unlock();
230     }
231     time_stamp mutt_out;
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");
239   }
240
241   amorph<ethread> thread_list;
242
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);
252   }
253
254   time_stamp when_to_leave(DEFAULT_RUN_TIME);
255   while (when_to_leave > time_stamp()) {
256     time_control::sleep_ms(100);
257   }
258
259 //hmmm: try just resetting the amorph;
260 //      that should work fine.
261
262 #ifdef DEBUG_MUTEX
263   LOG("now cancelling all threads....");
264 #endif
265
266   for (int j = 0; j < thread_list.elements(); j++) thread_list[j]->cancel();
267
268 #ifdef DEBUG_MUTEX
269   LOG("now stopping all threads....");
270 #endif
271
272   for (int k = 0; k < thread_list.elements(); k++) thread_list[k]->stop();
273
274   int threads_active = 0;
275   for (int k = 0; k < thread_list.elements(); k++) {
276     if (thread_list[k]->thread_active()) threads_active++;
277   }
278   ASSERT_EQUAL(threads_active, 0, "threads should actually have stopped by now");
279
280 #ifdef DEBUG_MUTEX
281   LOG("resetting thread list....");
282 #endif
283
284   thread_list.reset();  // should whack all threads.
285
286   ASSERT_EQUAL(concurrent_biters, 0, "threads should all be gone by now");
287
288 #ifdef DEBUG_MUTEX
289   LOG("done exiting from all threads....");
290
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(),
294       DEFAULT_FISH));
295 #endif
296
297   return final_report();
298 }
299
300 HOOPLE_MAIN(test_mutex, )
301