/mordor/tests/iomanager.cpp
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}