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 
16 #include <basis/astring.h>
17 #include <basis/guards.h>
18 #include <basis/mutex.h>
22 #include <mathematics/chaos.h>
23 #include <processes/ethread.h>
24 #include <processes/safe_roller.h>
25 #include <structures/amorph.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 
63  // the range of times we'll test recursively locking the mutex.
64 
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 
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.
83 {
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");
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 
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");
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");
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 
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 
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.",
294  DEFAULT_FISH));
295 #endif
296 
297  return final_report();
298 }
299 
300 HOOPLE_MAIN(test_mutex, )
301 
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:45
#define FUNCDEF(func_in)
FUNCDEF sets the name of a function (and plugs it into the callstack).
Definition: enhance_cpp.h:57
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:68
const int DEFAULT_FISH
Definition: test_mutex.cpp:50
const int MIN_SAME_THREAD_LOCKING_TESTS
Definition: test_mutex.cpp:61
const int MAX_SAME_THREAD_LOCKING_TESTS
Definition: test_mutex.cpp:62
const int THREAD_PAUSE_LOWEST
Definition: test_mutex.cpp:56
const int DEFAULT_RUN_TIME
Definition: test_mutex.cpp:53
const int THREAD_PAUSE_HIGHEST
Definition: test_mutex.cpp:57
mutex & guard()
Definition: test_mutex.cpp:71
const int MAX_MUTEX_TIMING_TEST
Definition: test_mutex.cpp:47
#define LOG(to_print)
Definition: test_mutex.cpp:78
void test_recursive_locking(chaos &_rando)
Definition: test_mutex.cpp:82
int concurrent_biters
Definition: test_mutex.cpp:65
astring protected_string
Definition: test_mutex.cpp:75
#define ASSERT_EQUAL(a, b, test_name)
Definition: unit_base.h:38
#define ASSERT_TRUE(a, test_name)
Definition: unit_base.h:46