THRIFT-3978: tighten up pthread mutex implementation, removing asserts and replacing them with exceptions
Client: cpp

This closes #1228
diff --git a/lib/cpp/test/concurrency/MutexTest.cpp b/lib/cpp/test/concurrency/MutexTest.cpp
new file mode 100644
index 0000000..781ec1a
--- /dev/null
+++ b/lib/cpp/test/concurrency/MutexTest.cpp
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// This is linked into the UnitTests test executable
+
+#include <boost/test/unit_test.hpp>
+
+#include "thrift/concurrency/Exception.h"
+#include "thrift/concurrency/Mutex.h"
+
+using boost::unit_test::test_suite;
+using boost::unit_test::framework::master_test_suite;
+
+using namespace apache::thrift::concurrency;
+
+struct LFAT
+{
+  LFAT()
+    : uut(Mutex::ERRORCHECK_INITIALIZER)
+  {
+    BOOST_CHECK_EQUAL(0, pthread_mutex_init(&mx, 0));
+    BOOST_CHECK_EQUAL(0, pthread_cond_init(&cv, 0));
+  }
+
+  Mutex uut;
+  pthread_mutex_t mx;
+  pthread_cond_t cv;
+};
+
+// Helper for testing mutex behavior when locked by another thread
+void * lockFromAnotherThread(void *ptr)
+{
+  struct LFAT *lfat = (LFAT *)ptr;
+  BOOST_CHECK_EQUAL   (0, pthread_mutex_lock(&lfat->mx));           // synchronize with testing thread
+  BOOST_CHECK_NO_THROW( lfat->uut.lock());
+  BOOST_CHECK_EQUAL   (0, pthread_cond_signal(&lfat->cv));          // tell testing thread we have locked the mutex
+  BOOST_CHECK_EQUAL   (0, pthread_cond_wait(&lfat->cv, &lfat->mx)); // wait for testing thread to signal condition variable telling us to unlock
+  BOOST_CHECK_NO_THROW( lfat->uut.unlock());
+  return ptr;                                                       // testing thread should join to ensure completeness
+}
+
+BOOST_AUTO_TEST_SUITE(MutexTest)
+
+BOOST_AUTO_TEST_CASE(happy_path)
+{
+  Mutex uut(Mutex::ERRORCHECK_INITIALIZER);                         // needed to test unlocking twice without undefined behavior
+
+  BOOST_CHECK_NO_THROW( uut.lock());
+  BOOST_CHECK_THROW   ( uut.lock(), SystemResourceException);       // EDEADLK (this thread owns it)
+  BOOST_CHECK_NO_THROW( uut.unlock());
+}
+
+BOOST_AUTO_TEST_CASE(recursive_happy_path)
+{
+  Mutex uut(Mutex::RECURSIVE_INITIALIZER);
+
+  BOOST_CHECK_NO_THROW( uut.lock());
+  BOOST_CHECK_NO_THROW( uut.lock());
+  BOOST_CHECK_NO_THROW( uut.unlock());
+  BOOST_CHECK_NO_THROW( uut.lock());
+  BOOST_CHECK_NO_THROW( uut.lock());
+  BOOST_CHECK_NO_THROW( uut.unlock());
+  BOOST_CHECK_NO_THROW( uut.lock());
+  BOOST_CHECK_NO_THROW( uut.unlock());
+  BOOST_CHECK_NO_THROW( uut.unlock());
+  BOOST_CHECK_NO_THROW( uut.unlock());
+}
+
+BOOST_AUTO_TEST_CASE(trylock)
+{
+  Mutex uut(Mutex::ADAPTIVE_INITIALIZER);   // just using another initializer for coverage
+
+  BOOST_CHECK         ( uut.trylock());
+  BOOST_CHECK         (!uut.trylock());
+  BOOST_CHECK_NO_THROW( uut.unlock());
+}
+
+BOOST_AUTO_TEST_CASE(timedlock)
+{
+  pthread_t th;
+  struct LFAT lfat;
+
+  BOOST_CHECK         ( lfat.uut.timedlock(100));
+  BOOST_CHECK_THROW   ( lfat.uut.timedlock(100),
+                        SystemResourceException);                   // EDEADLK (current thread owns mutex - logic error)
+  BOOST_CHECK_NO_THROW( lfat.uut.unlock());
+
+  BOOST_CHECK_EQUAL   (0, pthread_mutex_lock(&lfat.mx));            // synchronize with helper thread
+  BOOST_CHECK_EQUAL   (0, pthread_create(&th, NULL,
+                            lockFromAnotherThread, &lfat));         // create helper thread
+  BOOST_CHECK_EQUAL   (0, pthread_cond_wait(&lfat.cv, &lfat.mx));   // wait for helper thread to lock mutex
+
+  BOOST_CHECK         (!lfat.uut.timedlock(100));                   // false: another thread owns the lock
+
+  BOOST_CHECK_EQUAL   (0, pthread_cond_signal(&lfat.cv));           // tell helper thread we are done
+  BOOST_CHECK_EQUAL   (0, pthread_mutex_unlock(&lfat.mx));          // let helper thread clean up
+  BOOST_CHECK_EQUAL   (0, pthread_join(th, 0));                     // wait for testing thread to unlock and be done
+}
+
+BOOST_AUTO_TEST_CASE(underlying)
+{
+  Mutex uut;
+
+  BOOST_CHECK         ( uut.getUnderlyingImpl());
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/lib/cpp/test/concurrency/RWMutexStarveTest.cpp b/lib/cpp/test/concurrency/RWMutexStarveTest.cpp
new file mode 100644
index 0000000..63d780f
--- /dev/null
+++ b/lib/cpp/test/concurrency/RWMutexStarveTest.cpp
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// This is linked into the UnitTests test executable
+
+#include <boost/shared_ptr.hpp>
+#include <boost/test/unit_test.hpp>
+
+#include "thrift/concurrency/Mutex.h"
+#include "thrift/concurrency/PosixThreadFactory.h"
+
+using boost::shared_ptr;
+using boost::unit_test::test_suite;
+using boost::unit_test::framework::master_test_suite;
+
+using namespace apache::thrift::concurrency;
+using namespace std;
+
+class Locker : public Runnable {
+protected:
+  Locker(boost::shared_ptr<ReadWriteMutex> rwlock, bool writer)
+    : rwlock_(rwlock), writer_(writer), started_(false), gotLock_(false), signaled_(false) {}
+
+public:
+  virtual void run() {
+    started_ = true;
+    if (writer_) {
+      rwlock_->acquireWrite();
+    } else {
+      rwlock_->acquireRead();
+    }
+    gotLock_ = true;
+    while (!signaled_) {
+      usleep(5000);
+    }
+    rwlock_->release();
+  }
+
+  bool started() const { return started_; }
+  bool gotLock() const { return gotLock_; }
+  void signal() { signaled_ = true; }
+
+protected:
+  boost::shared_ptr<ReadWriteMutex> rwlock_;
+  bool writer_;
+  volatile bool started_;
+  volatile bool gotLock_;
+  volatile bool signaled_;
+};
+
+class Reader : public Locker {
+public:
+  Reader(boost::shared_ptr<ReadWriteMutex> rwlock) : Locker(rwlock, false) {}
+};
+
+class Writer : public Locker {
+public:
+  Writer(boost::shared_ptr<ReadWriteMutex> rwlock) : Locker(rwlock, true) {}
+};
+
+void test_starve(PosixThreadFactory::POLICY policy) {
+  // the man pages for pthread_wrlock_rdlock suggest that any OS guarantee about
+  // writer starvation may be influenced by the scheduling policy, so let's try
+  // all 3 policies to see if any of them work.
+  PosixThreadFactory factory(policy);
+  factory.setDetached(false);
+
+  boost::shared_ptr<ReadWriteMutex> rwlock(new NoStarveReadWriteMutex());
+
+  boost::shared_ptr<Reader> reader1(new Reader(rwlock));
+  boost::shared_ptr<Reader> reader2(new Reader(rwlock));
+  boost::shared_ptr<Writer> writer(new Writer(rwlock));
+
+  boost::shared_ptr<Thread> treader1 = factory.newThread(reader1);
+  boost::shared_ptr<Thread> treader2 = factory.newThread(reader2);
+  boost::shared_ptr<Thread> twriter = factory.newThread(writer);
+
+  // launch a reader and make sure he has the lock
+  treader1->start();
+  while (!reader1->gotLock()) {
+    usleep(2000);
+  }
+
+  // launch a writer and make sure he's blocked on the lock
+  twriter->start();
+  while (!writer->started()) {
+    usleep(2000);
+  }
+  // tricky part... we can never be 100% sure that the writer is actually
+  // blocked on the lock, but we can pretty reasonably sure because we know
+  // he just executed the line immediately before getting the lock, and
+  // we'll wait a full second for him to get on it.
+  sleep(1);
+
+  // launch a second reader... if the RWMutex guarantees that writers won't
+  // starve, this reader should not be able to acquire the lock until the writer
+  // has acquired and released it.
+  treader2->start();
+  while (!reader2->started()) {
+    usleep(2000);
+  }
+  // again... can't be 100% sure the reader is waiting on (or has) the lock
+  // but we can be close.
+  sleep(1);
+
+  // tell reader 1 to let go of the lock
+  reader1->signal();
+
+  // wait for someone to get the lock
+  while (!reader2->gotLock() && !writer->gotLock()) {
+    usleep(2000);
+  }
+
+  // the test succeeded if the WRITER got the lock.
+  bool success = writer->gotLock();
+
+  // tell everyone we're done and wait for them to finish
+  reader2->signal();
+  writer->signal();
+  treader1->join();
+  treader2->join();
+  twriter->join();
+
+  // make sure it worked.
+  BOOST_CHECK_MESSAGE(success, "writer is starving");
+}
+
+BOOST_AUTO_TEST_SUITE(RWMutexStarveTest)
+
+BOOST_AUTO_TEST_CASE(test_starve_other) {
+  test_starve(PosixThreadFactory::OTHER);
+}
+
+BOOST_AUTO_TEST_CASE(test_starve_rr) {
+  test_starve(PosixThreadFactory::ROUND_ROBIN);
+}
+
+BOOST_AUTO_TEST_CASE(test_starve_fifo) {
+  test_starve(PosixThreadFactory::FIFO);
+}
+
+BOOST_AUTO_TEST_SUITE_END()