Browse Source

Interface and tests for ZoneEntry::Lock

Which will lock both the zone entry and the nameservers, so we can
manipulate with them a little bit.

git-svn-id: svn://bind10.isc.org/svn/bind10/branches/trac408@3516 e5f2f494-b856-4b98-b285-d166d9295462
Michal Vaner 14 years ago
parent
commit
7c68cbb3d6
3 changed files with 108 additions and 4 deletions
  1. 72 0
      src/lib/nsas/tests/zone_entry_unittest.cc
  2. 4 4
      src/lib/nsas/zone_entry.cc
  3. 32 0
      src/lib/nsas/zone_entry.h

+ 72 - 0
src/lib/nsas/tests/zone_entry_unittest.cc

@@ -16,6 +16,7 @@
 
 #include <gtest/gtest.h>
 #include <boost/shared_ptr.hpp>
+#include <boost/thread.hpp>
 
 #include <dns/rrclass.h>
 
@@ -36,6 +37,7 @@ namespace nsas {
 
 // String constants.  These should end in a dot.
 static const std::string EXAMPLE_CO_UK("example.co.uk.");
+static const std::string EXAMPLE_NET("example.net.");
 
 /// \brief Test Fixture Class
 class ZoneEntryTest : public ::testing::Test {
@@ -95,5 +97,75 @@ TEST_F(ZoneEntryTest, NameserverIterators) {
     EXPECT_TRUE(zone_const.begin() + 1 == zone_const.end());
 }
 
+void lockAndWait(ZoneEntry* zone, barrier* when) {
+    ZoneEntry::Lock lock(zone->getLock());
+    when->wait();
+}
+
+void lockAndKeep(ZoneEntry* zone, bool* locked_self, bool* locked_other,
+    barrier* when)
+{
+    // Lock
+    ZoneEntry::Lock lock(zone->getLock());
+    *locked_self = true;
+    // Wait for the start of the other thread
+    when->wait();
+    // Make sure the other thread gets a chance to run
+    for (int i(0); i < 100; ++ i) {
+        this_thread::yield();
+        EXPECT_FALSE(*locked_other);
+    }
+    *locked_self = false;
+}
+
+TEST_F(ZoneEntryTest, Lock) {
+    // Create some testing data
+    ZoneEntry z1(EXAMPLE_CO_UK, RRClass::IN().getCode());
+    ZoneEntry z2(EXAMPLE_NET, RRClass::IN().getCode());
+    shared_ptr<NameserverEntry> ns1(new NameserverEntry(EXAMPLE_CO_UK,
+        RRClass::IN().getCode()));
+    shared_ptr<NameserverEntry> ns2(new NameserverEntry(EXAMPLE_NET,
+        RRClass::IN().getCode()));
+    z1.nameserverAdd(ns1);
+    z2.nameserverAdd(ns2);
+
+    barrier both(2);
+
+    // This tries that both can lock right now.
+    // FIXME If they can't it will deadlock. Any idea how to do it better?
+    thread t1(lockAndWait, &z1, &both);
+    thread t2(lockAndWait, &z2, &both);
+    t1.join();
+    t2.join();
+
+    z1.nameserverAdd(ns2);
+    z2.nameserverAdd(ns1);
+    // Now check that they can't both lock at the same time.
+    barrier both_second(2);
+    bool l1(false), l2(false);
+    thread t3(lockAndKeep, &z1, &l1, &l2, &both);
+    thread t4(lockAndKeep, &z2, &l2, &l1, &both_second);
+    both.wait();
+    // Make sure one of them is started
+    for (int i(0); i < 100; ++ i) {
+        this_thread::yield();
+    }
+    both_second.wait();
+    t3.join();
+    t4.join();
+
+    // Try it the other way around (so it does not depend on the order of nameservers
+    thread t6(lockAndKeep, &z2, &l2, &l1, &both);
+    thread t5(lockAndKeep, &z1, &l1, &l2, &both_second);
+    both.wait();
+    // Make sure one of them is started
+    for (int i(0); i < 100; ++ i) {
+        this_thread::yield();
+    }
+    both_second.wait();
+    t5.join();
+    t6.join();
+}
+
 }   // namespace nsas
 }   // namespace isc

+ 4 - 4
src/lib/nsas/zone_entry.cc

@@ -22,25 +22,25 @@ namespace nsas {
 
 namespace {
 // Shorter aliases for frequently used types
-typedef boost::mutex::scoped_lock Lock;
+typedef boost::mutex::scoped_lock LLock; // Local lock, nameservers not locked
 typedef boost::shared_ptr<AddressRequestCallback> CallbackPtr;
 }
 
 void
 ZoneEntry::addCallback(CallbackPtr callback) {
-    Lock lock(mutex_);
+    LLock lock(mutex_);
     callbacks_.push_back(callback);
 }
 
 bool
 ZoneEntry::hasCallbacks() const {
-    Lock lock(mutex_);
+    LLock lock(mutex_);
     return (!callbacks_.empty());
 }
 
 CallbackPtr
 ZoneEntry::popCallback() {
-    Lock lock(mutex_);
+    LLock lock(mutex_);
     CallbackPtr result(callbacks_.front());
     callbacks_.pop_front();
     return (result);

+ 32 - 0
src/lib/nsas/zone_entry.h

@@ -116,6 +116,38 @@ public:
     iterator end() { return (nameservers_.end()); }
     const_iterator begin() const { return (nameservers_.begin()); }
     const_iterator end() const { return (nameservers_.end()); }
+
+    /**
+     * \short Lock of the zone entry.
+     *
+     * Something like a scope lock for the zone entry. It can be copyed (so
+     * the result of the getLock() can be assigned to a local variable). The
+     * lock is released once all copyes of the getLock result are destroyed.
+     * However, it is not reentrant (another call to getLock will block).
+     *
+     * This locks both the zone entry and all nameserver entries in a manner
+     * avoiding deadlocks (sorts the nameserver entries before trying to
+     * acquire them). However, it asumes noone does any other kind of locking
+     * of multiple muteces.
+     *
+     * Copy constructor, assignment operator and destructor are default.
+     * The constructor that creates a new lock is private, use getLock()
+     * to lock a zone entry.
+     */
+    class Lock {
+        private:
+            struct Impl;
+            boost::shared_ptr<Impl> impl_;
+            Lock(ZoneEntry&);
+            friend class ZoneEntry;
+    };
+
+    /**
+     * \short Acquire a lock.
+     *
+     * \see Lock
+     */
+    Lock getLock() { return Lock(*this); }
 private:
     mutable boost::mutex    mutex_;     ///< Mutex protecting this zone entry
     std::string     name_;      ///< Canonical zone name