Skip to content
Snippets Groups Projects
Commit f0353abe authored by Monty Brandenberg's avatar Monty Brandenberg
Browse files

Implement timeout and retry count options for requests.

Pretty straightforward.  Still don't like how I'm managing
the options block.  Struct?  Accessors?  Can't decide.  But
the options now speed up the unit test runs even as I add
tests.
parent 1cf8e785
No related branches found
No related tags found
No related merge requests found
...@@ -108,7 +108,7 @@ HttpOpRequest::HttpOpRequest() ...@@ -108,7 +108,7 @@ HttpOpRequest::HttpOpRequest()
mReplyHeaders(NULL), mReplyHeaders(NULL),
mPolicyRetries(0), mPolicyRetries(0),
mPolicyRetryAt(HttpTime(0)), mPolicyRetryAt(HttpTime(0)),
mPolicyRetryLimit(5) // *FIXME: Get from policy definitions mPolicyRetryLimit(5)
{ {
// *NOTE: As members are added, retry initialization/cleanup // *NOTE: As members are added, retry initialization/cleanup
// may need to be extended in @prepareRequest(). // may need to be extended in @prepareRequest().
...@@ -333,6 +333,8 @@ void HttpOpRequest::setupCommon(HttpRequest::policy_t policy_id, ...@@ -333,6 +333,8 @@ void HttpOpRequest::setupCommon(HttpRequest::policy_t policy_id,
{ {
mProcFlags |= PF_SAVE_HEADERS; mProcFlags |= PF_SAVE_HEADERS;
} }
mPolicyRetryLimit = options->getRetries();
mPolicyRetryLimit = llclamp(mPolicyRetryLimit, 0, 100);
mTracing = (std::max)(mTracing, llclamp(options->getTrace(), 0, 3)); mTracing = (std::max)(mTracing, llclamp(options->getTrace(), 0, 3));
} }
} }
...@@ -371,10 +373,7 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service) ...@@ -371,10 +373,7 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service)
HttpPolicyGlobal & policy(service->getPolicy().getGlobalOptions()); HttpPolicyGlobal & policy(service->getPolicy().getGlobalOptions());
mCurlHandle = curl_easy_init(); mCurlHandle = curl_easy_init();
// curl_easy_setopt(mCurlHandle, CURLOPT_VERBOSE, 1);
curl_easy_setopt(mCurlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); curl_easy_setopt(mCurlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
curl_easy_setopt(mCurlHandle, CURLOPT_TIMEOUT, 30);
curl_easy_setopt(mCurlHandle, CURLOPT_CONNECTTIMEOUT, 30);
curl_easy_setopt(mCurlHandle, CURLOPT_NOSIGNAL, 1); curl_easy_setopt(mCurlHandle, CURLOPT_NOSIGNAL, 1);
curl_easy_setopt(mCurlHandle, CURLOPT_NOPROGRESS, 1); curl_easy_setopt(mCurlHandle, CURLOPT_NOPROGRESS, 1);
curl_easy_setopt(mCurlHandle, CURLOPT_URL, mReqURL.c_str()); curl_easy_setopt(mCurlHandle, CURLOPT_URL, mReqURL.c_str());
...@@ -493,13 +492,25 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service) ...@@ -493,13 +492,25 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service)
} }
mCurlHeaders = curl_slist_append(mCurlHeaders, "Pragma:"); mCurlHeaders = curl_slist_append(mCurlHeaders, "Pragma:");
// Request options
long timeout(30);
if (mReqOptions)
{
timeout = mReqOptions->getTimeout();
timeout = llclamp(timeout, 0L, 3600L);
}
curl_easy_setopt(mCurlHandle, CURLOPT_TIMEOUT, timeout);
curl_easy_setopt(mCurlHandle, CURLOPT_CONNECTTIMEOUT, timeout);
// Request headers
if (mReqHeaders) if (mReqHeaders)
{ {
// Caller's headers last to override // Caller's headers last to override
mCurlHeaders = append_headers_to_slist(mReqHeaders, mCurlHeaders); mCurlHeaders = append_headers_to_slist(mReqHeaders, mCurlHeaders);
} }
curl_easy_setopt(mCurlHandle, CURLOPT_HTTPHEADER, mCurlHeaders); curl_easy_setopt(mCurlHandle, CURLOPT_HTTPHEADER, mCurlHeaders);
if (mProcFlags & (PF_SCAN_RANGE_HEADER | PF_SAVE_HEADERS)) if (mProcFlags & (PF_SCAN_RANGE_HEADER | PF_SAVE_HEADERS))
{ {
curl_easy_setopt(mCurlHandle, CURLOPT_HEADERFUNCTION, headerCallback); curl_easy_setopt(mCurlHandle, CURLOPT_HEADERFUNCTION, headerCallback);
......
...@@ -155,7 +155,7 @@ class HttpOpRequest : public HttpOperation ...@@ -155,7 +155,7 @@ class HttpOpRequest : public HttpOperation
// Policy data // Policy data
int mPolicyRetries; int mPolicyRetries;
HttpTime mPolicyRetryAt; HttpTime mPolicyRetryAt;
const int mPolicyRetryLimit; int mPolicyRetryLimit;
}; // end class HttpOpRequest }; // end class HttpOpRequest
......
...@@ -34,14 +34,9 @@ namespace LLCore ...@@ -34,14 +34,9 @@ namespace LLCore
HttpOptions::HttpOptions() HttpOptions::HttpOptions()
: RefCounted(true), : RefCounted(true),
mWantHeaders(false), mWantHeaders(false),
mTracing(0) mTracing(0),
{} mTimeout(30),
mRetries(5)
HttpOptions::HttpOptions(const HttpOptions & rhs)
: RefCounted(true),
mWantHeaders(rhs.mWantHeaders),
mTracing(rhs.mTracing)
{} {}
...@@ -61,4 +56,16 @@ void HttpOptions::setTrace(long level) ...@@ -61,4 +56,16 @@ void HttpOptions::setTrace(long level)
} }
void HttpOptions::setTimeout(unsigned int timeout)
{
mTimeout = timeout;
}
void HttpOptions::setRetries(unsigned int retries)
{
mRetries = retries;
}
} // end namespace LLCore } // end namespace LLCore
...@@ -60,29 +60,44 @@ class HttpOptions : public LLCoreInt::RefCounted ...@@ -60,29 +60,44 @@ class HttpOptions : public LLCoreInt::RefCounted
{ {
public: public:
HttpOptions(); HttpOptions();
HttpOptions(const HttpOptions &);
protected: protected:
virtual ~HttpOptions(); // Use release() virtual ~HttpOptions(); // Use release()
HttpOptions(const HttpOptions &); // Not defined
void operator=(const HttpOptions &); // Not defined void operator=(const HttpOptions &); // Not defined
public: public:
void setWantHeaders(); void setWantHeaders();
bool getWantHeaders() const bool getWantHeaders() const
{ {
return mWantHeaders; return mWantHeaders;
} }
void setTrace(long level); void setTrace(int long);
int getTrace() const int getTrace() const
{ {
return mTracing; return mTracing;
} }
void setTimeout(unsigned int timeout);
unsigned int getTimeout() const
{
return mTimeout;
}
void setRetries(unsigned int retries);
unsigned int getRetries() const
{
return mRetries;
}
protected: protected:
bool mWantHeaders; bool mWantHeaders;
long int mTracing; int mTracing;
unsigned int mTimeout;
unsigned int mRetries;
}; // end class HttpOptions }; // end class HttpOptions
......
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
#include "bufferarray.h" #include "bufferarray.h"
#include "httphandler.h" #include "httphandler.h"
#include "httpresponse.h" #include "httpresponse.h"
#include "httpoptions.h"
#include "_httpservice.h" #include "_httpservice.h"
#include "_httprequestqueue.h" #include "_httprequestqueue.h"
...@@ -407,7 +408,8 @@ void HttpRequestTestObjectType::test<5>() ...@@ -407,7 +408,8 @@ void HttpRequestTestObjectType::test<5>()
mHandlerCalls = 0; mHandlerCalls = 0;
HttpRequest * req = NULL; HttpRequest * req = NULL;
HttpOptions * opts = NULL;
try try
{ {
// Get singletons created // Get singletons created
...@@ -421,6 +423,9 @@ void HttpRequestTestObjectType::test<5>() ...@@ -421,6 +423,9 @@ void HttpRequestTestObjectType::test<5>()
req = new HttpRequest(); req = new HttpRequest();
ensure("Memory allocated on construction", mMemTotal < GetMemTotal()); ensure("Memory allocated on construction", mMemTotal < GetMemTotal());
opts = new HttpOptions();
opts->setRetries(1); // Don't try for too long - default retries take about 18S
// Issue a GET that can't connect // Issue a GET that can't connect
mStatus = HttpStatus(HttpStatus::EXT_CURL_EASY, CURLE_COULDNT_CONNECT); mStatus = HttpStatus(HttpStatus::EXT_CURL_EASY, CURLE_COULDNT_CONNECT);
HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID, HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID,
...@@ -428,14 +433,14 @@ void HttpRequestTestObjectType::test<5>() ...@@ -428,14 +433,14 @@ void HttpRequestTestObjectType::test<5>()
"http://127.0.0.1:2/nothing/here", "http://127.0.0.1:2/nothing/here",
0, 0,
0, 0,
NULL, opts,
NULL, NULL,
&handler); &handler);
ensure("Valid handle returned for ranged request", handle != LLCORE_HTTP_HANDLE_INVALID); ensure("Valid handle returned for ranged request", handle != LLCORE_HTTP_HANDLE_INVALID);
// Run the notification pump. // Run the notification pump.
int count(0); int count(0);
int limit(180); // With retries, can take more than 10 seconds to give up int limit(50); // With one retry, should fail quickish
while (count++ < limit && mHandlerCalls < 1) while (count++ < limit && mHandlerCalls < 1)
{ {
req->update(1000); req->update(1000);
...@@ -468,7 +473,11 @@ void HttpRequestTestObjectType::test<5>() ...@@ -468,7 +473,11 @@ void HttpRequestTestObjectType::test<5>()
usleep(100000); usleep(100000);
} }
ensure("Thread actually stopped running", HttpService::isStopped()); ensure("Thread actually stopped running", HttpService::isStopped());
// release options
opts->release();
opts = NULL;
// release the request object // release the request object
delete req; delete req;
req = NULL; req = NULL;
...@@ -490,6 +499,11 @@ void HttpRequestTestObjectType::test<5>() ...@@ -490,6 +499,11 @@ void HttpRequestTestObjectType::test<5>()
catch (...) catch (...)
{ {
stop_thread(req); stop_thread(req);
if (opts)
{
opts->release();
opts = NULL;
}
delete req; delete req;
HttpRequest::destroyService(); HttpRequest::destroyService();
throw; throw;
...@@ -503,7 +517,7 @@ void HttpRequestTestObjectType::test<6>() ...@@ -503,7 +517,7 @@ void HttpRequestTestObjectType::test<6>()
ScopedCurlInit ready; ScopedCurlInit ready;
std::string url_base(get_base_url()); std::string url_base(get_base_url());
std::cerr << "Base: " << url_base << std::endl; // std::cerr << "Base: " << url_base << std::endl;
set_test_name("HttpRequest GET to real service"); set_test_name("HttpRequest GET to real service");
...@@ -611,7 +625,7 @@ void HttpRequestTestObjectType::test<7>() ...@@ -611,7 +625,7 @@ void HttpRequestTestObjectType::test<7>()
ScopedCurlInit ready; ScopedCurlInit ready;
std::string url_base(get_base_url()); std::string url_base(get_base_url());
std::cerr << "Base: " << url_base << std::endl; // std::cerr << "Base: " << url_base << std::endl;
set_test_name("HttpRequest GET with Range: header to real service"); set_test_name("HttpRequest GET with Range: header to real service");
...@@ -721,7 +735,7 @@ void HttpRequestTestObjectType::test<8>() ...@@ -721,7 +735,7 @@ void HttpRequestTestObjectType::test<8>()
ScopedCurlInit ready; ScopedCurlInit ready;
std::string url_base(get_base_url()); std::string url_base(get_base_url());
std::cerr << "Base: " << url_base << std::endl; // std::cerr << "Base: " << url_base << std::endl;
set_test_name("HttpRequest PUT to real service"); set_test_name("HttpRequest PUT to real service");
...@@ -840,7 +854,7 @@ void HttpRequestTestObjectType::test<9>() ...@@ -840,7 +854,7 @@ void HttpRequestTestObjectType::test<9>()
ScopedCurlInit ready; ScopedCurlInit ready;
std::string url_base(get_base_url()); std::string url_base(get_base_url());
std::cerr << "Base: " << url_base << std::endl; // std::cerr << "Base: " << url_base << std::endl;
set_test_name("HttpRequest POST to real service"); set_test_name("HttpRequest POST to real service");
...@@ -959,7 +973,7 @@ void HttpRequestTestObjectType::test<10>() ...@@ -959,7 +973,7 @@ void HttpRequestTestObjectType::test<10>()
ScopedCurlInit ready; ScopedCurlInit ready;
std::string url_base(get_base_url()); std::string url_base(get_base_url());
std::cerr << "Base: " << url_base << std::endl; // std::cerr << "Base: " << url_base << std::endl;
set_test_name("HttpRequest GET with some tracing"); set_test_name("HttpRequest GET with some tracing");
...@@ -1065,6 +1079,129 @@ void HttpRequestTestObjectType::test<10>() ...@@ -1065,6 +1079,129 @@ void HttpRequestTestObjectType::test<10>()
} }
} }
template <> template <>
void HttpRequestTestObjectType::test<11>()
{
ScopedCurlInit ready;
set_test_name("HttpRequest GET timeout");
// Handler can be stack-allocated *if* there are no dangling
// references to it after completion of this method.
// Create before memory record as the string copy will bump numbers.
TestHandler2 handler(this, "handler");
std::string url_base(get_base_url() + "/sleep/"); // path to a 30-second sleep
// record the total amount of dynamically allocated memory
mMemTotal = GetMemTotal();
mHandlerCalls = 0;
HttpRequest * req = NULL;
HttpOptions * opts = NULL;
try
{
// Get singletons created
HttpRequest::createService();
// Start threading early so that thread memory is invariant
// over the test.
HttpRequest::startThread();
// create a new ref counted object with an implicit reference
req = new HttpRequest();
ensure("Memory allocated on construction", mMemTotal < GetMemTotal());
opts = new HttpOptions();
opts->setRetries(0); // Don't retry
opts->setTimeout(2);
// Issue a GET that can't connect
mStatus = HttpStatus(HttpStatus::EXT_CURL_EASY, CURLE_OPERATION_TIMEDOUT);
HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID,
0U,
url_base,
0,
0,
opts,
NULL,
&handler);
ensure("Valid handle returned for ranged request", handle != LLCORE_HTTP_HANDLE_INVALID);
// Run the notification pump.
int count(0);
int limit(50); // With one retry, should fail quickish
while (count++ < limit && mHandlerCalls < 1)
{
req->update(1000);
usleep(100000);
}
ensure("Request executed in reasonable time", count < limit);
ensure("One handler invocation for request", mHandlerCalls == 1);
// Okay, request a shutdown of the servicing thread
mStatus = HttpStatus();
handle = req->requestStopThread(&handler);
ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID);
// Run the notification pump again
count = 0;
limit = 100;
while (count++ < limit && mHandlerCalls < 2)
{
req->update(1000);
usleep(100000);
}
ensure("Second request executed in reasonable time", count < limit);
ensure("Second handler invocation", mHandlerCalls == 2);
// See that we actually shutdown the thread
count = 0;
limit = 10;
while (count++ < limit && ! HttpService::isStopped())
{
usleep(100000);
}
ensure("Thread actually stopped running", HttpService::isStopped());
// release options
opts->release();
opts = NULL;
// release the request object
delete req;
req = NULL;
// Shut down service
HttpRequest::destroyService();
ensure("Two handler calls on the way out", 2 == mHandlerCalls);
#if defined(WIN32)
// Can only do this memory test on Windows. On other platforms,
// the LL logging system holds on to memory and produces what looks
// like memory leaks...
// printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal());
ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal());
#endif
}
catch (...)
{
stop_thread(req);
if (opts)
{
opts->release();
opts = NULL;
}
delete req;
HttpRequest::destroyService();
throw;
}
}
} // end namespace tut } // end namespace tut
namespace namespace
......
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
import os import os
import sys import sys
import time
from threading import Thread from threading import Thread
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
...@@ -97,6 +98,9 @@ def do_PUT(self): ...@@ -97,6 +98,9 @@ def do_PUT(self):
def answer(self, data): def answer(self, data):
debug("%s.answer(%s): self.path = %r", self.__class__.__name__, data, self.path) debug("%s.answer(%s): self.path = %r", self.__class__.__name__, data, self.path)
if self.path.find("/sleep/") != -1:
time.sleep(30)
if "fail" not in self.path: if "fail" not in self.path:
response = llsd.format_xml(data.get("reply", llsd.LLSD("success"))) response = llsd.format_xml(data.get("reply", llsd.LLSD("success")))
debug("success: %s", response) debug("success: %s", response)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment