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/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 | 
13 files changed, 303 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/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. | 
