diff options
Diffstat (limited to 'indra/llmessage')
-rw-r--r-- | indra/llmessage/llassetstorage.cpp | 2 | ||||
-rw-r--r-- | indra/llmessage/llcurl.cpp | 195 | ||||
-rw-r--r-- | indra/llmessage/llcurl.h | 3 | ||||
-rw-r--r-- | indra/llmessage/lldatapacker.h | 5 | ||||
-rw-r--r-- | indra/llmessage/llhttpassetstorage.cpp | 8 | ||||
-rw-r--r-- | indra/llmessage/llsdmessagebuilder.cpp | 1 | ||||
-rw-r--r-- | indra/llmessage/lltemplatemessagebuilder.cpp | 1 | ||||
-rw-r--r-- | indra/llmessage/lltemplatemessagereader.cpp | 1 | ||||
-rw-r--r-- | indra/llmessage/lltransfermanager.cpp | 2 | ||||
-rw-r--r-- | indra/llmessage/lltransfersourceasset.cpp | 1 | ||||
-rw-r--r-- | indra/llmessage/tests/commtest.h | 20 | ||||
-rw-r--r-- | indra/llmessage/tests/llsdmessage_test.cpp | 1 | ||||
-rw-r--r-- | indra/llmessage/tests/test_llsdmessage_peer.py | 26 | ||||
-rw-r--r-- | indra/llmessage/tests/testrunner.py | 81 |
14 files changed, 308 insertions, 39 deletions
diff --git a/indra/llmessage/llassetstorage.cpp b/indra/llmessage/llassetstorage.cpp index 69d092de76..31cdb1219b 100644 --- a/indra/llmessage/llassetstorage.cpp +++ b/indra/llmessage/llassetstorage.cpp @@ -561,7 +561,7 @@ void LLAssetStorage::_queueDataRequest(const LLUUID& uuid, LLAssetType::EType at tpvf.setAsset(uuid, atype); tpvf.setCallback(downloadCompleteCallback, req); - llinfos << "Starting transfer for " << uuid << llendl; + //llinfos << "Starting transfer for " << uuid << llendl; LLTransferTargetChannel *ttcp = gTransferManager.getTargetChannel(mUpstreamHost, LLTCT_ASSET); ttcp->requestTransfer(spa, tpvf, 100.f + (is_priority ? 1.f : 0.f)); } diff --git a/indra/llmessage/llcurl.cpp b/indra/llmessage/llcurl.cpp index 9b3b24c312..7c8b7e3584 100644 --- a/indra/llmessage/llcurl.cpp +++ b/indra/llmessage/llcurl.cpp @@ -49,6 +49,7 @@ #include "llstl.h" #include "llsdserialize.h" #include "llthread.h" +#include "lltimer.h" ////////////////////////////////////////////////////////////////////////////// /* @@ -84,6 +85,26 @@ std::vector<LLMutex*> LLCurl::sSSLMutex; std::string LLCurl::sCAPath; std::string LLCurl::sCAFile; +void check_curl_code(CURLcode code) +{ + if (code != CURLE_OK) + { + // linux appears to throw a curl error once per session for a bad initialization + // at a pretty random time (when enabling cookies). + llinfos << "curl error detected: " << curl_easy_strerror(code) << llendl; + } +} + +void check_curl_multi_code(CURLMcode code) +{ + if (code != CURLM_OK) + { + // linux appears to throw a curl error once per session for a bad initialization + // at a pretty random time (when enabling cookies). + llinfos << "curl multi error detected: " << curl_multi_strerror(code) << llendl; + } +} + //static void LLCurl::setCAPath(const std::string& path) { @@ -234,7 +255,12 @@ public: void resetState(); + static CURL* allocEasyHandle(); + static void releaseEasyHandle(CURL* handle); + private: + friend class LLCurl; + CURL* mCurlEasyHandle; struct curl_slist* mHeaders; @@ -249,8 +275,62 @@ private: std::vector<char*> mStrings; ResponderPtr mResponder; + + static std::set<CURL*> sFreeHandles; + static std::set<CURL*> sActiveHandles; + static LLMutex* sHandleMutex; }; +std::set<CURL*> LLCurl::Easy::sFreeHandles; +std::set<CURL*> LLCurl::Easy::sActiveHandles; +LLMutex* LLCurl::Easy::sHandleMutex = NULL; + + +//static +CURL* LLCurl::Easy::allocEasyHandle() +{ + CURL* ret = NULL; + LLMutexLock lock(sHandleMutex); + if (sFreeHandles.empty()) + { + ret = curl_easy_init(); + } + else + { + ret = *(sFreeHandles.begin()); + sFreeHandles.erase(ret); + curl_easy_reset(ret); + } + + if (ret) + { + sActiveHandles.insert(ret); + } + + return ret; +} + +//static +void LLCurl::Easy::releaseEasyHandle(CURL* handle) +{ + if (!handle) + { + llerrs << "handle cannot be NULL!" << llendl; + } + + LLMutexLock lock(sHandleMutex); + + if (sActiveHandles.find(handle) != sActiveHandles.end()) + { + sActiveHandles.erase(handle); + sFreeHandles.insert(handle); + } + else + { + llerrs << "Invalid handle." << llendl; + } +} + LLCurl::Easy::Easy() : mHeaders(NULL), mCurlEasyHandle(NULL) @@ -261,25 +341,28 @@ LLCurl::Easy::Easy() LLCurl::Easy* LLCurl::Easy::getEasy() { Easy* easy = new Easy(); - easy->mCurlEasyHandle = curl_easy_init(); + easy->mCurlEasyHandle = allocEasyHandle(); + if (!easy->mCurlEasyHandle) { // this can happen if we have too many open files (fails in c-ares/ares_init.c) - llwarns << "curl_multi_init() returned NULL! Easy handles: " << gCurlEasyCount << " Multi handles: " << gCurlMultiCount << llendl; + llwarns << "allocEasyHandle() returned NULL! Easy handles: " << gCurlEasyCount << " Multi handles: " << gCurlMultiCount << llendl; delete easy; return NULL; } - // set no DMS caching as default for all easy handles. This prevents them adopting a + // set no DNS caching as default for all easy handles. This prevents them adopting a // multi handles cache if they are added to one. - curl_easy_setopt(easy->mCurlEasyHandle, CURLOPT_DNS_CACHE_TIMEOUT, 0); + CURLcode result = curl_easy_setopt(easy->mCurlEasyHandle, CURLOPT_DNS_CACHE_TIMEOUT, 0); + check_curl_code(result); + ++gCurlEasyCount; return easy; } LLCurl::Easy::~Easy() { - curl_easy_cleanup(mCurlEasyHandle); + releaseEasyHandle(mCurlEasyHandle); --gCurlEasyCount; curl_slist_free_all(mHeaders); for_each(mStrings.begin(), mStrings.end(), DeletePointerArray()); @@ -338,9 +421,9 @@ void LLCurl::Easy::setHeaders() void LLCurl::Easy::getTransferInfo(LLCurl::TransferInfo* info) { - curl_easy_getinfo(mCurlEasyHandle, CURLINFO_SIZE_DOWNLOAD, &info->mSizeDownload); - curl_easy_getinfo(mCurlEasyHandle, CURLINFO_TOTAL_TIME, &info->mTotalTime); - curl_easy_getinfo(mCurlEasyHandle, CURLINFO_SPEED_DOWNLOAD, &info->mSpeedDownload); + check_curl_code(curl_easy_getinfo(mCurlEasyHandle, CURLINFO_SIZE_DOWNLOAD, &info->mSizeDownload)); + check_curl_code(curl_easy_getinfo(mCurlEasyHandle, CURLINFO_TOTAL_TIME, &info->mTotalTime)); + check_curl_code(curl_easy_getinfo(mCurlEasyHandle, CURLINFO_SPEED_DOWNLOAD, &info->mSpeedDownload)); } U32 LLCurl::Easy::report(CURLcode code) @@ -350,13 +433,14 @@ U32 LLCurl::Easy::report(CURLcode code) if (code == CURLE_OK) { - curl_easy_getinfo(mCurlEasyHandle, CURLINFO_RESPONSE_CODE, &responseCode); + check_curl_code(curl_easy_getinfo(mCurlEasyHandle, CURLINFO_RESPONSE_CODE, &responseCode)); //*TODO: get reason from first line of mHeaderOutput } else { responseCode = 499; responseReason = strerror(code) + " : " + mErrorBuffer; + setopt(CURLOPT_FRESH_CONNECT, TRUE); } if (mResponder) @@ -372,17 +456,20 @@ U32 LLCurl::Easy::report(CURLcode code) // Note: these all assume the caller tracks the value (i.e. keeps it persistant) void LLCurl::Easy::setopt(CURLoption option, S32 value) { - curl_easy_setopt(mCurlEasyHandle, option, value); + CURLcode result = curl_easy_setopt(mCurlEasyHandle, option, value); + check_curl_code(result); } void LLCurl::Easy::setopt(CURLoption option, void* value) { - curl_easy_setopt(mCurlEasyHandle, option, value); + CURLcode result = curl_easy_setopt(mCurlEasyHandle, option, value); + check_curl_code(result); } void LLCurl::Easy::setopt(CURLoption option, char* value) { - curl_easy_setopt(mCurlEasyHandle, option, value); + CURLcode result = curl_easy_setopt(mCurlEasyHandle, option, value); + check_curl_code(result); } // Note: this copies the string so that the caller does not have to keep it around @@ -391,7 +478,8 @@ void LLCurl::Easy::setoptString(CURLoption option, const std::string& value) char* tstring = new char[value.length()+1]; strcpy(tstring, value.c_str()); mStrings.push_back(tstring); - curl_easy_setopt(mCurlEasyHandle, option, tstring); + CURLcode result = curl_easy_setopt(mCurlEasyHandle, option, tstring); + check_curl_code(result); } void LLCurl::Easy::slist_append(const char* str) @@ -443,7 +531,7 @@ void LLCurl::Easy::prepRequest(const std::string& url, if (post) setoptString(CURLOPT_ENCODING, ""); -// setopt(CURLOPT_VERBOSE, 1); // usefull for debugging + //setopt(CURLOPT_VERBOSE, 1); // usefull for debugging setopt(CURLOPT_NOSIGNAL, 1); mOutput.reset(new LLBufferArray); @@ -467,6 +555,9 @@ void LLCurl::Easy::prepRequest(const std::string& url, setCA(); setopt(CURLOPT_SSL_VERIFYPEER, true); + + //don't verify host name so urls with scrubbed host names will work (improves DNS performance) + setopt(CURLOPT_SSL_VERIFYHOST, 0); setopt(CURLOPT_TIMEOUT, CURL_REQUEST_TIMEOUT); setoptString(CURLOPT_URL, url); @@ -532,6 +623,7 @@ LLCurl::Multi::Multi() llwarns << "curl_multi_init() returned NULL! Easy handles: " << gCurlEasyCount << " Multi handles: " << gCurlMultiCount << llendl; mCurlMultiHandle = curl_multi_init(); } + llassert_always(mCurlMultiHandle); ++gCurlMultiCount; } @@ -543,7 +635,7 @@ LLCurl::Multi::~Multi() iter != mEasyActiveList.end(); ++iter) { Easy* easy = *iter; - curl_multi_remove_handle(mCurlMultiHandle, easy->getCurlHandle()); + check_curl_multi_code(curl_multi_remove_handle(mCurlMultiHandle, easy->getCurlHandle())); delete easy; } mEasyActiveList.clear(); @@ -553,7 +645,7 @@ LLCurl::Multi::~Multi() for_each(mEasyFreeList.begin(), mEasyFreeList.end(), DeletePointer()); mEasyFreeList.clear(); - curl_multi_cleanup(mCurlMultiHandle); + check_curl_multi_code(curl_multi_cleanup(mCurlMultiHandle)); --gCurlMultiCount; } @@ -574,8 +666,10 @@ S32 LLCurl::Multi::perform() CURLMcode code = curl_multi_perform(mCurlMultiHandle, &q); if (CURLM_CALL_MULTI_PERFORM != code || q == 0) { + check_curl_multi_code(code); break; } + } mQueued = q; return q; @@ -642,11 +736,12 @@ LLCurl::Easy* LLCurl::Multi::allocEasy() bool LLCurl::Multi::addEasy(Easy* easy) { CURLMcode mcode = curl_multi_add_handle(mCurlMultiHandle, easy->getCurlHandle()); - if (mcode != CURLM_OK) - { - llwarns << "Curl Error: " << curl_multi_strerror(mcode) << llendl; - return false; - } + check_curl_multi_code(mcode); + //if (mcode != CURLM_OK) + //{ + // llwarns << "Curl Error: " << curl_multi_strerror(mcode) << llendl; + // return false; + //} return true; } @@ -667,7 +762,7 @@ void LLCurl::Multi::easyFree(Easy* easy) void LLCurl::Multi::removeEasy(Easy* easy) { - curl_multi_remove_handle(mCurlMultiHandle, easy->getCurlHandle()); + check_curl_multi_code(curl_multi_remove_handle(mCurlMultiHandle, easy->getCurlHandle())); easyFree(easy); } @@ -686,6 +781,7 @@ LLCurlRequest::LLCurlRequest() : mActiveRequestCount(0) { mThreadID = LLThread::currentID(); + mProcessing = FALSE; } LLCurlRequest::~LLCurlRequest() @@ -720,6 +816,11 @@ LLCurl::Easy* LLCurlRequest::allocEasy() bool LLCurlRequest::addEasy(LLCurl::Easy* easy) { llassert_always(mActiveMulti); + + if (mProcessing) + { + llerrs << "Posting to a LLCurlRequest instance from within a responder is not allowed (causes DNS timeouts)." << llendl; + } bool res = mActiveMulti->addEasy(easy); return res; } @@ -777,12 +878,41 @@ bool LLCurlRequest::post(const std::string& url, bool res = addEasy(easy); return res; } + +bool LLCurlRequest::post(const std::string& url, + const headers_t& headers, + const std::string& data, + LLCurl::ResponderPtr responder) +{ + LLCurl::Easy* easy = allocEasy(); + if (!easy) + { + return false; + } + easy->prepRequest(url, headers, responder); + + easy->getInput().write(data.data(), data.size()); + S32 bytes = easy->getInput().str().length(); + easy->setopt(CURLOPT_POST, 1); + easy->setopt(CURLOPT_POSTFIELDS, (void*)NULL); + easy->setopt(CURLOPT_POSTFIELDSIZE, bytes); + + easy->slist_append("Content-Type: application/octet-stream"); + easy->setHeaders(); + + lldebugs << "POSTING: " << bytes << " bytes." << llendl; + bool res = addEasy(easy); + return res; +} + // Note: call once per frame S32 LLCurlRequest::process() { llassert_always(mThreadID == LLThread::currentID()); S32 res = 0; + + mProcessing = TRUE; for (curlmulti_set_t::iterator iter = mMultiSet.begin(); iter != mMultiSet.end(); ) { @@ -796,6 +926,7 @@ S32 LLCurlRequest::process() delete multi; } } + mProcessing = FALSE; return res; } @@ -1025,8 +1156,12 @@ void LLCurl::initClass() // Do not change this "unless you are familiar with and mean to control // internal operations of libcurl" // - http://curl.haxx.se/libcurl/c/curl_global_init.html - curl_global_init(CURL_GLOBAL_ALL); + CURLcode code = curl_global_init(CURL_GLOBAL_ALL); + + check_curl_code(code); + Easy::sHandleMutex = new LLMutex(NULL); + #if SAFE_SSL S32 mutex_count = CRYPTO_num_locks(); for (S32 i=0; i<mutex_count; i++) @@ -1044,7 +1179,19 @@ void LLCurl::cleanupClass() CRYPTO_set_locking_callback(NULL); for_each(sSSLMutex.begin(), sSSLMutex.end(), DeletePointer()); #endif - curl_global_cleanup(); + + delete Easy::sHandleMutex; + Easy::sHandleMutex = NULL; + + for (std::set<CURL*>::iterator iter = Easy::sFreeHandles.begin(); iter != Easy::sFreeHandles.end(); ++iter) + { + CURL* curl = *iter; + curl_easy_cleanup(curl); + } + + Easy::sFreeHandles.clear(); + + llassert(Easy::sActiveHandles.empty()); } const unsigned int LLCurl::MAX_REDIRECTS = 5; diff --git a/indra/llmessage/llcurl.h b/indra/llmessage/llcurl.h index 64dadd6640..4ce3fa1078 100644 --- a/indra/llmessage/llcurl.h +++ b/indra/llmessage/llcurl.h @@ -202,6 +202,8 @@ public: void get(const std::string& url, LLCurl::ResponderPtr responder); bool getByteRange(const std::string& url, const headers_t& headers, S32 offset, S32 length, LLCurl::ResponderPtr responder); bool post(const std::string& url, const headers_t& headers, const LLSD& data, LLCurl::ResponderPtr responder); + bool post(const std::string& url, const headers_t& headers, const std::string& data, LLCurl::ResponderPtr responder); + S32 process(); S32 getQueued(); @@ -215,6 +217,7 @@ private: curlmulti_set_t mMultiSet; LLCurl::Multi* mActiveMulti; S32 mActiveRequestCount; + BOOL mProcessing; U32 mThreadID; // debug }; diff --git a/indra/llmessage/lldatapacker.h b/indra/llmessage/lldatapacker.h index dd9c4eaa38..b0a638c16e 100644 --- a/indra/llmessage/lldatapacker.h +++ b/indra/llmessage/lldatapacker.h @@ -168,10 +168,15 @@ public: S32 getCurrentSize() const { return (S32)(mCurBufferp - mBufferp); } S32 getBufferSize() const { return mBufferSize; } + const U8* getBuffer() const { return mBufferp; } void reset() { mCurBufferp = mBufferp; mWriteEnabled = (mCurBufferp != NULL); } void freeBuffer() { delete [] mBufferp; mBufferp = mCurBufferp = NULL; mBufferSize = 0; mWriteEnabled = FALSE; } void assignBuffer(U8 *bufferp, S32 size) { + if(mBufferp && mBufferp != bufferp) + { + freeBuffer() ; + } mBufferp = bufferp; mCurBufferp = bufferp; mBufferSize = size; diff --git a/indra/llmessage/llhttpassetstorage.cpp b/indra/llmessage/llhttpassetstorage.cpp index 9ea2ff4153..5a38b7fd9f 100644 --- a/indra/llmessage/llhttpassetstorage.cpp +++ b/indra/llmessage/llhttpassetstorage.cpp @@ -174,8 +174,8 @@ LLSD LLHTTPAssetRequest::getFullDetails() const double curl_total_time = -1.0f; double curl_size_upload = -1.0f; double curl_size_download = -1.0f; - long curl_content_length_upload = -1; - long curl_content_length_download = -1; + double curl_content_length_upload = -1.0f; + double curl_content_length_download = -1.0f; long curl_request_size = -1; const char* curl_content_type = NULL; @@ -194,8 +194,8 @@ LLSD LLHTTPAssetRequest::getFullDetails() const sd["curl_total_time"] = curl_total_time; sd["curl_size_upload"] = curl_size_upload; sd["curl_size_download"] = curl_size_download; - sd["curl_content_length_upload"] = (int) curl_content_length_upload; - sd["curl_content_length_download"] = (int) curl_content_length_download; + sd["curl_content_length_upload"] = curl_content_length_upload; + sd["curl_content_length_download"] = curl_content_length_download; sd["curl_request_size"] = (int) curl_request_size; if (curl_content_type) { diff --git a/indra/llmessage/llsdmessagebuilder.cpp b/indra/llmessage/llsdmessagebuilder.cpp index 42c179782f..2698a271ee 100644 --- a/indra/llmessage/llsdmessagebuilder.cpp +++ b/indra/llmessage/llsdmessagebuilder.cpp @@ -29,6 +29,7 @@ #include "llsdmessagebuilder.h" #include "llmessagetemplate.h" +#include "llmath.h" #include "llquaternion.h" #include "llsdutil.h" #include "llsdutil_math.h" diff --git a/indra/llmessage/lltemplatemessagebuilder.cpp b/indra/llmessage/lltemplatemessagebuilder.cpp index 6611d704e6..9e8eb48460 100644 --- a/indra/llmessage/lltemplatemessagebuilder.cpp +++ b/indra/llmessage/lltemplatemessagebuilder.cpp @@ -29,6 +29,7 @@ #include "lltemplatemessagebuilder.h" #include "llmessagetemplate.h" +#include "llmath.h" #include "llquaternion.h" #include "u64.h" #include "v3dmath.h" diff --git a/indra/llmessage/lltemplatemessagereader.cpp b/indra/llmessage/lltemplatemessagereader.cpp index 3bfcd58c69..f470e1b2a5 100644 --- a/indra/llmessage/lltemplatemessagereader.cpp +++ b/indra/llmessage/lltemplatemessagereader.cpp @@ -30,6 +30,7 @@ #include "llfasttimer.h" #include "llmessagebuilder.h" #include "llmessagetemplate.h" +#include "llmath.h" #include "llquaternion.h" #include "message.h" #include "u64.h" diff --git a/indra/llmessage/lltransfermanager.cpp b/indra/llmessage/lltransfermanager.cpp index 754eb99cbd..034680caf8 100644 --- a/indra/llmessage/lltransfermanager.cpp +++ b/indra/llmessage/lltransfermanager.cpp @@ -338,7 +338,7 @@ void LLTransferManager::processTransferInfo(LLMessageSystem *msgp, void **) } } - llinfos << "Receiving " << transfer_id << ", size " << size << " bytes" << llendl; + //llinfos << "Receiving " << transfer_id << ", size " << size << " bytes" << llendl; ttp->setSize(size); ttp->setGotInfo(TRUE); diff --git a/indra/llmessage/lltransfersourceasset.cpp b/indra/llmessage/lltransfersourceasset.cpp index 7e57841580..8537773a3f 100644 --- a/indra/llmessage/lltransfersourceasset.cpp +++ b/indra/llmessage/lltransfersourceasset.cpp @@ -251,3 +251,4 @@ BOOL LLTransferSourceParamsAsset::unpackParams(LLDataPacker &dp) return TRUE; } + diff --git a/indra/llmessage/tests/commtest.h b/indra/llmessage/tests/commtest.h index 32035783e2..0fef596df2 100644 --- a/indra/llmessage/tests/commtest.h +++ b/indra/llmessage/tests/commtest.h @@ -35,6 +35,13 @@ #include "llhost.h" #include "stringize.h" #include <string> +#include <stdexcept> +#include <boost/lexical_cast.hpp> + +struct CommtestError: public std::runtime_error +{ + CommtestError(const std::string& what): std::runtime_error(what) {} +}; /** * This struct is shared by a couple of standalone comm tests (ADD_COMM_BUILD_TEST). @@ -55,13 +62,24 @@ struct commtest_data replyPump("reply"), errorPump("error"), success(false), - host("127.0.0.1", 8000), + host("127.0.0.1", getport("PORT")), server(STRINGIZE("http://" << host.getString() << "/")) { replyPump.listen("self", boost::bind(&commtest_data::outcome, this, _1, true)); errorPump.listen("self", boost::bind(&commtest_data::outcome, this, _1, false)); } + static int getport(const std::string& var) + { + const char* port = getenv(var.c_str()); + if (! port) + { + throw CommtestError("missing $PORT environment variable"); + } + // This will throw, too, if the value of PORT isn't numeric. + return boost::lexical_cast<int>(port); + } + bool outcome(const LLSD& _result, bool _success) { // std::cout << "commtest_data::outcome(" << _result << ", " << _success << ")\n"; diff --git a/indra/llmessage/tests/llsdmessage_test.cpp b/indra/llmessage/tests/llsdmessage_test.cpp index 9998a1b8bb..0f2c069303 100644 --- a/indra/llmessage/tests/llsdmessage_test.cpp +++ b/indra/llmessage/tests/llsdmessage_test.cpp @@ -61,6 +61,7 @@ namespace tut llsdmessage_data(): httpPump(pumps.obtain("LLHTTPClient")) { + LLCurl::initClass(); LLSDMessage::link(); } }; diff --git a/indra/llmessage/tests/test_llsdmessage_peer.py b/indra/llmessage/tests/test_llsdmessage_peer.py index 580ee7f8b4..cea5032111 100644 --- a/indra/llmessage/tests/test_llsdmessage_peer.py +++ b/indra/llmessage/tests/test_llsdmessage_peer.py @@ -38,7 +38,7 @@ mydir = os.path.dirname(__file__) # expected to be .../indra/llmessage/tes sys.path.insert(0, os.path.join(mydir, os.pardir, os.pardir, "lib", "python")) from indra.util.fastest_elementtree import parse as xml_parse from indra.base import llsd -from testrunner import run, debug +from testrunner import freeport, run, debug class TestHTTPRequestHandler(BaseHTTPRequestHandler): """This subclass of BaseHTTPRequestHandler is to receive and echo @@ -97,6 +97,10 @@ class TestHTTPRequestHandler(BaseHTTPRequestHandler): self.wfile.write(response) else: # fail requested status = data.get("status", 500) + # self.responses maps an int status to a (short, long) pair of + # strings. We want the longer string. That's why we pass a string + # pair to get(): the [1] will select the second string, whether it + # came from self.responses or from our default pair. reason = data.get("reason", self.responses.get(status, ("fail requested", @@ -113,11 +117,17 @@ class TestHTTPRequestHandler(BaseHTTPRequestHandler): # Suppress error output as well pass -class TestHTTPServer(Thread): - def run(self): - httpd = HTTPServer(('127.0.0.1', 8000), TestHTTPRequestHandler) - debug("Starting HTTP server...\n") - httpd.serve_forever() - if __name__ == "__main__": - sys.exit(run(server=TestHTTPServer(name="httpd"), *sys.argv[1:])) + # Instantiate an HTTPServer(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)) + # 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 + # performed in TUT code rather than our own. + os.environ["PORT"] = str(port) + sys.exit(run(server=Thread(name="httpd", target=httpd.serve_forever), *sys.argv[1:])) diff --git a/indra/llmessage/tests/testrunner.py b/indra/llmessage/tests/testrunner.py index b70ce91ee7..8ff13e0426 100644 --- a/indra/llmessage/tests/testrunner.py +++ b/indra/llmessage/tests/testrunner.py @@ -29,6 +29,8 @@ $/LicenseInfo$ import os import sys +import errno +import socket def debug(*args): sys.stdout.writelines(args) @@ -36,6 +38,85 @@ def debug(*args): # comment out the line below to enable debug output debug = lambda *args: None +def freeport(portlist, expr): + """ + Find a free server port to use. Specifically, evaluate 'expr' (a + callable(port)) until it stops raising EADDRINUSE exception. + + Pass: + + portlist: an iterable (e.g. xrange()) of ports to try. If you exhaust the + range, freeport() lets the socket.error exception propagate. If you want + unbounded, you could pass itertools.count(baseport), though of course in + practice the ceiling is 2^16-1 anyway. But it seems prudent to constrain + the range much more sharply: if we're iterating an absurd number of times, + probably something else is wrong. + + expr: a callable accepting a port number, specifically one of the items + from portlist. If calling that callable raises socket.error with + EADDRINUSE, freeport() retrieves the next item from portlist and retries. + + Returns: (expr(port), port) + + port: the value from portlist for which expr(port) succeeded + + Raises: + + Any exception raised by expr(port) other than EADDRINUSE. + + socket.error if, for every item from portlist, expr(port) raises + socket.error. The exception you see is the one from the last item in + portlist. + + StopIteration if portlist is completely empty. + + Example: + + server, port = freeport(xrange(8000, 8010), + lambda port: HTTPServer(("localhost", port), + MyRequestHandler)) + # pass 'port' to client code + # call server.serve_forever() + """ + # If portlist is completely empty, let StopIteration propagate: that's an + # error because we can't return meaningful values. We have no 'port', + # therefore no 'expr(port)'. + portiter = iter(portlist) + port = portiter.next() + + while True: + try: + # If this value of port works, return as promised. + return expr(port), port + + except socket.error, err: + # Anything other than 'Address already in use', propagate + if err.args[0] != errno.EADDRINUSE: + raise + + # Here we want the next port from portiter. But on StopIteration, + # we want to raise the original exception rather than + # StopIteration. So save the original exc_info(). + type, value, tb = sys.exc_info() + try: + try: + port = portiter.next() + except StopIteration: + raise type, value, tb + finally: + # Clean up local traceback, see docs for sys.exc_info() + del tb + + # Recap of the control flow above: + # If expr(port) doesn't raise, return as promised. + # If expr(port) raises anything but EADDRINUSE, propagate that + # exception. + # If portiter.next() raises StopIteration -- that is, if the port + # value we just passed to expr(port) was the last available -- reraise + # the EADDRINUSE exception. + # If we've actually arrived at this point, portiter.next() delivered a + # new port value. Loop back to pass that to expr(port). + def run(*args, **kwds): """All positional arguments collectively form a command line, executed as a synchronous child process. |