diff options
Diffstat (limited to 'indra/llmessage')
| -rw-r--r-- | indra/llmessage/lliosocket.cpp | 26 | ||||
| -rw-r--r-- | indra/llmessage/lliosocket.h | 11 | ||||
| -rw-r--r-- | indra/llmessage/message_prehash.cpp | 3 | ||||
| -rw-r--r-- | indra/llmessage/message_prehash.h | 3 | ||||
| -rw-r--r-- | indra/llmessage/tests/test_llsdmessage_peer.py | 9 | ||||
| -rw-r--r-- | indra/llmessage/tests/testrunner.py | 100 | 
6 files changed, 141 insertions, 11 deletions
diff --git a/indra/llmessage/lliosocket.cpp b/indra/llmessage/lliosocket.cpp index ca84fa8bb8..8c752fbe30 100644 --- a/indra/llmessage/lliosocket.cpp +++ b/indra/llmessage/lliosocket.cpp @@ -191,7 +191,7 @@ LLSocket::ptr_t LLSocket::create(apr_pool_t* pool, EType type, U16 port)  		port = PORT_EPHEMERAL;  	}  	rv->mPort = port; -	rv->setOptions(); +	rv->setNonBlocking();  	return rv;  } @@ -206,7 +206,7 @@ LLSocket::ptr_t LLSocket::create(apr_socket_t* socket, apr_pool_t* pool)  	}  	rv = ptr_t(new LLSocket(socket, pool));  	rv->mPort = PORT_EPHEMERAL; -	rv->setOptions(); +	rv->setNonBlocking();  	return rv;  } @@ -227,10 +227,10 @@ bool LLSocket::blockingConnect(const LLHost& host)  	{  		return false;  	} -	apr_socket_timeout_set(mSocket, 1000); +	setBlocking(1000);  	ll_debug_socket("Blocking connect", mSocket);  	if(ll_apr_warn_status(apr_socket_connect(mSocket, sa))) return false; -	setOptions(); +	setNonBlocking();  	return true;  } @@ -258,11 +258,27 @@ LLSocket::~LLSocket()  	}  } -void LLSocket::setOptions() +// See http://dev.ariel-networks.com/apr/apr-tutorial/html/apr-tutorial-13.html#ss13.4 +// for an explanation of how to get non-blocking sockets and timeouts with +// consistent behavior across platforms. + +void LLSocket::setBlocking(S32 timeout) +{ +	LLMemType m1(LLMemType::MTYPE_IO_TCP); +	// set up the socket options +	ll_apr_warn_status(apr_socket_timeout_set(mSocket, timeout)); +	ll_apr_warn_status(apr_socket_opt_set(mSocket, APR_SO_NONBLOCK, 0)); +	ll_apr_warn_status(apr_socket_opt_set(mSocket, APR_SO_SNDBUF, LL_SEND_BUFFER_SIZE)); +	ll_apr_warn_status(apr_socket_opt_set(mSocket, APR_SO_RCVBUF, LL_RECV_BUFFER_SIZE)); + +} + +void LLSocket::setNonBlocking()  {  	LLMemType m1(LLMemType::MTYPE_IO_TCP);  	// set up the socket options  	ll_apr_warn_status(apr_socket_timeout_set(mSocket, 0)); +	ll_apr_warn_status(apr_socket_opt_set(mSocket, APR_SO_NONBLOCK, 1));  	ll_apr_warn_status(apr_socket_opt_set(mSocket, APR_SO_SNDBUF, LL_SEND_BUFFER_SIZE));  	ll_apr_warn_status(apr_socket_opt_set(mSocket, APR_SO_RCVBUF, LL_RECV_BUFFER_SIZE)); diff --git a/indra/llmessage/lliosocket.h b/indra/llmessage/lliosocket.h index 6806e5084a..e0f6c1e34d 100644 --- a/indra/llmessage/lliosocket.h +++ b/indra/llmessage/lliosocket.h @@ -153,9 +153,16 @@ protected:  	LLSocket(apr_socket_t* socket, apr_pool_t* pool);  	/**  -	 * @brief Set default socket options. +	 * @brief Set default socket options, with SO_NONBLOCK = 0 and a timeout in us. +	 * @param timeout Number of microseconds to wait on this socket. Any +	 * negative number means block-forever. TIMEOUT OF 0 IS NON-PORTABLE.  	 */ -	void setOptions(); +	void setBlocking(S32 timeout); + +	/** +	 * @brief Set default socket options, with SO_NONBLOCK = 1 and timeout = 0. +	 */ +	void setNonBlocking();  public:  	/**  diff --git a/indra/llmessage/message_prehash.cpp b/indra/llmessage/message_prehash.cpp index 6133f50637..e71fb96540 100644 --- a/indra/llmessage/message_prehash.cpp +++ b/indra/llmessage/message_prehash.cpp @@ -1376,3 +1376,6 @@ char const* const _PREHASH_VCoord = LLMessageStringTable::getInstance()->getStri  char const* const _PREHASH_FaceIndex = LLMessageStringTable::getInstance()->getString("FaceIndex");  char const* const _PREHASH_StatusData = LLMessageStringTable::getInstance()->getString("StatusData");  char const* const _PREHASH_ProductSKU = LLMessageStringTable::getInstance()->getString("ProductSKU"); +char const* const _PREHASH_SeeAVs = LLMessageStringTable::getInstance()->getString("SeeAVs"); +char const* const _PREHASH_AnyAVSounds = LLMessageStringTable::getInstance()->getString("AnyAVSounds"); +char const* const _PREHASH_GroupAVSounds = LLMessageStringTable::getInstance()->getString("GroupAVSounds"); diff --git a/indra/llmessage/message_prehash.h b/indra/llmessage/message_prehash.h index f94ee1ed22..dd2c2dbd64 100644 --- a/indra/llmessage/message_prehash.h +++ b/indra/llmessage/message_prehash.h @@ -1376,4 +1376,7 @@ extern char const* const _PREHASH_VCoord;  extern char const* const _PREHASH_FaceIndex;  extern char const* const _PREHASH_StatusData;  extern char const* const _PREHASH_ProductSKU; +extern char const* const _PREHASH_SeeAVs; +extern char const* const _PREHASH_AnyAVSounds; +extern char const* const _PREHASH_GroupAVSounds;  #endif diff --git a/indra/llmessage/tests/test_llsdmessage_peer.py b/indra/llmessage/tests/test_llsdmessage_peer.py index 9886d49ccc..22edd9dad8 100644 --- a/indra/llmessage/tests/test_llsdmessage_peer.py +++ b/indra/llmessage/tests/test_llsdmessage_peer.py @@ -124,14 +124,19 @@ class TestHTTPRequestHandler(BaseHTTPRequestHandler):              # Suppress error output as well              pass +class Server(HTTPServer): +    # This pernicious flag is on by default in HTTPServer. But proper +    # operation of freeport() absolutely depends on it being off. +    allow_reuse_address = False +  if __name__ == "__main__": -    # Instantiate an HTTPServer(TestHTTPRequestHandler) on the first free port +    # Instantiate a Server(TestHTTPRequestHandler) on the first free port      # in the specified port range. Doing this inline is better than in a      # daemon thread: if it blows up here, we'll get a traceback. If it blew up      # in some other thread, the traceback would get eaten and we'd run the      # subject test program anyway.      httpd, port = freeport(xrange(8000, 8020), -                           lambda port: HTTPServer(('127.0.0.1', port), TestHTTPRequestHandler)) +                           lambda port: Server(('127.0.0.1', port), TestHTTPRequestHandler))      # Pass the selected port number to the subject test program via the      # environment. We don't want to impose requirements on the test program's      # command-line parsing -- and anyway, for C++ integration tests, that's diff --git a/indra/llmessage/tests/testrunner.py b/indra/llmessage/tests/testrunner.py index f329ec2a0e..f2c841532a 100644 --- a/indra/llmessage/tests/testrunner.py +++ b/indra/llmessage/tests/testrunner.py @@ -27,6 +27,8 @@ Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA  $/LicenseInfo$  """ +from __future__ import with_statement +  import os  import sys  import re @@ -79,9 +81,14 @@ def freeport(portlist, expr):      Example: +    class Server(HTTPServer): +        # If you use BaseHTTPServer.HTTPServer, turning off this flag is +        # essential for proper operation of freeport()! +        allow_reuse_address = False +    # ...      server, port = freeport(xrange(8000, 8010), -                            lambda port: HTTPServer(("localhost", port), -                                                    MyRequestHandler)) +                            lambda port: Server(("localhost", port), +                                                MyRequestHandler))      # pass 'port' to client code      # call server.serve_forever()      """ @@ -164,3 +171,92 @@ def run(*args, **kwds):      rc = os.spawnv(os.P_WAIT, args[0], args)      debug("%s returned %s", args[0], rc)      return rc + +# **************************************************************************** +#   test code -- manual at this point, see SWAT-564 +# **************************************************************************** +def test_freeport(): +    # ------------------------------- Helpers -------------------------------- +    from contextlib import contextmanager +    # helper Context Manager for expecting an exception +    # with exc(SomeError): +    #     raise SomeError() +    # raises AssertionError otherwise. +    @contextmanager +    def exc(exception_class, *args): +        try: +            yield +        except exception_class, err: +            for i, expected_arg in enumerate(args): +                assert expected_arg == err.args[i], \ +                       "Raised %s, but args[%s] is %r instead of %r" % \ +                       (err.__class__.__name__, i, err.args[i], expected_arg) +            print "Caught expected exception %s(%s)" % \ +                  (err.__class__.__name__, ', '.join(repr(arg) for arg in err.args)) +        else: +            assert False, "Failed to raise " + exception_class.__class__.__name__ + +    # helper to raise specified exception +    def raiser(exception): +        raise exception + +    # the usual +    def assert_equals(a, b): +        assert a == b, "%r != %r" % (a, b) + +    # ------------------------ Sanity check the above ------------------------ +    class SomeError(Exception): pass +    # Without extra args, accept any err.args value +    with exc(SomeError): +        raiser(SomeError("abc")) +    # With extra args, accept only the specified value +    with exc(SomeError, "abc"): +        raiser(SomeError("abc")) +    with exc(AssertionError): +        with exc(SomeError, "abc"): +            raiser(SomeError("def")) +    with exc(AssertionError): +        with exc(socket.error, errno.EADDRINUSE): +            raiser(socket.error(errno.ECONNREFUSED, 'Connection refused')) + +    # ----------- freeport() without engaging socket functionality ----------- +    # If portlist is empty, freeport() raises StopIteration. +    with exc(StopIteration): +        freeport([], None) + +    assert_equals(freeport([17], str), ("17", 17)) + +    # This is the magic exception that should prompt us to retry +    inuse = socket.error(errno.EADDRINUSE, 'Address already in use') +    # Get the iterator to our ports list so we can check later if we've used all +    ports = iter(xrange(5)) +    with exc(socket.error, errno.EADDRINUSE): +        freeport(ports, lambda port: raiser(inuse)) +    # did we entirely exhaust 'ports'? +    with exc(StopIteration): +        ports.next() + +    ports = iter(xrange(2)) +    # Any exception but EADDRINUSE should quit immediately +    with exc(SomeError): +        freeport(ports, lambda port: raiser(SomeError())) +    assert_equals(ports.next(), 1) + +    # ----------- freeport() with platform-dependent socket stuff ------------ +    # This is what we should've had unit tests to begin with (see CHOP-661). +    def newbind(port): +        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +        sock.bind(('127.0.0.1', port)) +        return sock + +    bound0, port0 = freeport(xrange(7777, 7780), newbind) +    assert_equals(port0, 7777) +    bound1, port1 = freeport(xrange(7777, 7780), newbind) +    assert_equals(port1, 7778) +    bound2, port2 = freeport(xrange(7777, 7780), newbind) +    assert_equals(port2, 7779) +    with exc(socket.error, errno.EADDRINUSE): +        bound3, port3 = freeport(xrange(7777, 7780), newbind) + +if __name__ == "__main__": +    test_freeport()  | 
