PageRenderTime 144ms CodeModel.GetById 40ms app.highlight 67ms RepoModel.GetById 34ms app.codeStats 0ms

/mordor/tests/iomanager.cpp

http://github.com/mozy/mordor
C++ | 242 lines | 200 code | 25 blank | 17 comment | 8 complexity | b9cb0964119f8783c99df5b7ad635ddd MD5 | raw file
  1// Copyright (c) 2009 - Mozy, Inc.
  2
  3#include <boost/bind.hpp>
  4
  5#include "mordor/future.h"
  6#include "mordor/iomanager.h"
  7#include "mordor/test/test.h"
  8#include "mordor/thread.h"
  9#include "mordor/version.h"
 10#include "mordor/socket.h"
 11
 12using namespace Mordor;
 13using namespace Mordor::Test;
 14
 15namespace
 16{
 17    class EmptyTimeClass
 18    {
 19    public:
 20        typedef boost::shared_ptr<EmptyTimeClass> ptr;
 21    public:
 22        EmptyTimeClass()
 23            : m_timedOut(false)
 24            , m_future(NULL)
 25        {}
 26
 27        void onTimer() {
 28            m_timedOut = true;
 29            if (m_future)
 30                m_future->signal();
 31        }
 32
 33        void setFuture(Future<> *future) { m_future = future; }
 34        bool timedOut() const { return m_timedOut; }
 35
 36    private:
 37        bool m_timedOut;
 38        Future<> * m_future;
 39    };
 40
 41    void testTimerExpired(IOManager &manager)
 42    {
 43        Future<> future;
 44        EmptyTimeClass::ptr tester(new EmptyTimeClass());
 45        tester->setFuture(&future);
 46        MORDOR_TEST_ASSERT(tester.unique());
 47        manager.registerTimer(1000, boost::bind(&EmptyTimeClass::onTimer, tester));
 48        MORDOR_TEST_ASSERT(!tester.unique());
 49        // wait until timed out
 50        future.wait();
 51        MORDOR_TEST_ASSERT(tester->timedOut());
 52        MORDOR_TEST_ASSERT(tester.unique());
 53    }
 54
 55    void testTimerNoExpire(IOManager &manager)
 56    {
 57        EmptyTimeClass::ptr tester(new EmptyTimeClass());
 58        MORDOR_TEST_ASSERT(tester.unique());
 59        Timer::ptr timer = manager.registerTimer(30000000,
 60            boost::bind(&EmptyTimeClass::onTimer, tester));
 61        MORDOR_TEST_ASSERT(!tester.unique());
 62        timer->cancel();
 63        MORDOR_TEST_ASSERT(!tester->timedOut());
 64        MORDOR_TEST_ASSERT(tester.unique());
 65    }
 66}
 67
 68static void
 69singleTimer(int &sequence, int &expected)
 70{
 71    ++sequence;
 72    MORDOR_TEST_ASSERT_EQUAL(sequence, expected);
 73}
 74
 75MORDOR_UNITTEST(IOManager, singleTimer)
 76{
 77    int sequence = 0;
 78    IOManager manager;
 79    manager.registerTimer(0, boost::bind(&singleTimer, boost::ref(sequence), 1));
 80    MORDOR_TEST_ASSERT_EQUAL(sequence, 0);
 81    manager.dispatch();
 82    ++sequence;
 83    MORDOR_TEST_ASSERT_EQUAL(sequence, 2);
 84}
 85
 86MORDOR_UNITTEST(IOManager, laterTimer)
 87{
 88    int sequence = 0;
 89    IOManager manager;
 90    manager.registerTimer(100000, boost::bind(&singleTimer, boost::ref(sequence), 1));
 91    MORDOR_TEST_ASSERT_EQUAL(sequence, 0);
 92    manager.dispatch();
 93    ++sequence;
 94    MORDOR_TEST_ASSERT_EQUAL(sequence, 2);
 95}
 96
 97MORDOR_UNITTEST(IOManager, timerRefCountNoExpired)
 98{
 99    IOManager manager;
100    manager.schedule(boost::bind(testTimerNoExpire, boost::ref(manager)));
101    manager.dispatch();
102}
103
104MORDOR_UNITTEST(IOManager, timerRefCountExpired)
105{
106    IOManager manager;
107    manager.schedule(boost::bind(testTimerExpired, boost::ref(manager)));
108    manager.dispatch();
109}
110
111static void
112busyFiber(int &sequence)
113{
114    // this function will keep the scheduler busy (no idle)
115    while (true) {
116        --sequence;
117        if (sequence <= 0)
118            break;
119        MORDOR_LOG_INFO(Mordor::Log::root()) << " sequence: " << sequence;
120        Scheduler::yield();
121    }
122}
123
124static void
125switchThreadFiber(int &sequence, tid_t tidA, tid_t tidB)
126{
127    // this function will switch itself to anther thread and continue
128    while (true) {
129        if (sequence <= 0)
130            return;
131        tid_t otherTid = (gettid() == tidA) ? tidB : tidA;
132        Scheduler::getThis()->schedule(Fiber::getThis(), otherTid);
133        Scheduler::yieldTo();
134    }
135}
136
137MORDOR_UNITTEST(IOManager, tickleDeadlock)
138{
139    IOManager manager(2, true);
140    tid_t tidA = manager.rootThreadId();
141    tid_t tidB = 0;
142    {
143        const std::vector<boost::shared_ptr<Thread> > threads =
144            manager.threads();
145        MORDOR_TEST_ASSERT_EQUAL(threads.size(), 1U);
146        tidB = threads[0]->tid();
147    }
148    MORDOR_TEST_ASSERT_NOT_EQUAL(tidA, tidB);
149    // there are >2 tickles each time the busyFiber get running.
150    // sequence initial value will control how long the case will run
151#ifdef LINUX
152    // pipe buffer is 64K on Linux 2.6.x kernel
153    int sequence = 32768;
154#elif defined(OSX)
155    // MAC pipe buffer is 8K by default
156    int sequence = 8192;
157#else
158    // Other platform, sanity check with a small number
159    int sequence = 4096;
160#endif
161
162    // schedule a busy fiber
163    manager.schedule(boost::bind(busyFiber, boost::ref(sequence)));
164    // schedule aditional two fibers to ensure that each thread will get
165    // get at least one fiber to execute at anytime, no chance to idle.
166    manager.schedule(boost::bind(switchThreadFiber,
167                                 boost::ref(sequence), tidA, tidB),
168                     tidA);
169    manager.schedule(boost::bind(switchThreadFiber,
170                                 boost::ref(sequence), tidA, tidB),
171                     tidB);
172    manager.stop();
173}
174
175namespace {
176    struct Connection
177    {
178        Socket::ptr connect;
179        Socket::ptr listen;
180        Socket::ptr accept;
181        IPAddress::ptr address;
182    };
183}
184
185static Connection establishConn(IOManager &ioManager)
186{
187    Connection result;
188    std::vector<Address::ptr> addresses = Address::lookup("localhost");
189    MORDOR_TEST_ASSERT(!addresses.empty());
190    result.address = boost::dynamic_pointer_cast<IPAddress>(addresses.front());
191    result.listen = result.address->createSocket(ioManager, SOCK_STREAM);
192    unsigned int opt = 1;
193    result.listen->setOption(SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
194    while (true) {
195        try {
196            // Random port > 1000
197            result.address->port(rand() % 50000 + 1000);
198            result.listen->bind(result.address);
199            break;
200        } catch (AddressInUseException &) {
201        }
202    }
203    result.listen->listen();
204    result.connect = result.address->createSocket(ioManager, SOCK_STREAM);
205    return result;
206}
207
208static void connect(IOManager &manager)
209{
210    try {
211        Connection conns = establishConn(manager);
212        conns.connect->sendTimeout(30000000);
213        conns.connect->receiveTimeout(30000000);
214        conns.listen->receiveTimeout(30000000);
215        conns.connect->connect(conns.address);
216        conns.connect->shutdown();
217    } catch (TimedOutException &) {
218    }
219}
220
221#ifdef OSX
222#define THREADCOUNT 100U
223#else
224#define THREADCOUNT 10U
225#endif
226
227//Use 100 threads creating sockets, add/delete kevent into/from kqueue at same time,
228//overwhelm the system so that it cannot respond to all the events,
229//This could easily make kqueue return with EINPROGRESS (deferred deletion) error
230//and we make sure error ignored in MacOS -- redmine #145605
231MORDOR_UNITTEST(IOManager, ignoreEinprogressErrorOnMac)
232{
233    IOManager manager(THREADCOUNT, false);
234
235    const std::vector<boost::shared_ptr<Thread> > threads = manager.threads();
236    MORDOR_TEST_ASSERT_EQUAL(threads.size(), THREADCOUNT);
237
238    for(size_t i=0; i<threads.size(); i++) {
239        manager.schedule(boost::bind(connect, boost::ref(manager)), threads[i]->tid());
240    }
241    manager.stop();
242}