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>
25 #include <processes/safe_roller.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 
36 using namespace application;
37 using namespace basis;
38 using namespace loggers;
39 using namespace mathematics;
40 using namespace timely;
41 using namespace processes;
42 using namespace structures;
43 using namespace unit_test;
44 
45 class test_mutex; // forward.
46 
47 #define DEBUG_MUTEX
48  // uncomment for a verbose test run.
49 
50 const int MAX_MUTEX_TIMING_TEST = 2000000;
51  // the number of times we'll lock and unlock a mutex.
52 
53 const int DEFAULT_FISH = 32;
54  // the number of threads, by default.
55 
56 const int DEFAULT_RUN_TIME = 2 * SECOND_ms;
57  // the length of time to run the program.
58 
59 const int THREAD_PAUSE_LOWEST = 0;
60 const 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 
71 int grab_lock = 0;
72  // this is upped whenever a fish obtains access to the mutex.
73 
74 mutex &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 
89 class test_mutex : virtual public unit_base, virtual public application_shell
90 {
91 public:
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 
112 class piranha : public ethread
113 {
114 public:
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.
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  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())
157  time_control::sleep_ms(_rando.inclusive(THREAD_PAUSE_LOWEST, THREAD_PAUSE_HIGHEST));
158  }
159 
160 };
161 
163 
164 class barracuda : public ethread
165 {
166 public:
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();
189  safe_add(grab_lock, 1);
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())
204  time_control::sleep_ms(_rando.inclusive(THREAD_PAUSE_LOWEST, THREAD_PAUSE_HIGHEST));
205  }
206 };
207 
209 
210 #undef UNIT_BASE_THIS_OBJECT
211 #define UNIT_BASE_THIS_OBJECT (*this)
212 
213 int 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++) {
259  ethread *t = NULL_POINTER;
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.
283  q->start(NULL_POINTER);
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()) {
295  time_control::sleep_ms(100);
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.
346 void 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 
376 HOOPLE_MAIN(test_mutex, )
377 
#define GET_AND_TEST_STACK_TRACE(header, failure_return)
The application_shell is a base object for console programs.
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:95
void unlock()
Gives up the possession of the mutex.
Definition: mutex.cpp:107
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:146
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
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.
Definition: definitions.h:120
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.
Definition: safe_roller.cpp:29
A dynamic container class that holds any kind of object via pointers.
Definition: amorph.h:55
#include <time.h>
Definition: earth_time.cpp:37
Useful support functions for unit testing, especially within hoople.
Definition: unit_base.cpp:35
chaos _rando
int grab_lock
Definition: test_mutex.cpp:71
const int DEFAULT_FISH
Definition: test_mutex.cpp:53
const int MIN_SAME_THREAD_LOCKING_TESTS
Definition: test_mutex.cpp:64
const int MAX_SAME_THREAD_LOCKING_TESTS
Definition: test_mutex.cpp:65
const int THREAD_PAUSE_LOWEST
Definition: test_mutex.cpp:59
const int DEFAULT_RUN_TIME
Definition: test_mutex.cpp:56
const int THREAD_PAUSE_HIGHEST
Definition: test_mutex.cpp:60
mutex & guard()
Definition: test_mutex.cpp:74
const int MAX_MUTEX_TIMING_TEST
Definition: test_mutex.cpp:50
#define LOG(to_print)
Definition: test_mutex.cpp:81
int concurrent_biters
Definition: test_mutex.cpp:68
astring protected_string
Definition: test_mutex.cpp:78
#define ASSERT_EQUAL(a, b, test_name)
Definition: unit_base.h:38
#define ASSERT_TRUE(a, test_name)
Definition: unit_base.h:46