|
@@ -1,4 +1,4 @@
|
|
|
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
|
|
|
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
|
|
|
//
|
|
|
// Permission to use, copy, modify, and/or distribute this software for any
|
|
|
// purpose with or without fee is hereby granted, provided that the above
|
|
@@ -12,29 +12,19 @@
|
|
|
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
|
// PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
|
|
-#include "../lock.h"
|
|
|
-#include "../thread.h"
|
|
|
+#include <util/threads/lock.h>
|
|
|
+#include <util/threads/thread.h>
|
|
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
|
|
#include <boost/bind.hpp>
|
|
|
+#include <unistd.h>
|
|
|
+#include <signal.h>
|
|
|
|
|
|
using namespace isc::util::thread;
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
-// Test a recursive mutex can be locked multiple times
|
|
|
-TEST(MutexTest, recursiveLockMultiple) {
|
|
|
- Mutex mutex(true);
|
|
|
- EXPECT_FALSE(mutex.locked()); // Debug-only build
|
|
|
- Mutex::Locker l1(mutex);
|
|
|
- EXPECT_TRUE(mutex.locked()); // Debug-only build
|
|
|
- Mutex::Locker l2(mutex);
|
|
|
- Mutex::Locker l3(mutex);
|
|
|
- Mutex::Locker l4(mutex);
|
|
|
- Mutex::Locker l5(mutex);
|
|
|
-}
|
|
|
-
|
|
|
// If we try to lock the debug mutex multiple times, it should throw.
|
|
|
TEST(MutexTest, lockMultiple) {
|
|
|
// TODO: Once we support non-debug mutexes, disable the test if we compile
|
|
@@ -50,21 +40,17 @@ TEST(MutexTest, lockMultiple) {
|
|
|
}
|
|
|
|
|
|
// Destroying a locked mutex is a bad idea as well
|
|
|
-//
|
|
|
-// FIXME: The test is disabled, since it screws something up in the VM (other
|
|
|
-// tests fail then with rather cryptic messages, memory dumps and stuff).
|
|
|
-// Any idea how to make the test work and reasonably safe?
|
|
|
-TEST(MutexTest, DISABLED_destroyLocked) {
|
|
|
- // TODO: This probably won't work for non-debug mutexes. Disable on
|
|
|
- // non-debug compilation.
|
|
|
- Mutex* mutex = new Mutex;
|
|
|
- new Mutex::Locker(*mutex);
|
|
|
- EXPECT_THROW(delete mutex, isc::InvalidOperation);
|
|
|
- // Note: This leaks the locker. But this is a test for development aid
|
|
|
- // exception. The exception won't happen in normal build anyway and seeing
|
|
|
- // it means there's a bug. And we can't delete the locker now, since it
|
|
|
- // would access uninitialized memory.
|
|
|
+#ifdef EXPECT_DEATH
|
|
|
+TEST(MutexTest, destroyLocked) {
|
|
|
+ EXPECT_DEATH({
|
|
|
+ Mutex* mutex = new Mutex;
|
|
|
+ new Mutex::Locker(*mutex);
|
|
|
+ delete mutex;
|
|
|
+ // This'll leak the locker, but inside the slave process, it should
|
|
|
+ // not be an issue.
|
|
|
+ }, "");
|
|
|
}
|
|
|
+#endif
|
|
|
|
|
|
// This test tries if a mutex really locks. We could try that with a deadlock,
|
|
|
// but that's not practical (the test would not end).
|
|
@@ -74,6 +60,9 @@ TEST(MutexTest, DISABLED_destroyLocked) {
|
|
|
// threads are started and the operation must be complicated enough so the
|
|
|
// compiler won't turn it into some kind of single atomic instruction.
|
|
|
//
|
|
|
+// FIXME: Any idea for a simpler but non-atomic operation that keeps an
|
|
|
+// invariant?
|
|
|
+//
|
|
|
// So, we'll have an array of numbers. Each thread will try to repeatedly
|
|
|
// find a number large at least as half of the average, take the number,
|
|
|
// distribute the value across the rest of the positions of the array and
|
|
@@ -82,49 +71,77 @@ TEST(MutexTest, DISABLED_destroyLocked) {
|
|
|
// one will add something to the position another one have chosen and
|
|
|
// the other one will then zero it, not taking the new value into account.
|
|
|
// That'd lower the total value of the array.
|
|
|
+//
|
|
|
+// We run the threads in opposite directions (so we have no chance of them
|
|
|
+// keeping the same distance to each other and not meeting). Also, the indexing
|
|
|
+// is performed in a circular manner like with a ring buffer.
|
|
|
const unsigned long long length = 1000;
|
|
|
const unsigned long long iterations = 10000;
|
|
|
const unsigned long long value = 2000;
|
|
|
void
|
|
|
-performStrangeOperation(long long unsigned* array, int direction, Mutex* mutex)
|
|
|
+performStrangeOperation(std::vector<long long unsigned>& array, int direction,
|
|
|
+ Mutex* mutex)
|
|
|
{
|
|
|
unsigned long long position = 0;
|
|
|
for (size_t i = 0; i < iterations; ++i) {
|
|
|
Mutex::Locker lock(*mutex);
|
|
|
+ // Find a place with large enough value
|
|
|
while (array[position % length] < value) {
|
|
|
position += direction;
|
|
|
}
|
|
|
- unsigned long long value = array[position % length];
|
|
|
+ // Take the value
|
|
|
+ unsigned long long found_value = array[position % length];
|
|
|
+ // And distribute it to places following the found one, by
|
|
|
+ // adding 1 to each.
|
|
|
unsigned long long p2 = position;
|
|
|
- while (value > 0) {
|
|
|
+ while (found_value > 0) {
|
|
|
p2 += direction;
|
|
|
if (p2 % length == position % length) {
|
|
|
continue;
|
|
|
}
|
|
|
++array[p2 % length];
|
|
|
- --value;
|
|
|
+ --found_value;
|
|
|
}
|
|
|
+ // Zero the distributed value
|
|
|
array[position % length] = 0;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+void
|
|
|
+no_handler(int) {}
|
|
|
+
|
|
|
TEST(MutexTest, swarm) {
|
|
|
+ // Create a timeout in case something got stuck here
|
|
|
+ struct sigaction ignored, original;
|
|
|
+ memset(&ignored, 0, sizeof ignored);
|
|
|
+ ignored.sa_handler = no_handler;
|
|
|
+ if (sigaction(SIGALRM, &ignored, &original)) {
|
|
|
+ FAIL() << "Couldn't set alarm";
|
|
|
+ }
|
|
|
+ alarm(10);
|
|
|
// This type has a low chance of being atomic itself, further raising
|
|
|
// the chance of problems appearing.
|
|
|
- long long unsigned array[length];
|
|
|
+ std::vector<long long unsigned> array(length);
|
|
|
for (size_t i = 0; i < length; ++i) {
|
|
|
array[i] = value;
|
|
|
}
|
|
|
Mutex mutex;
|
|
|
+ // Run two parallel threads, each in one direction
|
|
|
Thread t1(boost::bind(&performStrangeOperation, array, 1, &mutex));
|
|
|
Thread t2(boost::bind(&performStrangeOperation, array, -1, &mutex));
|
|
|
t1.wait();
|
|
|
t2.wait();
|
|
|
+ // Check the sum didn't change.
|
|
|
long long unsigned sum = 0;
|
|
|
for (size_t i = 0; i < length; ++i) {
|
|
|
sum += array[i];
|
|
|
}
|
|
|
EXPECT_EQ(length * value, sum) << "Threads are badly synchronized";
|
|
|
+ // Cancel the alarm and return the original handler
|
|
|
+ alarm(0);
|
|
|
+ if (sigaction(SIGALRM, &original, NULL)) {
|
|
|
+ FAIL() << "Couldn't restore alarm";
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
}
|