| /* |
| * 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. |
| */ |
| |
| #include <iostream> |
| #include <unistd.h> |
| |
| #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() |