feisty meow concerns codebase 2.140
test_mutex.cpp
Go to the documentation of this file.
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
17#include <basis/astring.h>
18#include <basis/guards.h>
19#include <basis/mutex.h>
23#include <mathematics/chaos.h>
24#include <processes/ethread.h>
26#include <structures/amorph.h>
28#include <timely/time_control.h>
29#include <timely/time_stamp.h>
30#include <unit_test/unit_base.h>
31
32#ifdef __WIN32__
33 #include <process.h>
34#endif
35
36using namespace application;
37using namespace basis;
38using namespace loggers;
39using namespace mathematics;
40using namespace timely;
41using namespace processes;
42using namespace structures;
43using namespace unit_test;
44
45class test_mutex; // forward.
46
47//#define DEBUG_MUTEX
48 // uncomment for a verbose test run.
49
50const int MAX_MUTEX_TIMING_TEST = 2000000;
51 // the number of times we'll lock and unlock a mutex.
52
53const int DEFAULT_FISH = 32;
54 // the number of threads, by default.
55
57 // the length of time to run the program.
58
59const int THREAD_PAUSE_LOWEST = 0;
60const int THREAD_PAUSE_HIGHEST = 48;
61 // this is the range of random sleeps that a thread will take after
62 // performing it's actions.
63
66 // the range of times we'll test recursively locking the mutex.
67
69 // the number of threads that are currently active.
70
71int grab_lock = 0;
72 // this is upped whenever a fish obtains access to the mutex.
73
74mutex &guard() { static mutex _muttini; return _muttini; }
75 // the guard ensures that the grab lock isn't molested by two fish at
76 // once... hopefully.
77
79 // this string is protected only by the mutex of guard().
80
81#define LOG(to_print) CLASS_EMERGENCY_LOG(program_wide_logger::get(), to_print)
82 // our macro for logging with a timestamp.
83
85
86#undef UNIT_BASE_THIS_OBJECT
87#define UNIT_BASE_THIS_OBJECT (*this)
88
89class test_mutex : virtual public unit_base, virtual public application_shell
90{
91public:
92 chaos _rando; // our randomizer.
93
94 test_mutex() : application_shell() {}
95
96 DEFINE_CLASS_NAME("test_mutex");
97
98 int execute();
99
100 void test_recursive_locking(chaos &_rando);
101 // invoked by the threads to do a little testing of locks.
102};
103
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
112class piranha : public ethread
113{
114public:
115 chaos _rando; // our randomizer.
116 test_mutex &c_testing; // provides for test recording.
117
118 piranha(test_mutex &testing) : ethread(0), c_testing(testing) {
119 FUNCDEF("constructor");
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");
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.
143 ASSERT_TRUE(grab_lock <= 1, "grab lock should not already be active");
144 protected_string += char(_rando.inclusive('a', 'z'));
145
146 c_testing.test_recursive_locking(_rando);
147
148 safe_add(grab_lock, -1);
149 }
150
151 #ifdef ENABLE_CALLSTACK_TRACKING
152 GET_AND_TEST_STACK_TRACE(class_name() + "::" + func + " callstack:", );
153 #endif
154
155 // dropped the lock. snooze a bit.
156 if (!should_stop())
158 }
159
160};
161
163
164class barracuda : public ethread
165{
166public:
167 chaos _rando; // our randomizer.
168 test_mutex &c_testing; // provides for test recording.
169
170 barracuda(test_mutex &testing) : ethread(0), c_testing(testing) {
171 FUNCDEF("constructor");
173 ASSERT_TRUE(concurrent_biters >= 1, "our presence should have been noticed");
174//LOG(astring(astring::SPRINTF, "there are %d biters.", concurrent_biters));
175 }
176
177 virtual ~barracuda() {
178 FUNCDEF("destructor");
180//LOG("reached barracuda destructor.");
181 }
182
183 DEFINE_CLASS_NAME("barracuda");
184
185 void perform_activity(void *formal(data)) {
186 FUNCDEF("perform_activity");
187 // we grab the lock.
188 guard().lock();
190 ASSERT_TRUE(grab_lock <= 1, "grab lock should not already be active");
191
192 c_testing.test_recursive_locking(_rando);
193
194 protected_string += char(_rando.inclusive('a', 'z'));
195 safe_add(grab_lock, -1);
196 guard().unlock();
197
198 #ifdef ENABLE_CALLSTACK_TRACKING
199 GET_AND_TEST_STACK_TRACE(class_name() + "::" + func + " callstack:", );
200 #endif
201
202 // done with the lock. sleep for a while.
203 if (!should_stop())
205 }
206};
207
209
210#undef UNIT_BASE_THIS_OBJECT
211#define UNIT_BASE_THIS_OBJECT (*this)
212
213int test_mutex::execute()
214{
215 FUNCDEF("execute");
216
217#ifdef DEBUG_MUTEX
218 LOG("entering execute method.");
219#endif
220
221 // make sure the guard is initialized before the threads run.
222 guard().lock();
223 guard().unlock();
224
225 {
226 // we check how long a lock and unlock of a non-locked mutex will take.
227 // this is important to know so that we aren't spending much of our time
228 // locking mutexes just due to the mechanism.
229 mutex ted;
230 time_stamp mutt_in;
231 for (int qb = 0; qb < MAX_MUTEX_TIMING_TEST; qb++) {
232 ted.lock();
233 ted.unlock();
234 }
235 time_stamp mutt_out;
236 double run_count = MAX_MUTEX_TIMING_TEST;
237 double full_run_time = (mutt_out.value() - mutt_in.value()) / SECOND_ms;
238 double time_per_lock = (mutt_out.value() - mutt_in.value()) / run_count;
239 log(a_sprintf("%.0f mutex lock & unlock pairs took %.3f seconds,",
240 run_count, full_run_time));
241 log(a_sprintf("or %f ms per (lock+unlock).", time_per_lock));
242 ASSERT_TRUE(time_per_lock < 1.0, "mutex lock timing should be super fast");
243#ifdef DEBUG_MUTEX
244 LOG("about to exit scope and dump automatic objects.");
245#endif
246
247 #ifdef ENABLE_CALLSTACK_TRACKING
248 GET_AND_TEST_STACK_TRACE(class_name() + "::" + func + " mutex lock timing callstack:", 1);
249 #endif
250 }
251
252#ifdef DEBUG_MUTEX
253 LOG("succeeded in exiting scope.");
254#endif
255
256 amorph<ethread> thread_list;
257
258 for (int i = 0; i < DEFAULT_FISH; i++) {
260 if (i % 2) {
261 t = new piranha(*this);
262#ifdef DEBUG_MUTEX
263 LOG(a_sprintf("indy %i: adding new piranha now.", i));
264#endif
265 } else {
266 t = new barracuda(*this);
267#ifdef DEBUG_MUTEX
268 LOG(a_sprintf("indy %i: adding new piranha now.", i));
269#endif
270 }
271
272 #ifdef ENABLE_CALLSTACK_TRACKING
273 GET_AND_TEST_STACK_TRACE(class_name() + "::" + func + a_sprintf(" starting fish indy #%d", i+1) + " callstack:", 1);
274 #endif
275
276 thread_list.append(t);
277 ethread *q = thread_list[thread_list.elements() - 1];
278 ASSERT_EQUAL(q, t, "amorph pointer equivalence is required");
279#ifdef DEBUG_MUTEX
280 LOG(a_sprintf("indy %i: about to start new thread.", i));
281#endif
282 // start the thread we added.
284#ifdef DEBUG_MUTEX
285 LOG(a_sprintf("indy %i: after new thread started.", i));
286#endif
287 }
288
289#ifdef DEBUG_MUTEX
290 LOG("about to begin snoozing.");
291#endif
292
293 time_stamp when_to_leave(DEFAULT_RUN_TIME);
294 while (when_to_leave > time_stamp()) {
296 }
297
298//hmmm: try just resetting the amorph;
299// that should work fine.
300
301#ifdef DEBUG_MUTEX
302 LOG("now cancelling all threads.");
303#endif
304
305 for (int j = 0; j < thread_list.elements(); j++) thread_list[j]->cancel();
306
307#ifdef DEBUG_MUTEX
308 LOG("now stopping all threads.");
309#endif
310
311 for (int k = 0; k < thread_list.elements(); k++) thread_list[k]->stop();
312
313 int threads_active = 0;
314 for (int k = 0; k < thread_list.elements(); k++) {
315 if (thread_list[k]->thread_active()) threads_active++;
316 }
317 ASSERT_EQUAL(threads_active, 0, "threads should actually have stopped by now");
318
319#ifdef DEBUG_MUTEX
320 LOG("resetting thread list.");
321#endif
322
323 thread_list.reset(); // should whack all threads.
324
325 ASSERT_EQUAL(concurrent_biters, 0, "threads should all be gone by now");
326
327#ifdef DEBUG_MUTEX
328 LOG("done exiting from all threads.");
329
330 #ifdef ENABLE_CALLSTACK_TRACKING
331 GET_AND_TEST_STACK_TRACE(class_name() + "::" + func + " after all threads have exited callstack:", 1);
332 #endif
333
334 LOG(astring(astring::SPRINTF, "the accumulated string had %d characters "
335 "which means\nthere were %d thread activations from %d threads.",
337 DEFAULT_FISH));
338#endif
339
340 return final_report();
341}
342
344
345// expects guardian mutex to already be locked once when coming in.
346void test_mutex::test_recursive_locking(chaos &_rando)
347{
348 FUNCDEF("test_recursive_locking");
349 int test_attempts = _rando.inclusive(MIN_SAME_THREAD_LOCKING_TESTS,
351 int locked = 0;
352 #ifdef ENABLE_CALLSTACK_TRACKING
353 GET_AND_TEST_STACK_TRACE(class_name() + "::" + func + " callstack:", );
354 #endif
355 for (int i = 0; i < test_attempts; i++) {
356 bool lock = !!(_rando.inclusive(0, 1));
357 if (lock) {
358 guard().lock();
359 locked++; // one more lock.
360 } else {
361 if (locked > 0) {
362 // must be sure we are not already locally unlocked completely.
363 guard().unlock();
364 locked--;
365 }
366 }
367 }
368 for (int j = 0; j < locked; j++) {
369 // drop any locks we had left during the test.
370 guard().unlock();
371 }
372}
373
375
376HOOPLE_MAIN(test_mutex, )
377
#define GET_AND_TEST_STACK_TRACE(header, failure_return)
The application_shell is a base object for console programs.
virtual int execute()=0
< retrieves the command line from the /proc hierarchy on linux.
application_shell()
constructs an application_shell to serve as the root of the program.
a_sprintf is a specialization of astring that provides printf style support.
Definition astring.h:440
Provides a dynamically resizable ASCII character string.
Definition astring.h:35
int length() const
Returns the current length of the string.
Definition astring.cpp:132
auto_synchronizer simplifies concurrent code by automatically unlocking.
Definition mutex.h:113
void lock()
Clamps down on the mutex, if possible.
Definition mutex.cpp:101
void unlock()
Gives up the possession of the mutex.
Definition mutex.cpp:113
a platform-independent way to acquire random numbers in a specific range.
Definition chaos.h:51
int inclusive(int low, int high) const
< Returns a pseudo-random number r, such that "low" <= r <= "high".
Definition chaos.h:88
Provides a platform-independent object for adding threads to a program.
Definition ethread.h:36
bool start(void *thread_data)
causes the thread to start, if it has not already been started.
Definition ethread.cpp:145
virtual void perform_activity(void *thread_data)=0
< invoked just after after start(), when the OS thread is created.
bool should_stop() const
reports whether the thread should stop right now.
Definition ethread.h:136
int elements() const
the maximum number of elements currently allowed in this amorph.
Definition amorph.h:66
basis::outcome append(const contents *data)
puts "data" on the end of this amorph.
Definition amorph.h:303
void reset()
cleans out all of the contents.
Definition amorph.h:81
static void sleep_ms(basis::un_int msec)
a system independent name for a forced snooze measured in milliseconds.
Represents a point in time relative to the operating system startup time.
Definition time_stamp.h:38
time_representation value() const
returns the time_stamp in terms of the lower level type.
Definition time_stamp.h:61
#define formal(parameter)
This macro just eats what it's passed; it marks unused formal parameters.
Definition definitions.h:48
#define NULL_POINTER
The value representing a pointer to nothing.
Definition definitions.h:32
#define DEFINE_CLASS_NAME(objname)
Defines the name of a class by providing a couple standard methods.
Definition enhance_cpp.h:42
#define FUNCDEF(func_in)
FUNCDEF sets the name of a function (and plugs it into the callstack).
Definition enhance_cpp.h:54
Provides macros that implement the 'main' program of an application.
#define HOOPLE_MAIN(obj_name, obj_args)
options that should work for most unix and linux apps.
Definition hoople_main.h:61
Implements an application lock to ensure only one is running at once.
The guards collection helps in testing preconditions and reporting errors.
Definition array.h:30
const int SECOND_ms
Number of milliseconds in a second.
A logger that sends to the console screen using the standard output device.
An extension to floating point primitives providing approximate equality.
Definition averager.h:21
void safe_add(int &to_change, int addition)
thread-safe integer addition.
A dynamic container class that holds any kind of object via pointers.
Definition amorph.h:55
#include <time.h>
Useful support functions for unit testing, especially within hoople.
Definition unit_base.cpp:35
int grab_lock
const int DEFAULT_FISH
const int MIN_SAME_THREAD_LOCKING_TESTS
const int MAX_SAME_THREAD_LOCKING_TESTS
const int THREAD_PAUSE_LOWEST
const int DEFAULT_RUN_TIME
const int THREAD_PAUSE_HIGHEST
mutex & guard()
const int MAX_MUTEX_TIMING_TEST
#define LOG(to_print)
int concurrent_biters
astring protected_string
#define ASSERT_EQUAL(a, b, test_name)
Definition unit_base.h:38
#define ASSERT_TRUE(a, test_name)
Definition unit_base.h:46