PageRenderTime 4ms CodeModel.GetById 216ms app.highlight 405ms RepoModel.GetById 150ms app.codeStats 1ms

/mordor/tests/socket.cpp

http://github.com/mozy/mordor
C++ | 566 lines | 487 code | 56 blank | 23 comment | 27 complexity | 10030b7e246f02b0da0cad627d40b8d5 MD5 | raw file
  1// Copyright (c) 2009 - Mozy, Inc.
  2
  3#include <iostream>
  4
  5#include <boost/lexical_cast.hpp>
  6#include <boost/scoped_array.hpp>
  7#include <boost/shared_array.hpp>
  8
  9#include "mordor/exception.h"
 10#include "mordor/fiber.h"
 11#include "mordor/iomanager.h"
 12#include "mordor/socket.h"
 13#include "mordor/test/test.h"
 14
 15using namespace Mordor;
 16using namespace Mordor::Test;
 17
 18namespace {
 19struct Connection
 20{
 21    Socket::ptr connect;
 22    Socket::ptr listen;
 23    Socket::ptr accept;
 24    IPAddress::ptr address;
 25};
 26}
 27
 28static void acceptOne(Connection &conns)
 29{
 30    MORDOR_ASSERT(conns.listen);
 31    conns.accept = conns.listen->accept();
 32}
 33
 34static Connection
 35establishConn(IOManager &ioManager)
 36{
 37    Connection result;
 38    std::vector<Address::ptr> addresses = Address::lookup("localhost");
 39    MORDOR_TEST_ASSERT(!addresses.empty());
 40    result.address = boost::dynamic_pointer_cast<IPAddress>(addresses.front());
 41    result.listen = result.address->createSocket(ioManager, SOCK_STREAM);
 42    unsigned int opt = 1;
 43    result.listen->setOption(SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
 44    while (true) {
 45        try {
 46            // Random port > 1000
 47            result.address->port(rand() % 50000 + 1000);
 48            result.listen->bind(result.address);
 49            break;
 50        } catch (AddressInUseException &) {
 51        }
 52    }
 53    result.listen->listen();
 54    result.connect = result.address->createSocket(ioManager, SOCK_STREAM);
 55    return result;
 56}
 57
 58MORDOR_SUITE_INVARIANT(Socket)
 59{
 60    MORDOR_TEST_ASSERT(!Scheduler::getThis());
 61}
 62
 63MORDOR_UNITTEST(Socket, acceptTimeout)
 64{
 65    IOManager ioManager(2, true);
 66    Connection conns = establishConn(ioManager);
 67    conns.listen->receiveTimeout(200000);
 68    unsigned long long start = TimerManager::now();
 69    MORDOR_TEST_ASSERT_EXCEPTION(conns.listen->accept(), TimedOutException);
 70    MORDOR_TEST_ASSERT_ABOUT_EQUAL(start + 200000, TimerManager::now(), 100000);
 71    MORDOR_TEST_ASSERT_EXCEPTION(conns.listen->accept(), TimedOutException);
 72}
 73
 74MORDOR_UNITTEST(Socket, receiveTimeout)
 75{
 76    IOManager ioManager;
 77    Connection conns = establishConn(ioManager);
 78    conns.connect->receiveTimeout(100000);
 79    ioManager.schedule(boost::bind(&acceptOne, boost::ref(conns)));
 80    conns.connect->connect(conns.address);
 81    ioManager.dispatch();
 82    char buf;
 83    unsigned long long start = TimerManager::now();
 84    MORDOR_TEST_ASSERT_EXCEPTION(conns.connect->receive(&buf, 1), TimedOutException);
 85    MORDOR_TEST_ASSERT_ABOUT_EQUAL(start + 100000, TimerManager::now(), 50000);
 86    MORDOR_TEST_ASSERT_EXCEPTION(conns.connect->receive(&buf, 1), TimedOutException);
 87}
 88
 89MORDOR_UNITTEST(Socket, sendTimeout)
 90{
 91    IOManager ioManager;
 92    Connection conns = establishConn(ioManager);
 93    conns.connect->sendTimeout(200000);
 94    ioManager.schedule(boost::bind(&acceptOne, boost::ref(conns)));
 95    conns.connect->connect(conns.address);
 96    ioManager.dispatch();
 97    char buf[65536];
 98    memset(buf, 0, sizeof(buf));
 99    unsigned long long start = TimerManager::now();
100    MORDOR_TEST_ASSERT_EXCEPTION(while (true) conns.connect->send(buf, sizeof(buf)), TimedOutException);
101    MORDOR_TEST_ASSERT_ABOUT_EQUAL(start + 200000, TimerManager::now(), 500000);
102}
103
104class DummyException
105{};
106
107template <class Exception>
108static void testShutdownException(bool send, bool shutdown, bool otherEnd)
109{
110    IOManager ioManager;
111    Connection conns = establishConn(ioManager);
112    ioManager.schedule(boost::bind(&acceptOne, boost::ref(conns)));
113    conns.connect->connect(conns.address);
114    ioManager.dispatch();
115
116    Socket::ptr &socketToClose = otherEnd ? conns.accept : conns.connect;
117    if (shutdown)
118        socketToClose->shutdown(SHUT_RDWR);
119    else
120        socketToClose.reset();
121
122    if (send) {
123        if (otherEnd) {
124            try {
125                conns.connect->sendTimeout(100);
126                while (true) {
127                    MORDOR_TEST_ASSERT_EQUAL(conns.connect->send("abc", 3), 3u);
128                }
129            } catch (const Exception&) {
130            }
131        } else {
132            MORDOR_TEST_ASSERT_EXCEPTION(conns.connect->send("abc", 3), Exception);
133        }
134    } else {
135        unsigned char readBuf[3];
136        if (otherEnd) {
137            MORDOR_TEST_ASSERT_EQUAL(conns.connect->receive(readBuf, 3), 0u);
138        } else {
139#ifndef WINDOWS
140            // Silly non-Windows letting you receive after you told it no more
141            if (shutdown) {
142                MORDOR_TEST_ASSERT_EQUAL(conns.connect->receive(readBuf, 3), 0u);
143            } else
144#endif
145            {
146                MORDOR_TEST_ASSERT_EXCEPTION(conns.connect->receive(readBuf, 3), Exception);
147            }
148        }
149    }
150}
151
152MORDOR_UNITTEST(Socket, sendAfterShutdown)
153{
154    testShutdownException<BrokenPipeException>(true, true, false);
155}
156
157MORDOR_UNITTEST(Socket, receiveAfterShutdown)
158{
159    testShutdownException<BrokenPipeException>(false, true, false);
160}
161
162MORDOR_UNITTEST(Socket, sendAfterCloseOtherEnd)
163{
164#ifdef WINDOWS
165    testShutdownException<ConnectionAbortedException>(true, false, true);
166#else
167    try {
168        testShutdownException<BrokenPipeException>(true, false, true);
169        // Could also be ConnectionReset on BSDs
170    } catch (const ConnectionResetException&)
171    {}
172#ifdef OSX
173    // EPROTOTYPE may be thrown out on mac os 10
174    catch (const WrongProtocolTypeException&)
175    {}
176#endif
177#endif
178}
179
180MORDOR_UNITTEST(Socket, receiveAfterCloseOtherEnd)
181{
182    // Exception is not used; this is special cased in testShutdownException
183    testShutdownException<DummyException>(false, false, true);
184}
185
186MORDOR_UNITTEST(Socket, sendAfterShutdownOtherEnd)
187{
188#ifdef WINDOWS
189    testShutdownException<ConnectionAbortedException>(true, false, true);
190#elif defined(BSD)
191    // temporarily skip this case for further investigation
192    throw TestSkippedException();
193    // BSD lets you write to the socket, but it blocks, so we have to check
194    // for it blocking
195    testShutdownException<TimedOutException>(true, true, true);
196#else
197    testShutdownException<BrokenPipeException>(true, false, true);
198#endif
199}
200
201MORDOR_UNITTEST(Socket, receiveAfterShutdownOtherEnd)
202{
203    // Exception is not used; this is special cased in testShutdownException
204    testShutdownException<DummyException>(false, true, true);
205}
206
207static void testAddress(const char *addr, const char *expected)
208{
209    MORDOR_TEST_ASSERT_EQUAL(
210        boost::lexical_cast<std::string>(*IPAddress::create(addr, 80)),
211        expected);
212}
213
214MORDOR_UNITTEST(Address, formatIPv4Address)
215{
216    testAddress("127.0.0.1", "127.0.0.1:80");
217}
218
219MORDOR_UNITTEST(Address, formatIPv6Address)
220{
221    try {
222        testAddress("::", "[::]:80");
223        testAddress("::1", "[::1]:80");
224        testAddress("[2001:470:1f05:273:20c:29ff:feb3:5ddf]",
225            "[2001:470:1f05:273:20c:29ff:feb3:5ddf]:80");
226        testAddress("[2001:470::273:20c:0:0:5ddf]",
227            "[2001:470::273:20c:0:0:5ddf]:80");
228        testAddress("[2001:470:0:0:273:20c::5ddf]",
229            "[2001:470::273:20c:0:5ddf]:80");
230    } catch (std::invalid_argument &) {
231        throw TestSkippedException();
232    }
233}
234
235MORDOR_UNITTEST(Address, parseIPv4Address)
236{
237    MORDOR_TEST_ASSERT_EQUAL(boost::lexical_cast<std::string>(IPv4Address(0x7f000001, 80)),
238        "127.0.0.1:80");
239    MORDOR_TEST_ASSERT_EQUAL(boost::lexical_cast<std::string>(IPv4Address("127.0.0.1")),
240        "127.0.0.1:0");
241    MORDOR_TEST_ASSERT_EXCEPTION(IPv4Address("garbage"), std::invalid_argument);
242}
243
244MORDOR_UNITTEST(Address, parseIPv6Address)
245{
246    try {
247        MORDOR_TEST_ASSERT_EQUAL(boost::lexical_cast<std::string>(IPv6Address("::")),
248            "[::]:0");
249        MORDOR_TEST_ASSERT_EQUAL(boost::lexical_cast<std::string>(IPv6Address("::1", 443)),
250            "[::1]:443");
251        MORDOR_TEST_ASSERT_EQUAL(boost::lexical_cast<std::string>(
252            IPv6Address("2001:470::273:20c:0:0:5ddf")),
253            "[2001:470::273:20c:0:0:5ddf]:0");
254        MORDOR_TEST_ASSERT_EXCEPTION(IPv6Address("garbage"), std::invalid_argument);
255    } catch (OperationNotSupportedException &) {
256        throw TestSkippedException();
257    }
258}
259
260// Test the address prefix length is 32 (IPv4), 128 (IPv6)
261MORDOR_UNITTEST(Address, fullPrefixLength)
262{
263    IPv4Address ipv4("127.0.0.1");
264    MORDOR_TEST_ASSERT_EQUAL(boost::lexical_cast<std::string>(*ipv4.subnetMask(32)), "255.255.255.255:0");
265    MORDOR_TEST_ASSERT_EQUAL(boost::lexical_cast<std::string>(*ipv4.broadcastAddress(32)), "127.0.0.1:0");
266    MORDOR_TEST_ASSERT_EQUAL(boost::lexical_cast<std::string>(*ipv4.networkAddress(32)), "127.0.0.1:0");
267
268    IPv6Address ipv6("2001:470::273:20c:0:0:5ddf");
269    MORDOR_TEST_ASSERT_EQUAL(boost::lexical_cast<std::string>(*ipv6.subnetMask(128)),
270                             "[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:0");
271    MORDOR_TEST_ASSERT_EQUAL(boost::lexical_cast<std::string>(*ipv6.broadcastAddress(128)),
272                             "[2001:470::273:20c:0:0:5ddf]:0");
273    MORDOR_TEST_ASSERT_EQUAL(boost::lexical_cast<std::string>(*ipv6.networkAddress(128)),
274                             "[2001:470::273:20c:0:0:5ddf]:0");
275}
276
277static void cancelMe(Socket::ptr sock)
278{
279    sock->cancelAccept();
280}
281
282MORDOR_UNITTEST(Socket, cancelAccept)
283{
284    IOManager ioManager;
285    Connection conns = establishConn(ioManager);
286
287    // cancelMe will get run when this fiber yields because it would block
288    ioManager.schedule(boost::bind(&cancelMe, conns.listen));
289    MORDOR_TEST_ASSERT_EXCEPTION(conns.listen->accept(), OperationAbortedException);
290}
291
292MORDOR_UNITTEST(Socket, cancelSend)
293{
294    IOManager ioManager;
295    Connection conns = establishConn(ioManager);
296    ioManager.schedule(boost::bind(&acceptOne, boost::ref(conns)));
297    conns.connect->connect(conns.address);
298    ioManager.dispatch();
299
300    boost::scoped_array<char> array(new char[65536]);
301    memset(array.get(), 1, 65536);
302    struct iovec iov;
303    iov.iov_base = array.get();
304    iov.iov_len = 65536;
305    conns.connect->cancelSend();
306    MORDOR_TEST_ASSERT_EXCEPTION(while (conns.connect->send(iov.iov_base, 65536)) {}, OperationAbortedException);
307    MORDOR_TEST_ASSERT_EXCEPTION(conns.connect->send(&iov, 1), OperationAbortedException);
308}
309
310MORDOR_UNITTEST(Socket, cancelReceive)
311{
312    IOManager ioManager;
313    Connection conns = establishConn(ioManager);
314    ioManager.schedule(boost::bind(&acceptOne, boost::ref(conns)));
315    conns.connect->connect(conns.address);
316    ioManager.dispatch();
317
318    char buf[3];
319    memset(buf, 1, 3);
320    struct iovec iov;
321    iov.iov_base = buf;
322    iov.iov_len = 3;
323    conns.connect->cancelReceive();
324    MORDOR_TEST_ASSERT_EXCEPTION(conns.connect->receive(iov.iov_base, 3), OperationAbortedException);
325    MORDOR_TEST_ASSERT_EXCEPTION(conns.connect->receive(&iov, 1), OperationAbortedException);
326}
327
328MORDOR_UNITTEST(Socket, sendReceive)
329{
330    IOManager ioManager;
331    Connection conns = establishConn(ioManager);
332    ioManager.schedule(boost::bind(&acceptOne, boost::ref(conns)));
333    conns.connect->connect(conns.address);
334    ioManager.dispatch();
335
336    const char *sendbuf = "abcd";
337    char receivebuf[5];
338    memset(receivebuf, 0, 5);
339    MORDOR_TEST_ASSERT_EQUAL(conns.connect->send(sendbuf, 1), 1u);
340    MORDOR_TEST_ASSERT_EQUAL(conns.accept->receive(receivebuf, 1), 1u);
341    MORDOR_TEST_ASSERT_EQUAL(receivebuf[0], 'a');
342    receivebuf[0] = 0;
343    iovec iov[2];
344    iov[0].iov_base = (void *)&sendbuf[0];
345    iov[1].iov_base = (void *)&sendbuf[2];
346    iov[0].iov_len = 2;
347    iov[1].iov_len = 2;
348    MORDOR_TEST_ASSERT_EQUAL(conns.connect->send(iov, 2), 4u);
349    iov[0].iov_base = &receivebuf[0];
350    iov[1].iov_base = &receivebuf[2];
351    MORDOR_TEST_ASSERT_EQUAL(conns.accept->receive(iov, 2), 4u);
352    MORDOR_TEST_ASSERT_EQUAL(sendbuf, (const char *)receivebuf);
353}
354
355#ifndef WINDOWS
356MORDOR_UNITTEST(Socket, exceedIOVMAX)
357{
358    IOManager ioManager;
359    Connection conns = establishConn(ioManager);
360    ioManager.schedule(boost::bind(&acceptOne, boost::ref(conns)));
361    conns.connect->connect(conns.address);
362    ioManager.dispatch();
363
364    const char * buf = "abcd";
365    size_t n = IOV_MAX + 1, len = 4;
366    boost::shared_array<iovec> iovs(new iovec[n]);
367    for (size_t i = 0 ; i < n; ++i) {
368        iovs[i].iov_base = (void*)buf;
369        iovs[i].iov_len = len;
370    }
371    size_t rc;
372    try {
373        rc = conns.connect->send(iovs.get(), n);
374    } catch (...) {
375        MORDOR_NOTREACHED();
376    }
377    MORDOR_TEST_ASSERT(rc <= IOV_MAX * len);
378    MORDOR_TEST_ASSERT(rc >= 0);
379}
380#endif
381
382static void receiveFiber(Socket::ptr listen, size_t &sent, int &sequence)
383{
384    MORDOR_TEST_ASSERT_EQUAL(++sequence, 1);
385    Socket::ptr sock = listen->accept();
386    MORDOR_TEST_ASSERT_EQUAL(++sequence, 3);
387    char receivebuf[5];
388    memset(receivebuf, 0, 5);
389    Scheduler::yieldTo();
390    MORDOR_TEST_ASSERT_EQUAL(++sequence, 5);
391    MORDOR_TEST_ASSERT_EQUAL(sock->receive(receivebuf, 2), 2u);
392    MORDOR_TEST_ASSERT_EQUAL(++sequence, 7);
393    MORDOR_TEST_ASSERT_EQUAL((const char*)receivebuf, "ab");
394    memset(receivebuf, 0, 5);
395    iovec iov[2];
396    iov[0].iov_base = &receivebuf[0];
397    iov[1].iov_base = &receivebuf[2];
398    iov[0].iov_len = 2;
399    iov[1].iov_len = 2;
400    Scheduler::yieldTo();
401    MORDOR_TEST_ASSERT_EQUAL(++sequence, 9);
402    MORDOR_TEST_ASSERT_EQUAL(sock->receive(iov, 2), 4u);
403    MORDOR_TEST_ASSERT_EQUAL(++sequence, 11);
404    MORDOR_TEST_ASSERT_EQUAL((const char*)receivebuf, "abcd");
405
406    // Let the main fiber take control again until he "blocks"
407    Scheduler::yieldTo();
408    MORDOR_TEST_ASSERT_EQUAL(++sequence, 13);
409    boost::shared_array<char> receivebuf2;
410    if (sent > 0u) {
411        receivebuf2.reset(new char[sent]);
412        while (sent > 0u)
413            sent -= sock->receive(receivebuf2.get(), sent);
414    }
415    // Let the main fiber update sent
416    Scheduler::yieldTo();
417    MORDOR_TEST_ASSERT_EQUAL(++sequence, 15);
418
419    if (sent > 0u) {
420        receivebuf2.reset(new char[sent]);
421        while (sent > 0u)
422            sent -= sock->receive(receivebuf2.get(), sent);
423    }
424
425    // Let the main fiber take control again until he "blocks"
426    Scheduler::yieldTo();
427    MORDOR_TEST_ASSERT_EQUAL(++sequence, 17);
428    if (sent > 0u) {
429        receivebuf2.reset(new char[std::max<size_t>(sent, 3u)]);
430        iov[0].iov_base = &receivebuf2.get()[0];
431        iov[1].iov_base = &receivebuf2.get()[2];
432        while (sent > 0u) {
433            size_t iovs = 2;
434            if (sent > 2) {
435                iov[1].iov_len = (iov_len_t)std::max<size_t>(sent, 3u) - 2;
436            } else {
437                iov[0].iov_len = (iov_len_t)sent;
438                iovs = 1;
439            }
440            sent -= sock->receive(iov, iovs);
441        }
442    }
443    // Let the main fiber update sent
444    Scheduler::yieldTo();
445    MORDOR_TEST_ASSERT_EQUAL(++sequence, 19);
446
447    if (sent > 0u) {
448        receivebuf2.reset(new char[std::max<size_t>(sent, 3u)]);
449        iov[0].iov_base = &receivebuf2.get()[0];
450        iov[1].iov_base = &receivebuf2.get()[2];
451        iov[0].iov_len = 2;
452        iov[1].iov_len = (iov_len_t)std::max<size_t>(sent, 3u) - 2;
453        while (sent > 0u)
454            sent -= sock->receive(iov, 2);
455    }
456}
457
458// Disabled until frequently occuring deadlock on mac is investigated - SYNC-2070
459#ifndef OSX
460MORDOR_UNITTEST(Socket, sendReceiveForceAsync)
461{
462    IOManager ioManager;
463    Connection conns = establishConn(ioManager);
464    int sequence = 0;
465    size_t sent = 0;
466
467    Fiber::ptr otherfiber(new Fiber(boost::bind(&receiveFiber, conns.listen,
468        boost::ref(sent), boost::ref(sequence))));
469
470    ioManager.schedule(otherfiber);
471    // Wait for otherfiber to "block", and return control to us
472    Scheduler::yield();
473    MORDOR_TEST_ASSERT_EQUAL(++sequence, 2);
474    // Unblock him, then wait for him to block again
475    conns.connect->connect(conns.address);
476    ioManager.dispatch();
477    MORDOR_TEST_ASSERT_EQUAL(++sequence, 4);
478    ioManager.schedule(otherfiber);
479    Scheduler::yield();
480    MORDOR_TEST_ASSERT_EQUAL(++sequence, 6);
481
482    // Again
483    const char *sendbuf = "abcd";
484    MORDOR_TEST_ASSERT_EQUAL(conns.connect->send(sendbuf, 2), 2u);
485    ioManager.dispatch();
486    MORDOR_TEST_ASSERT_EQUAL(++sequence, 8);
487    ioManager.schedule(otherfiber);
488    Scheduler::yield();
489    MORDOR_TEST_ASSERT_EQUAL(++sequence, 10);
490
491    // And again
492    iovec iov[2];
493    iov[0].iov_base = (void *)&sendbuf[0];
494    iov[1].iov_base = (void *)&sendbuf[2];
495    iov[0].iov_len = 2;
496    iov[1].iov_len = 2;
497    MORDOR_TEST_ASSERT_EQUAL(conns.connect->send(iov, 2), 4u);
498    ioManager.dispatch();
499    MORDOR_TEST_ASSERT_EQUAL(++sequence, 12);
500
501    char sendbuf2[65536];
502    memset(sendbuf2, 1, 65536);
503    // Keep sending until the other fiber said we blocked
504    ioManager.schedule(otherfiber);
505    while (true) {
506        sent += conns.connect->send(sendbuf2, 65536);
507        if (sequence == 13)
508            break;
509    }
510    MORDOR_TEST_ASSERT_EQUAL(++sequence, 14);
511    ioManager.schedule(otherfiber);
512    ioManager.dispatch();
513    MORDOR_TEST_ASSERT_EQUAL(++sequence, 16);
514    MORDOR_TEST_ASSERT_EQUAL(sent, 0u);
515
516    iov[0].iov_base = &sendbuf2[0];
517    iov[1].iov_base = &sendbuf2[2];
518    iov[1].iov_len = 65536 - 2;
519    // Keep sending until the other fiber said we blocked
520    ioManager.schedule(otherfiber);
521    while (true) {
522        sent += conns.connect->send(iov, 2);
523        if (sequence == 17)
524            break;
525    }
526    MORDOR_TEST_ASSERT_EQUAL(++sequence, 18);
527    ioManager.schedule(otherfiber);
528    ioManager.dispatch();
529    MORDOR_TEST_ASSERT_EQUAL(++sequence, 20);
530}
531#endif
532
533static void closed(bool &remoteClosed)
534{
535    remoteClosed = true;
536}
537
538MORDOR_UNITTEST(Socket, eventOnRemoteShutdown)
539{
540    IOManager ioManager;
541    Connection conns = establishConn(ioManager);
542    ioManager.schedule(boost::bind(&acceptOne, boost::ref(conns)));
543    conns.connect->connect(conns.address);
544    ioManager.dispatch();
545
546    bool remoteClosed = false;
547    conns.accept->onRemoteClose(boost::bind(&closed, boost::ref(remoteClosed)));
548    conns.connect->shutdown();
549    ioManager.dispatch();
550    MORDOR_TEST_ASSERT(remoteClosed);
551}
552
553MORDOR_UNITTEST(Socket, eventOnRemoteReset)
554{
555    IOManager ioManager;
556    Connection conns = establishConn(ioManager);
557    ioManager.schedule(boost::bind(&acceptOne, boost::ref(conns)));
558    conns.connect->connect(conns.address);
559    ioManager.dispatch();
560
561    bool remoteClosed = false;
562    conns.accept->onRemoteClose(boost::bind(&closed, boost::ref(remoteClosed)));
563    conns.connect.reset();
564    ioManager.dispatch();
565    MORDOR_TEST_ASSERT(remoteClosed);
566}