Merge remote-tracking branch 'Aleric/master' into Cupcake

This commit is contained in:
Drake Arconis
2013-03-11 19:55:38 -04:00
47 changed files with 1925 additions and 1178 deletions

View File

@@ -418,7 +418,8 @@ void cleanupCurl(void)
stopCurlThread();
if (CurlMultiHandle::getTotalMultiHandles() != 0)
llwarns << "Not all CurlMultiHandle objects were destroyed!" << llendl;
AIStateMachine::flush();
gMainThreadEngine.flush(); // Not really related to curl, but why not.
gStateMachineThreadEngine.flush();
clearCommandQueue();
Stats::print();
ssl_cleanup();
@@ -770,6 +771,22 @@ void CurlEasyRequest::setoptString(CURLoption option, std::string const& value)
setopt(option, value.c_str());
}
void CurlEasyRequest::setPut(U32 size, bool keepalive)
{
DoutCurl("PUT size is " << size << " bytes.");
mContentLength = size;
// The server never replies with 100-continue, so suppress the "Expect: 100-continue" header that libcurl adds by default.
addHeader("Expect:");
if (size > 0 && keepalive)
{
addHeader("Connection: keep-alive");
addHeader("Keep-alive: 300");
}
setopt(CURLOPT_UPLOAD, 1);
setopt(CURLOPT_INFILESIZE, size);
}
void CurlEasyRequest::setPost(AIPostFieldPtr const& postdata, U32 size, bool keepalive)
{
llassert_always(postdata->data());
@@ -787,6 +804,7 @@ void CurlEasyRequest::setPost_raw(U32 size, char const* data, bool keepalive)
// data == NULL when we're going to read the data using CURLOPT_READFUNCTION.
DoutCurl("POST size is " << size << " bytes.");
}
mContentLength = size;
// The server never replies with 100-continue, so suppress the "Expect: 100-continue" header that libcurl adds by default.
addHeader("Expect:");
@@ -872,13 +890,13 @@ void CurlEasyRequest::setSSLCtxCallback(curl_ssl_ctx_callback callback, void* us
static size_t noHeaderCallback(char* ptr, size_t size, size_t nmemb, void* userdata)
{
llmaybewarns << "Calling noHeaderCallback(); curl session aborted." << llendl;
return 0; // Cause a CURL_WRITE_ERROR
return 0; // Cause a CURLE_WRITE_ERROR
}
static size_t noWriteCallback(char* ptr, size_t size, size_t nmemb, void* userdata)
{
llmaybewarns << "Calling noWriteCallback(); curl session aborted." << llendl;
return 0; // Cause a CURL_WRITE_ERROR
return 0; // Cause a CURLE_WRITE_ERROR
}
static size_t noReadCallback(char* ptr, size_t size, size_t nmemb, void* userdata)
@@ -1276,7 +1294,7 @@ static int const HTTP_REDIRECTS_DEFAULT = 10;
LLChannelDescriptors const BufferedCurlEasyRequest::sChannels;
BufferedCurlEasyRequest::BufferedCurlEasyRequest() : mRequestTransferedBytes(0), mResponseTransferedBytes(0), mBufferEventsTarget(NULL), mStatus(HTTP_INTERNAL_ERROR)
BufferedCurlEasyRequest::BufferedCurlEasyRequest() : mRequestTransferedBytes(0), mResponseTransferedBytes(0), mBufferEventsTarget(NULL), mStatus(HTTP_INTERNAL_ERROR_OTHER)
{
AICurlInterface::Stats::BufferedCurlEasyRequest_count++;
}
@@ -1311,7 +1329,7 @@ BufferedCurlEasyRequest::~BufferedCurlEasyRequest()
void BufferedCurlEasyRequest::timed_out(void)
{
mResponder->finished(CURLE_OK, HTTP_INTERNAL_ERROR, "Request timeout, aborted.", sChannels, mOutput);
mResponder->finished(CURLE_OK, HTTP_INTERNAL_ERROR_CURL_LOCKUP, "Request timeout, aborted.", sChannels, mOutput);
if (mResponder->needsHeaders())
{
send_buffer_events_to(NULL); // Revoke buffer events: we send them to the responder.
@@ -1321,7 +1339,7 @@ void BufferedCurlEasyRequest::timed_out(void)
void BufferedCurlEasyRequest::bad_socket(void)
{
mResponder->finished(CURLE_OK, HTTP_INTERNAL_ERROR, "File descriptor went bad! Aborted.", sChannels, mOutput);
mResponder->finished(CURLE_OK, HTTP_INTERNAL_ERROR_CURL_BADSOCKET, "File descriptor went bad! Aborted.", sChannels, mOutput);
if (mResponder->needsHeaders())
{
send_buffer_events_to(NULL); // Revoke buffer events: we send them to the responder.
@@ -1342,7 +1360,7 @@ void BufferedCurlEasyRequest::resetState(void)
mRequestTransferedBytes = 0;
mResponseTransferedBytes = 0;
mBufferEventsTarget = NULL;
mStatus = HTTP_INTERNAL_ERROR;
mStatus = HTTP_INTERNAL_ERROR_OTHER;
}
void BufferedCurlEasyRequest::print_diagnostics(CURLcode code)

View File

@@ -52,8 +52,6 @@
#include "stdtypes.h" // U16, S32, U32, F64
#include "llatomic.h" // LLAtomicU32
#include "aithreadsafe.h"
#include "llhttpstatuscodes.h"
#include "llhttpclient.h"
// Debug Settings.
extern bool gNoVerifySSLCert;

View File

@@ -36,10 +36,8 @@
enum curleasyrequeststatemachine_state_type {
AICurlEasyRequestStateMachine_addRequest = AIStateMachine::max_state,
AICurlEasyRequestStateMachine_waitAdded,
AICurlEasyRequestStateMachine_added,
AICurlEasyRequestStateMachine_timedOut, // This must be smaller than the rest, so they always overrule.
AICurlEasyRequestStateMachine_finished,
AICurlEasyRequestStateMachine_removed, // The removed states must be largest two, so they are never ignored.
AICurlEasyRequestStateMachine_timedOut, // This must be smaller than the rest, so they always overrule.
AICurlEasyRequestStateMachine_removed, // The removed states must be largest two, so they are never ignored.
AICurlEasyRequestStateMachine_removed_after_finished,
AICurlEasyRequestStateMachine_bad_file_descriptor
};
@@ -50,9 +48,7 @@ char const* AICurlEasyRequestStateMachine::state_str_impl(state_type run_state)
{
AI_CASE_RETURN(AICurlEasyRequestStateMachine_addRequest);
AI_CASE_RETURN(AICurlEasyRequestStateMachine_waitAdded);
AI_CASE_RETURN(AICurlEasyRequestStateMachine_added);
AI_CASE_RETURN(AICurlEasyRequestStateMachine_timedOut);
AI_CASE_RETURN(AICurlEasyRequestStateMachine_finished);
AI_CASE_RETURN(AICurlEasyRequestStateMachine_removed);
AI_CASE_RETURN(AICurlEasyRequestStateMachine_removed_after_finished);
AI_CASE_RETURN(AICurlEasyRequestStateMachine_bad_file_descriptor);
@@ -77,14 +73,12 @@ void AICurlEasyRequestStateMachine::initialize_impl(void)
// CURL-THREAD
void AICurlEasyRequestStateMachine::added_to_multi_handle(AICurlEasyRequest_wat&)
{
set_state(AICurlEasyRequestStateMachine_added);
}
// CURL-THREAD
void AICurlEasyRequestStateMachine::finished(AICurlEasyRequest_wat&)
{
mFinished = true;
set_state(AICurlEasyRequestStateMachine_finished);
}
// CURL-THREAD
@@ -93,7 +87,7 @@ void AICurlEasyRequestStateMachine::removed_from_multi_handle(AICurlEasyRequest_
llassert(mFinished || mTimedOut); // If we neither finished nor timed out, then why is this being removed?
// Note that allowing this would cause an assertion later on for removing
// a BufferedCurlEasyRequest with a still active Responder.
set_state(mFinished ? AICurlEasyRequestStateMachine_removed_after_finished : AICurlEasyRequestStateMachine_removed);
advance_state(mFinished ? AICurlEasyRequestStateMachine_removed_after_finished : AICurlEasyRequestStateMachine_removed);
}
// CURL-THREAD
@@ -102,7 +96,7 @@ void AICurlEasyRequestStateMachine::bad_file_descriptor(AICurlEasyRequest_wat&)
if (!mFinished)
{
mFinished = true;
set_state(AICurlEasyRequestStateMachine_bad_file_descriptor);
advance_state(AICurlEasyRequestStateMachine_bad_file_descriptor);
}
}
@@ -114,59 +108,46 @@ void AICurlEasyRequestStateMachine::queued_for_removal(AICurlEasyRequest_wat&)
}
#endif
void AICurlEasyRequestStateMachine::multiplex_impl(void)
void AICurlEasyRequestStateMachine::multiplex_impl(state_type run_state)
{
mSetStateLock.lock();
state_type current_state = mRunState;
mSetStateLock.unlock();
switch (current_state)
switch (run_state)
{
case AICurlEasyRequestStateMachine_addRequest:
{
set_state(AICurlEasyRequestStateMachine_waitAdded);
idle(AICurlEasyRequestStateMachine_waitAdded); // Wait till AICurlEasyRequestStateMachine::added_to_multi_handle() is called.
idle(); // Wait till AICurlEasyRequestStateMachine::added_to_multi_handle() is called.
// Only AFTER going idle, add request to curl thread; this is needed because calls to set_state() are
// ignored when the statemachine is not idle, and theoretically the callbacks could be called
// immediately after this call.
mAdded = true;
mCurlEasyRequest.addRequest(); // This causes the state to be changed, now or later, to
// AICurlEasyRequestStateMachine_added, then
// AICurlEasyRequestStateMachine_finished and then
// AICurlEasyRequestStateMachine_removed_after_finished.
// The first two states might be skipped thus, and the state at this point is one of
// The state at this point is thus one of
// 1) AICurlEasyRequestStateMachine_waitAdded (idle)
// 2) AICurlEasyRequestStateMachine_added (running)
// 3) AICurlEasyRequestStateMachine_finished (running)
// 4) AICurlEasyRequestStateMachine_removed_after_finished (running)
// 2) AICurlEasyRequestStateMachine_removed_after_finished (running)
if (mTotalDelayTimeout > 0.f)
{
// Set an inactivity timer.
// This shouldn't really be necessary, except in the case of a bug
// in libcurl; but lets be sure and set a timer for inactivity.
mTimer = new AIPersistentTimer; // Do not delete timer upon expiration.
mTimer = new AITimer;
mTimer->setInterval(mTotalDelayTimeout);
mTimer->run(this, AICurlEasyRequestStateMachine_timedOut, false, false);
}
break;
}
case AICurlEasyRequestStateMachine_added:
case AICurlEasyRequestStateMachine_waitAdded:
{
// The request was added to the multi handle. This is a no-op, which is good cause
// this state might be skipped anyway ;).
idle(current_state); // Wait for the next event.
// The state at this point is one of
// 1) AICurlEasyRequestStateMachine_added (idle)
// 2) AICurlEasyRequestStateMachine_finished (running)
// 3) AICurlEasyRequestStateMachine_removed_after_finished (running)
// Nothing to do.
idle();
break;
}
case AICurlEasyRequestStateMachine_timedOut:
{
// It is possible that exactly at this point the state changes into
// AICurlEasyRequestStateMachine_finished, with as result that mTimedOut
// AICurlEasyRequestStateMachine_removed_after_finished, with as result that mTimedOut
// is set while we will continue with that state. Hence that mTimedOut
// is explicitly reset in that state.
@@ -176,10 +157,9 @@ void AICurlEasyRequestStateMachine::multiplex_impl(void)
llassert(mAdded);
mAdded = false;
mCurlEasyRequest.removeRequest();
idle(current_state); // Wait till AICurlEasyRequestStateMachine::removed_from_multi_handle() is called.
idle(); // Wait till AICurlEasyRequestStateMachine::removed_from_multi_handle() is called.
break;
}
case AICurlEasyRequestStateMachine_finished:
case AICurlEasyRequestStateMachine_removed_after_finished:
{
if (!mHandled)
@@ -199,12 +179,6 @@ void AICurlEasyRequestStateMachine::multiplex_impl(void)
easy_request_w->processOutput();
}
if (current_state == AICurlEasyRequestStateMachine_finished)
{
idle(current_state); // Wait till AICurlEasyRequestStateMachine::removed_from_multi_handle() is called.
break;
}
// See above.
mTimedOut = false;
/* Fall-Through */
@@ -261,17 +235,14 @@ void AICurlEasyRequestStateMachine::finish_impl(void)
}
if (mTimer)
{
// Note that even if the timer expired, it wasn't deleted because we used AIPersistentTimer; so mTimer is still valid.
// Stop the timer, if it's still running.
if (!mHandled)
mTimer->abort();
}
// Auto clean up ourselves.
kill();
}
AICurlEasyRequestStateMachine::AICurlEasyRequestStateMachine(void) :
mTimer(NULL), mTotalDelayTimeout(AIHTTPTimeoutPolicy::getDebugSettingsCurlTimeout().getTotalDelay())
mTotalDelayTimeout(AIHTTPTimeoutPolicy::getDebugSettingsCurlTimeout().getTotalDelay())
{
Dout(dc::statemachine, "Calling AICurlEasyRequestStateMachine(void) [" << (void*)this << "] [" << (void*)mCurlEasyRequest.get() << "]");
AICurlInterface::Stats::AICurlEasyRequestStateMachine_count++;

View File

@@ -62,7 +62,7 @@ class AICurlEasyRequestStateMachine : public AIStateMachine, public AICurlEasyHa
bool mTimedOut; // Set if the expiration timer timed out.
bool mFinished; // Set by the curl thread to signal it finished.
bool mHandled; // Set when we processed the received data.
AITimer* mTimer; // Expiration timer.
LLPointer<AITimer> mTimer; // Expiration timer.
F32 mTotalDelayTimeout; // The time out value for mTimer.
public:
@@ -99,7 +99,7 @@ class AICurlEasyRequestStateMachine : public AIStateMachine, public AICurlEasyHa
/*virtual*/ void initialize_impl(void);
// Handle mRunState.
/*virtual*/ void multiplex_impl(void);
/*virtual*/ void multiplex_impl(state_type run_state);
// Handle aborting from current bs_run state.
/*virtual*/ void abort_impl(void);

View File

@@ -36,9 +36,11 @@
#include "llrefcount.h"
#include "aicurlperhost.h"
#include "aihttptimeout.h"
#include "llhttpclient.h"
class AIHTTPHeaders;
class AICurlEasyRequestStateMachine;
class AITransferInfo;
namespace AICurlPrivate {
@@ -212,6 +214,7 @@ class CurlEasyRequest : public CurlEasyHandle {
private:
void setPost_raw(U32 size, char const* data, bool keepalive);
public:
void setPut(U32 size, bool keepalive = true);
void setPost(U32 size, bool keepalive = true) { setPost_raw(size, NULL, keepalive); }
void setPost(AIPostFieldPtr const& postdata, U32 size, bool keepalive = true);
void setPost(char const* data, U32 size, bool keepalive = true) { setPost(new AIPostField(data), size, keepalive); }
@@ -297,6 +300,7 @@ class CurlEasyRequest : public CurlEasyHandle {
protected:
curl_slist* mHeaders;
AICurlEasyHandleEvents* mHandleEventsTarget;
U32 mContentLength; // Non-zero if known (only set for PUT and POST).
CURLcode mResult; //AIFIXME: this does not belong in the request object, but belongs in the response object.
AIHTTPTimeoutPolicy const* mTimeoutPolicy;
@@ -319,12 +323,12 @@ class CurlEasyRequest : public CurlEasyHandle {
// Accessor for mTimeout with optional creation of orphaned object (if lockobj != NULL).
LLPointer<curlthread::HTTPTimeout>& httptimeout(void) { if (!mTimeout) { create_timeout_object(); mTimeoutIsOrphan = true; } return mTimeout; }
// Return true if no data has been received on the latest socket (if any) for too long.
bool has_stalled(void) const { return mTimeout && mTimeout->has_stalled(); }
bool has_stalled(void) { return mTimeout && mTimeout->has_stalled(); }
protected:
// This class may only be created as base class of BufferedCurlEasyRequest.
// Throws AICurlNoEasyHandle.
CurlEasyRequest(void) : mHeaders(NULL), mHandleEventsTarget(NULL), mResult(CURLE_FAILED_INIT), mTimeoutPolicy(NULL), mTimeoutIsOrphan(false)
CurlEasyRequest(void) : mHeaders(NULL), mHandleEventsTarget(NULL), mContentLength(0), mResult(CURLE_FAILED_INIT), mTimeoutPolicy(NULL), mTimeoutIsOrphan(false)
#if defined(CWDEBUG) || defined(DEBUG_CURLIO)
, mDebugIsHeadOrGetMethod(false)
#endif
@@ -433,7 +437,7 @@ class BufferedCurlEasyRequest : public CurlEasyRequest {
ThreadSafeBufferedCurlEasyRequest const* get_lockobj(void) const;
// Return true when an error code was received that can occur before the upload finished.
// So far the only such error I've seen is HTTP_BAD_REQUEST.
bool upload_error_status(void) const { return mStatus == HTTP_BAD_REQUEST /*&& mStatus != HTTP_INTERNAL_ERROR*/; }
bool upload_error_status(void) const { return mStatus == HTTP_BAD_REQUEST; }
// Return true when prepRequest was already called and the object has not been
// invalidated as a result of calling timed_out().

View File

@@ -832,8 +832,9 @@ class AICurlThread : public LLThread
{
public:
static AICurlThread* sInstance;
LLMutex mWakeUpMutex;
bool mWakeUpFlag; // Protected by mWakeUpMutex.
LLMutex mWakeUpMutex; // Set while a thread is waking up the curl thread.
LLMutex mWakeUpFlagMutex; // Set when the curl thread is sleeping (in or about to enter select()).
bool mWakeUpFlag; // Protected by mWakeUpFlagMutex.
public:
// MAIN-THREAD
@@ -1067,11 +1068,10 @@ void AICurlThread::cleanup_wakeup_fds(void)
#endif
}
// MAIN-THREAD
// OTHER THREADS
void AICurlThread::wakeup_thread(bool stop_thread)
{
DoutEntering(dc::curl, "AICurlThread::wakeup_thread");
llassert(is_main_thread());
// If we are already exiting the viewer then return immediately.
if (!mRunning)
@@ -1079,12 +1079,29 @@ void AICurlThread::wakeup_thread(bool stop_thread)
// Last time we are run?
if (stop_thread)
mRunning = false;
mRunning = false; // Thread-safe because all other threads were already stopped.
// Note, we do not want this function to be blocking the calling thread; therefore we only use tryLock()s.
// Stop two threads running the following code concurrently.
if (!mWakeUpMutex.tryLock())
{
// If we failed to obtain mWakeUpMutex then another thread is (or was) in AICurlThread::wakeup_thread,
// or curl was holding the lock for a micro second at the start of process_commands.
// In the first case, curl might or might not yet have been woken up because of that, but if it was
// then it could not have started processing the commands yet, because it needs to obtain mWakeUpMutex
// between being woken up and processing the commands.
// Either way, the command that this thread called this function for was already in the queue (it's
// added before this function is called) but the command(s) that another thread called this function
// for were not processed yet. Hence, it's safe to exit here as our command(s) will be processed too.
return;
}
// Try if curl thread is still awake and if so, pass the new commands directly.
if (mWakeUpMutex.tryLock())
if (mWakeUpFlagMutex.tryLock())
{
mWakeUpFlag = true;
mWakeUpFlagMutex.unlock();
mWakeUpMutex.unlock();
return;
}
@@ -1111,7 +1128,10 @@ void AICurlThread::wakeup_thread(bool stop_thread)
{
len = write(mWakeUpFd_in, "!", 1);
if (len == -1 && errno == EAGAIN)
{
mWakeUpMutex.unlock();
return; // Unread characters are still in the pipe, so no need to add more.
}
}
while(len == -1 && errno == EINTR);
if (len == -1)
@@ -1120,6 +1140,12 @@ void AICurlThread::wakeup_thread(bool stop_thread)
}
llassert_always(len == 1);
#endif
// Release the lock here and not sooner, for the sole purpose of making sure
// that not two threads execute the above code concurrently. If the above code
// is thread-safe (maybe it is?) then we could release this lock arbitrarily
// sooner indeed - or even not lock it at all.
mWakeUpMutex.unlock();
}
apr_status_t AICurlThread::join_thread(void)
@@ -1239,6 +1265,16 @@ void AICurlThread::process_commands(AICurlMultiHandle_wat const& multi_handle_w)
{
DoutEntering(dc::curl, "AICurlThread::process_commands(void)");
// Block here until the thread that woke us up released mWakeUpMutex.
// This is necessary to make sure that a third thread added commands
// too then either it will signal us later, or we process those commands
// now, too.
mWakeUpMutex.lock();
// Note that if at THIS point another thread tries to obtain mWakeUpMutex in AICurlThread::wakeup_thread
// and fails, it is ok that it leaves that function without waking us up too: we're awake and
// about to process any commands!
mWakeUpMutex.unlock();
// If we get here then the main thread called wakeup_thread() recently.
for(;;)
{
@@ -1247,9 +1283,9 @@ void AICurlThread::process_commands(AICurlMultiHandle_wat const& multi_handle_w)
command_queue_wat command_queue_w(command_queue);
if (command_queue_w->empty())
{
mWakeUpMutex.lock();
mWakeUpFlagMutex.lock();
mWakeUpFlag = false;
mWakeUpMutex.unlock();
mWakeUpFlagMutex.unlock();
break;
}
// Move the next command from the queue into command_being_processed.
@@ -1312,10 +1348,10 @@ void AICurlThread::run(void)
// Process every command in command_queue before filling the fd_set passed to select().
for(;;)
{
mWakeUpMutex.lock();
mWakeUpFlagMutex.lock();
if (mWakeUpFlag)
{
mWakeUpMutex.unlock();
mWakeUpFlagMutex.unlock();
process_commands(multi_handle_w);
continue;
}
@@ -1324,7 +1360,7 @@ void AICurlThread::run(void)
// wakeup_thread() is also called after setting mRunning to false.
if (!mRunning)
{
mWakeUpMutex.unlock();
mWakeUpFlagMutex.unlock();
break;
}
@@ -1400,7 +1436,7 @@ void AICurlThread::run(void)
#endif
#endif
ready = select(nfds, read_fd_set, write_fd_set, NULL, &timeout);
mWakeUpMutex.unlock();
mWakeUpFlagMutex.unlock();
#ifdef CWDEBUG
#ifdef DEBUG_CURLIO
Dout(dc::finish|cond_error_cf(ready == -1), ready);
@@ -1901,7 +1937,7 @@ void BufferedCurlEasyRequest::setStatusAndReason(U32 status, std::string const&
// Sanity check. If the server replies with a redirect status then we better have that option turned on!
if ((status >= 300 && status < 400) && mResponder && !mResponder->redirect_status_ok())
{
llerrs << "Received " << status << " (" << reason << ") for responder \"" << mTimeoutPolicy->name() << "\" which has no followRedir()!" << llendl;
llerrs << "Received " << status << " (" << reason << ") for responder \"" << mResponder->getName() << "\" which has no followRedir()!" << llendl;
}
}
@@ -1913,7 +1949,7 @@ void BufferedCurlEasyRequest::processOutput(void)
CURLcode code;
AITransferInfo info;
getResult(&code, &info);
if (code == CURLE_OK && mStatus != HTTP_INTERNAL_ERROR)
if (code == CURLE_OK && !is_internal_http_error(mStatus))
{
getinfo(CURLINFO_RESPONSE_CODE, &responseCode);
// If getResult code is CURLE_OK then we should have decoded the first header line ourselves.
@@ -1925,8 +1961,30 @@ void BufferedCurlEasyRequest::processOutput(void)
}
else
{
responseCode = HTTP_INTERNAL_ERROR;
responseReason = (code == CURLE_OK) ? mReason : std::string(curl_easy_strerror(code));
switch (code)
{
case CURLE_FAILED_INIT:
responseCode = HTTP_INTERNAL_ERROR_OTHER;
break;
case CURLE_OPERATION_TIMEDOUT:
responseCode = HTTP_INTERNAL_ERROR_CURL_TIMEOUT;
break;
case CURLE_WRITE_ERROR:
responseCode = HTTP_INTERNAL_ERROR_LOW_SPEED;
break;
default:
responseCode = HTTP_INTERNAL_ERROR_CURL_OTHER;
break;
}
if (responseCode == HTTP_INTERNAL_ERROR_LOW_SPEED)
{
// Rewrite error to something understandable.
responseReason = llformat("Connection to \"%s\" stalled: download speed dropped below %u bytes/s for %u seconds (up till that point, %s received a total of %u bytes). "
"To change these values, go to Advanced --> Debug Settings and change CurlTimeoutLowSpeedLimit and CurlTimeoutLowSpeedTime respectively.",
mResponder->getURL().c_str(), mResponder->getHTTPTimeoutPolicy().getLowSpeedLimit(), mResponder->getHTTPTimeoutPolicy().getLowSpeedTime(),
mResponder->getName(), mResponseTransferedBytes);
}
setopt(CURLOPT_FRESH_CONNECT, TRUE);
}
@@ -2000,8 +2058,9 @@ size_t BufferedCurlEasyRequest::curlReadCallback(char* data, size_t size, size_t
S32 bytes = size * nmemb; // The maximum amount to read.
self_w->mLastRead = self_w->getInput()->readAfter(sChannels.out(), self_w->mLastRead, (U8*)data, bytes);
self_w->mRequestTransferedBytes += bytes; // Accumulate data sent to the server.
llassert(self_w->mRequestTransferedBytes <= self_w->mContentLength); // Content-Length should always be known, and we should never be sending more.
// Timeout administration.
if (self_w->httptimeout()->data_sent(bytes))
if (self_w->httptimeout()->data_sent(bytes, self_w->mRequestTransferedBytes >= self_w->mContentLength))
{
// Transfer timed out. Return CURL_READFUNC_ABORT which will abort with error CURLE_ABORTED_BY_CALLBACK.
return CURL_READFUNC_ABORT;
@@ -2063,7 +2122,7 @@ size_t BufferedCurlEasyRequest::curlHeaderCallback(char* data, size_t size, size
}
// Either way, this status value is not understood (or taken into account).
// Set it to internal error so that the rest of code treats it as an error.
status = HTTP_INTERNAL_ERROR;
status = HTTP_INTERNAL_ERROR_OTHER;
}
self_w->received_HTTP_header();
self_w->setStatusAndReason(status, reason);

View File

@@ -66,7 +66,7 @@ struct AIAccess {
struct AIHTTPTimeoutPolicy {
U16 getReplyDelay(void) const { return 60; }
U16 getLowSpeedTime(void) const { return 30; }
U32 getLowSpeedLimit(void) const { return 56000; }
U32 getLowSpeedLimit(void) const { return 7000; }
static bool connect_timed_out(std::string const&) { return false; }
};
@@ -84,6 +84,11 @@ public:
#include "aihttptimeout.h"
// If this is set, treat dc::curlio as off in the assertion below.
#if defined(CWDEBUG) || defined(DEBUG_CURLIO)
bool gCurlIo;
#endif
namespace AICurlPrivate {
namespace curlthread {
@@ -100,7 +105,7 @@ U64 HTTPTimeout::sClockCount; // Clock count, set once per select() exi
// queued--><--DNS lookup + connect + send headers-->[<--send body (if any)-->]<--replydelay--><--receive headers + body--><--done
// ^ ^ ^ ^ ^ ^
// | | | | | |
bool HTTPTimeout::data_sent(size_t n)
bool HTTPTimeout::data_sent(size_t n, bool finished)
{
// Generate events.
if (!mLowSpeedOn)
@@ -109,7 +114,7 @@ bool HTTPTimeout::data_sent(size_t n)
reset_lowspeed();
}
// Detect low speed.
return lowspeed(n);
return lowspeed(n, finished);
}
// CURL-THREAD
@@ -122,6 +127,7 @@ void HTTPTimeout::reset_lowspeed(void)
{
mLowSpeedClock = sClockCount;
mLowSpeedOn = true;
mLastBytesSent = false; // We're just starting!
mLastSecond = -1; // This causes lowspeed to initialize the rest.
mStalled = (U64)-1; // Stop reply delay timer.
DoutCurl("reset_lowspeed: mLowSpeedClock = " << mLowSpeedClock << "; mStalled = -1");
@@ -169,7 +175,7 @@ bool HTTPTimeout::data_received(size_t n/*,*/
// using CURLOPT_DEBUGFUNCTION. Note that mDebugIsHeadOrGetMethod is only valid when the debug channel 'curlio' is on,
// because it is set in the debug callback function.
// This is also normal if we received a HTTP header with an error status, since that can interrupt our upload.
Debug(llassert(upload_error_status || AICurlEasyRequest_wat(*mLockObj)->mDebugIsHeadOrGetMethod || !dc::curlio.is_on()));
Debug(llassert(upload_error_status || AICurlEasyRequest_wat(*mLockObj)->mDebugIsHeadOrGetMethod || !dc::curlio.is_on() || gCurlIo));
// 'Upload finished' detection failed, generate it now.
upload_finished();
}
@@ -193,9 +199,9 @@ bool HTTPTimeout::data_received(size_t n/*,*/
// queued--><--DNS lookup + connect + send headers-->[<--send body (if any)-->]<--replydelay--><--receive headers + body--><--done
// ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
// | | | | | | | | | | | | | |
bool HTTPTimeout::lowspeed(size_t bytes)
bool HTTPTimeout::lowspeed(size_t bytes, bool finished)
{
//DoutCurlEntering("HTTPTimeout::lowspeed(" << bytes << ")"); commented out... too spammy for normal use.
//DoutCurlEntering("HTTPTimeout::lowspeed(" << bytes << ", " << finished << ")"); commented out... too spammy for normal use.
// The algorithm to determine if we timed out if different from how libcurls CURLOPT_LOW_SPEED_TIME works.
//
@@ -219,6 +225,9 @@ bool HTTPTimeout::lowspeed(size_t bytes)
// and caused something so evil and hard to find that... NEVER AGAIN!
llassert(second >= 0);
// finished should be false until the very last call to this function.
mLastBytesSent = finished;
// If this is the same second as last time, just add the number of bytes to the current bucket.
if (second == mLastSecond)
{
@@ -274,21 +283,39 @@ bool HTTPTimeout::lowspeed(size_t bytes)
mBuckets[mBucket] = bytes;
// Check if we timed out.
U32 const low_speed_limit = mPolicy->getLowSpeedLimit();
U32 mintotalbytes = low_speed_limit * low_speed_time;
U32 const low_speed_limit = mPolicy->getLowSpeedLimit(); // In bytes/s
U32 mintotalbytes = low_speed_limit * low_speed_time; // In bytes.
DoutCurl("Transfered " << mTotalBytes << " bytes in " << llmin(second, (S32)low_speed_time) << " seconds after " << second << " second" << ((second == 1) ? "" : "s") << ".");
if (second >= low_speed_time)
{
DoutCurl("Average transfer rate is " << (mTotalBytes / low_speed_time) << " bytes/s (low speed limit is " << low_speed_limit << " bytes/s)");
if (mTotalBytes < mintotalbytes)
{
if (finished)
{
llwarns <<
#ifdef CWDEBUG
(void*)get_lockobj() << ": "
#endif
"Transfer rate timeout (average transfer rate below " << low_speed_limit <<
" bytes/s for more than " << low_speed_time << " second" << ((low_speed_time == 1) ? "" : "s") <<
") but we just sent the LAST bytes! Waiting an additional 4 seconds." << llendl;
// Lets hope these last bytes will make it and do not time out on transfer speed anymore.
// Just give these bytes 4 more seconds to be written to the socket (after which we'll
// assume that the 'upload finished' detection failed and we'll wait another ReplyDelay
// seconds before finally, actually timing out.
mStalled = sClockCount + 4 / sClockWidth;
DoutCurl("mStalled set to sClockCount (" << sClockCount << ") + " << (mStalled - sClockCount) << " (4 seconds)");
return false;
}
// The average transfer rate over the passed low_speed_time seconds is too low. Abort the transfer.
llwarns <<
#ifdef CWDEBUG
(void*)get_lockobj() << ": "
#endif
"aborting slow connection (average transfer rate below " << low_speed_limit <<
" for more than " << low_speed_time << " second" << ((low_speed_time == 1) ? "" : "s") << ")." << llendl;
" bytes/s for more than " << low_speed_time << " second" << ((low_speed_time == 1) ? "" : "s") << ")." << llendl;
// This causes curl to exit with CURLE_WRITE_ERROR.
return true;
}
}
@@ -327,7 +354,7 @@ bool HTTPTimeout::lowspeed(size_t bytes)
llassert_always(bucket < low_speed_time);
total_bytes -= mBuckets[bucket]; // Empty this bucket.
}
while(total_bytes >= 1); // Use 1 here instead of mintotalbytes, to test that total_bytes indeed always reaches zero.
while(total_bytes >= mintotalbytes);
}
// If this function isn't called again within max_stall_time seconds, we stalled.
mStalled = sClockCount + max_stall_time / sClockWidth;
@@ -377,6 +404,19 @@ void HTTPTimeout::done(AICurlEasyRequest_wat const& curlEasyRequest_w, CURLcode
DoutCurl("done: mStalled set to -1");
}
bool HTTPTimeout::maybe_upload_finished(void)
{
if (!mUploadFinished && mLastBytesSent)
{
// Assume that 'upload finished' detection failed and the server is slow with a reply.
// Switch to waiting for a reply.
upload_finished();
return true;
}
// The upload certainly finished or certainly did not finish.
return false;
}
// Libcurl uses GetTickCount on windows, with a resolution of 10 to 16 ms.
// As a result, we can not assume that namelookup_time == 0 has a special meaning.
#define LOWRESTIMER LL_WINDOWS
@@ -493,6 +533,10 @@ void HTTPTimeout::print_diagnostics(CurlEasyRequest const* curl_easy_request, ch
{
llinfos << "The request upload finished successfully." << llendl;
}
else if (mLastBytesSent)
{
llinfos << "All bytes where sent to libcurl for upload." << llendl;
}
if (mLastSecond > 0 && mLowSpeedOn)
{
llinfos << "The " << (mNothingReceivedYet ? "upload" : "download") << " did last " << mLastSecond << " second" << ((mLastSecond == 1) ? "" : "s") << ", before it timed out." << llendl;

View File

@@ -79,6 +79,7 @@ class HTTPTimeout : public LLRefCount {
U16 mBucket; // The bucket corresponding to mLastSecond.
bool mNothingReceivedYet; // Set when created, reset when the HTML reply header from the server is received.
bool mLowSpeedOn; // Set while uploading or downloading data.
bool mLastBytesSent; // Set when the last bytes were sent to libcurl to be uploaded.
bool mUploadFinished; // Used to keep track of whether upload_finished was called yet.
S32 mLastSecond; // The time at which lowspeed() was last called, in seconds since mLowSpeedClock.
S32 mOverwriteSecond; // The second at which the first bucket of this transfer will be overwritten.
@@ -94,7 +95,7 @@ class HTTPTimeout : public LLRefCount {
public:
HTTPTimeout(AIHTTPTimeoutPolicy const* policy, ThreadSafeBufferedCurlEasyRequest* lock_obj) :
mPolicy(policy), mNothingReceivedYet(true), mLowSpeedOn(false), mUploadFinished(false), mStalled((U64)-1)
mPolicy(policy), mNothingReceivedYet(true), mLowSpeedOn(false), mLastBytesSent(false), mUploadFinished(false), mStalled((U64)-1)
#if defined(CWDEBUG) || defined(DEBUG_CURLIO)
, mLockObj(lock_obj)
#endif
@@ -104,7 +105,7 @@ class HTTPTimeout : public LLRefCount {
void upload_finished(void);
// Called when data is sent. Returns true if transfer timed out.
bool data_sent(size_t n);
bool data_sent(size_t n, bool finished);
// Called when data is received. Returns true if transfer timed out.
bool data_received(size_t n/*,*/ ASSERT_ONLY_COMMA(bool upload_error_status = false));
@@ -112,8 +113,8 @@ class HTTPTimeout : public LLRefCount {
// Called immediately before done() after curl finished, with code.
void done(AICurlEasyRequest_wat const& curlEasyRequest_w, CURLcode code);
// Accessor.
bool has_stalled(void) const { return mStalled < sClockCount; }
// Returns true when we REALLY timed out. Might call upload_finished heuristically.
bool has_stalled(void) { return mStalled < sClockCount && !maybe_upload_finished(); }
// Called from BufferedCurlEasyRequest::processOutput if a timeout occurred.
void print_diagnostics(CurlEasyRequest const* curl_easy_request, char const* eff_url);
@@ -127,11 +128,19 @@ class HTTPTimeout : public LLRefCount {
void reset_lowspeed(void);
// Common low speed detection, Called from data_sent or data_received.
bool lowspeed(size_t bytes);
bool lowspeed(size_t bytes, bool finished = false);
// Return false when we timed out on reply delay, or didn't sent all bytes yet.
// Otherwise calls upload_finished() and return true;
bool maybe_upload_finished(void);
};
} // namespace curlthread
} // namespace AICurlPrivate
#if defined(CWDEBUG) || defined(DEBUG_CURLIO)
extern bool gCurlIo;
#endif
#endif

View File

@@ -88,7 +88,7 @@ U16 const AITP_default_DNS_lookup_grace = 60; // Allow for 60 seconds long DNS
U16 const AITP_default_maximum_connect_time = 10; // Allow the SSL/TLS connection through a proxy, including handshakes, to take up to 10 seconds.
U16 const AITP_default_maximum_reply_delay = 60; // Allow the server 60 seconds to do whatever it has to do before starting to send data.
U16 const AITP_default_low_speed_time = 30; // If a transfer speed drops below AITP_default_low_speed_limit bytes/s for 30 seconds, terminate the transfer.
U32 const AITP_default_low_speed_limit = 56000; // In bytes per second (use for CURLOPT_LOW_SPEED_LIMIT).
U32 const AITP_default_low_speed_limit = 7000; // In bytes per second (use for CURLOPT_LOW_SPEED_LIMIT).
U16 const AITP_default_maximum_curl_transaction = 300; // Allow large files to be transfered over slow connections.
U16 const AITP_default_maximum_total_delay = 600; // Avoid "leaking" by terminating anything that wasn't completed after 10 minutes.
@@ -105,6 +105,7 @@ AIHTTPTimeoutPolicy& AIHTTPTimeoutPolicy::operator=(AIHTTPTimeoutPolicy const& r
mLowSpeedLimit = rhs.mLowSpeedLimit;
mMaximumCurlTransaction = rhs.mMaximumCurlTransaction;
mMaximumTotalDelay = rhs.mMaximumTotalDelay;
changed();
return *this;
}
@@ -136,19 +137,27 @@ struct PolicyOp {
class AIHTTPTimeoutPolicyBase : public AIHTTPTimeoutPolicy {
private:
std::vector<AIHTTPTimeoutPolicy*> mDerived; // Policies derived from this one.
PolicyOp const* mOp; // Operator we applied to base to get ourselves.
public:
AIHTTPTimeoutPolicyBase(U16 dns_lookup_grace, U16 subsequent_connects, U16 reply_delay,
U16 low_speed_time, U32 low_speed_limit,
U16 curl_transaction, U16 total_delay) :
AIHTTPTimeoutPolicy(NULL, dns_lookup_grace, subsequent_connects, reply_delay, low_speed_time, low_speed_limit, curl_transaction, total_delay) { }
AIHTTPTimeoutPolicy(NULL, dns_lookup_grace, subsequent_connects, reply_delay, low_speed_time, low_speed_limit, curl_transaction, total_delay),
mOp(NULL) { }
// Derive base from base.
AIHTTPTimeoutPolicyBase(AIHTTPTimeoutPolicyBase& rhs, PolicyOp const& op) : AIHTTPTimeoutPolicy(rhs) { op.perform(this); }
AIHTTPTimeoutPolicyBase(AIHTTPTimeoutPolicyBase& rhs, PolicyOp& op) : AIHTTPTimeoutPolicy(rhs), mOp(&op) { rhs.derived(this); mOp->perform(this); }
// Called for every derived policy.
void derived(AIHTTPTimeoutPolicy* derived) { mDerived.push_back(derived); }
// Called when our base changed.
/*virtual*/ void base_changed(void);
// Called when we ourselves changed.
/*virtual*/ void changed(void);
// Provide public acces to sDebugSettingsCurlTimeout for this compilation unit.
static AIHTTPTimeoutPolicyBase& getDebugSettingsCurlTimeout(void) { return sDebugSettingsCurlTimeout; }
@@ -157,6 +166,27 @@ class AIHTTPTimeoutPolicyBase : public AIHTTPTimeoutPolicy {
AIHTTPTimeoutPolicyBase& operator=(AIHTTPTimeoutPolicy const& rhs);
};
void AIHTTPTimeoutPolicyBase::base_changed(void)
{
AIHTTPTimeoutPolicy::base_changed();
if (mOp)
mOp->perform(this);
changed();
}
void AIHTTPTimeoutPolicyBase::changed(void)
{
for (std::vector<AIHTTPTimeoutPolicy*>::iterator iter = mDerived.begin(); iter != mDerived.end(); ++iter)
(*iter)->base_changed();
}
void AIHTTPTimeoutPolicy::changed(void)
{
Dout(dc::notice, "Policy \"" << mName << "\" changed into: DNSLookup: " << mDNSLookupGrace << "; Connect: " << mMaximumConnectTime <<
"; ReplyDelay: " << mMaximumReplyDelay << "; LowSpeedTime: " << mLowSpeedTime << "; LowSpeedLimit: " << mLowSpeedLimit <<
"; MaxTransaction: " << mMaximumCurlTransaction << "; MaxTotalDelay:" << mMaximumTotalDelay);
}
AIHTTPTimeoutPolicy::AIHTTPTimeoutPolicy(AIHTTPTimeoutPolicy& base) :
mName(NULL),
mBase(static_cast<AIHTTPTimeoutPolicyBase*>(&base)),
@@ -192,6 +222,19 @@ AIHTTPTimeoutPolicy::AIHTTPTimeoutPolicy(char const* name, AIHTTPTimeoutPolicyBa
mBase->derived(this);
}
void AIHTTPTimeoutPolicy::base_changed(void)
{
// The same as *this = *mBase; but can't use operator= because of an assert that checks that mBase is not set.
mDNSLookupGrace = mBase->mDNSLookupGrace;
mMaximumConnectTime = mBase->mMaximumConnectTime;
mMaximumReplyDelay = mBase->mMaximumReplyDelay;
mLowSpeedTime = mBase->mLowSpeedTime;
mLowSpeedLimit = mBase->mLowSpeedLimit;
mMaximumCurlTransaction = mBase->mMaximumCurlTransaction;
mMaximumTotalDelay = mBase->mMaximumTotalDelay;
changed();
}
//static
void AIHTTPTimeoutPolicy::setDefaultCurlTimeout(AIHTTPTimeoutPolicy const& timeout)
{
@@ -621,7 +664,7 @@ AIHTTPTimeoutPolicyBase HTTPTimeoutPolicy_default(
AITP_default_maximum_curl_transaction,
AITP_default_maximum_total_delay);
//static. Initialized here, but shortly overwritten by Debug Settings.
//static. Initialized here, but shortly overwritten by Debug Settings (except for the crash logger, in which case these are the actual values).
AIHTTPTimeoutPolicyBase AIHTTPTimeoutPolicy::sDebugSettingsCurlTimeout(
AITP_default_DNS_lookup_grace,
AITP_default_maximum_connect_time,
@@ -631,8 +674,8 @@ AIHTTPTimeoutPolicyBase AIHTTPTimeoutPolicy::sDebugSettingsCurlTimeout(
AITP_default_maximum_curl_transaction,
AITP_default_maximum_total_delay);
// Note: Broken compiler doesn't allow as to use temporaries for the Operator ojects,
// so they are instantiated separately.
// Note: All operator objects (Transaction, Connect, etc) must be globals (not temporaries)!
// To enforce this they are passes as reference to non-const (but will never be changed).
// This used to be '5 seconds'.
Transaction transactionOp5s(5);

View File

@@ -110,6 +110,12 @@ class AIHTTPTimeoutPolicy {
// Called when a connect to a hostname timed out.
static bool connect_timed_out(std::string const& hostname);
// Called when the base that this policy was based on changed.
virtual void base_changed(void);
// Called when we ourselves changed.
virtual void changed(void);
protected:
// Used by AIHTTPTimeoutPolicyBase::AIHTTPTimeoutPolicyBase(AIHTTPTimeoutPolicyBase&).
AIHTTPTimeoutPolicy(AIHTTPTimeoutPolicy&);

View File

@@ -195,18 +195,20 @@ public:
LLAssetType::EType mAssetType;
};
static void request(
const std::string& url,
//static
void LLHTTPClient::request(
std::string const& url,
LLURLRequest::ERequestAction method,
Injector* body_injector,
LLHTTPClient::ResponderPtr responder,
AIHTTPHeaders& headers/*,*/
DEBUG_CURLIO_PARAM(EDebugCurl debug),
EKeepAlive keepalive = keep_alive,
bool is_auth = false,
bool no_compression = false,
AIStateMachine* parent = NULL,
AIStateMachine::state_type new_parent_state = 0)
EKeepAlive keepalive,
EDoesAuthentication does_auth,
EAllowCompressedReply allow_compression,
AIStateMachine* parent,
AIStateMachine::state_type new_parent_state,
AIEngine* default_engine)
{
llassert(responder);
@@ -219,7 +221,7 @@ static void request(
LLURLRequest* req;
try
{
req = new LLURLRequest(method, url, body_injector, responder, headers, keepalive, is_auth, no_compression);
req = new LLURLRequest(method, url, body_injector, responder, headers, keepalive, does_auth, allow_compression);
#ifdef DEBUG_CURLIO
req->mCurlEasyRequest.debug(debug);
#endif
@@ -231,7 +233,7 @@ static void request(
return ;
}
req->run(parent, new_parent_state, parent != NULL);
req->run(parent, new_parent_state, parent != NULL, true, default_engine);
}
void LLHTTPClient::getByteRange(std::string const& url, S32 offset, S32 bytes, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug))
@@ -240,22 +242,22 @@ void LLHTTPClient::getByteRange(std::string const& url, S32 offset, S32 bytes, R
{
headers.addHeader("Range", llformat("bytes=%d-%d", offset, offset + bytes - 1));
}
request(url, LLURLRequest::HTTP_GET, NULL, responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug));
request(url, HTTP_GET, NULL, responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug));
}
void LLHTTPClient::head(std::string const& url, ResponderHeadersOnly* responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug))
{
request(url, LLURLRequest::HTTP_HEAD, NULL, responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug));
request(url, HTTP_HEAD, NULL, responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug));
}
void LLHTTPClient::get(std::string const& url, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug))
{
request(url, LLURLRequest::HTTP_GET, NULL, responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug));
request(url, HTTP_GET, NULL, responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug));
}
void LLHTTPClient::getHeaderOnly(std::string const& url, ResponderHeadersOnly* responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug))
{
request(url, LLURLRequest::HTTP_HEAD, NULL, responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug));
request(url, HTTP_HEAD, NULL, responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug));
}
void LLHTTPClient::get(std::string const& url, LLSD const& query, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug))
@@ -302,7 +304,7 @@ AIHTTPTimeoutPolicy const& LLHTTPClient::ResponderBase::getHTTPTimeoutPolicy(voi
void LLHTTPClient::ResponderBase::decode_llsd_body(U32 status, std::string const& reason, LLChannelDescriptors const& channels, buffer_ptr_t const& buffer, LLSD& content)
{
AICurlInterface::Stats::llsd_body_count++;
if (status == HTTP_INTERNAL_ERROR)
if (is_internal_http_error(status))
{
// In case of an internal error (ie, a curl error), a description of the (curl) error is the best we can do.
// In any case, the body if anything was received at all, can not be relied upon.
@@ -353,7 +355,7 @@ void LLHTTPClient::ResponderBase::decode_llsd_body(U32 status, std::string const
void LLHTTPClient::ResponderBase::decode_raw_body(U32 status, std::string const& reason, LLChannelDescriptors const& channels, buffer_ptr_t const& buffer, std::string& content)
{
AICurlInterface::Stats::raw_body_count++;
if (status == HTTP_INTERNAL_ERROR)
if (is_internal_http_error(status))
{
// In case of an internal error (ie, a curl error), a description of the (curl) error is the best we can do.
// In any case, the body if anything was received at all, can not be relied upon.
@@ -487,7 +489,8 @@ void BlockingResponder::wait(void)
// We're the main thread, so we have to give AIStateMachine CPU cycles.
while (!mFinished)
{
AIStateMachine::mainloop();
// AIFIXME: this can probably be removed once curl is detached from the main thread.
gMainThreadEngine.mainloop();
ms_sleep(10);
}
}
@@ -609,11 +612,10 @@ static LLSD blocking_request(
responder->wait();
S32 http_status = HTTP_INTERNAL_ERROR;
LLSD response = LLSD::emptyMap();
CURLcode result = responder->result_code();
S32 http_status = responder->http_status();
http_status = responder->http_status();
bool http_success = http_status >= 200 && http_status < 300;
if (result == CURLE_OK && http_success)
{
@@ -685,17 +687,17 @@ U32 LLHTTPClient::blockingGetRaw(const std::string& url, std::string& body/*,*/
void LLHTTPClient::put(std::string const& url, LLSD const& body, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug))
{
request(url, LLURLRequest::HTTP_PUT, new LLSDInjector(body), responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug));
request(url, HTTP_PUT, new LLSDInjector(body), responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug), no_keep_alive, no_does_authentication, no_allow_compressed_reply);
}
void LLHTTPClient::post(std::string const& url, LLSD const& body, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug), EKeepAlive keepalive, AIStateMachine* parent, AIStateMachine::state_type new_parent_state)
{
request(url, LLURLRequest::HTTP_POST, new LLSDInjector(body), responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug), keepalive, false, false, parent, new_parent_state);
request(url, HTTP_POST, new LLSDInjector(body), responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug), keepalive, no_does_authentication, allow_compressed_reply, parent, new_parent_state);
}
void LLHTTPClient::postXMLRPC(std::string const& url, XMLRPC_REQUEST xmlrpc_request, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug), EKeepAlive keepalive)
{
request(url, LLURLRequest::HTTP_POST, new XMLRPCInjector(xmlrpc_request), responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug), keepalive, true, false); // Does use compression.
request(url, HTTP_POST, new XMLRPCInjector(xmlrpc_request), responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug), keepalive, does_authentication, allow_compressed_reply);
}
void LLHTTPClient::postXMLRPC(std::string const& url, char const* method, XMLRPC_VALUE value, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug), EKeepAlive keepalive)
@@ -706,33 +708,33 @@ void LLHTTPClient::postXMLRPC(std::string const& url, char const* method, XMLRPC
XMLRPC_RequestSetData(xmlrpc_request, value);
// XMLRPCInjector takes ownership of xmlrpc_request and will free it when done.
// LLURLRequest takes ownership of the XMLRPCInjector object and will free it when done.
request(url, LLURLRequest::HTTP_POST, new XMLRPCInjector(xmlrpc_request), responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug), keepalive, true, true); // Does not use compression.
request(url, HTTP_POST, new XMLRPCInjector(xmlrpc_request), responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug), keepalive, does_authentication, no_allow_compressed_reply);
}
void LLHTTPClient::postRaw(std::string const& url, char const* data, S32 size, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug), EKeepAlive keepalive)
{
request(url, LLURLRequest::HTTP_POST, new RawInjector(data, size), responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug), keepalive);
request(url, HTTP_POST, new RawInjector(data, size), responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug), keepalive);
}
void LLHTTPClient::postFile(std::string const& url, std::string const& filename, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug), EKeepAlive keepalive)
{
request(url, LLURLRequest::HTTP_POST, new FileInjector(filename), responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug), keepalive);
request(url, HTTP_POST, new FileInjector(filename), responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug), keepalive);
}
void LLHTTPClient::postFile(std::string const& url, LLUUID const& uuid, LLAssetType::EType asset_type, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug), EKeepAlive keepalive)
{
request(url, LLURLRequest::HTTP_POST, new VFileInjector(uuid, asset_type), responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug), keepalive);
request(url, HTTP_POST, new VFileInjector(uuid, asset_type), responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug), keepalive);
}
// static
void LLHTTPClient::del(std::string const& url, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug))
{
request(url, LLURLRequest::HTTP_DELETE, NULL, responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug));
request(url, HTTP_DELETE, NULL, responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug));
}
// static
void LLHTTPClient::move(std::string const& url, std::string const& destination, ResponderPtr responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug))
{
headers.addHeader("Destination", destination);
request(url, LLURLRequest::HTTP_MOVE, NULL, responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug));
request(url, HTTP_MOVE, NULL, responder, headers/*,*/ DEBUG_CURLIO_PARAM(debug));
}

View File

@@ -46,10 +46,13 @@ class AIHTTPTimeoutPolicy;
class LLBufferArray;
class LLChannelDescriptors;
class AIStateMachine;
class Injector;
class AIEngine;
extern AIHTTPTimeoutPolicy responderIgnore_timeout;
typedef struct _xmlrpc_request* XMLRPC_REQUEST;
typedef struct _xmlrpc_value* XMLRPC_VALUE;
extern AIEngine gMainThreadEngine;
// Output parameter of AICurlPrivate::CurlEasyRequest::getResult.
// Used in XMLRPCResponder.
@@ -72,6 +75,16 @@ enum EKeepAlive {
keep_alive
};
enum EDoesAuthentication {
no_does_authentication = 0,
does_authentication
};
enum EAllowCompressedReply {
no_allow_compressed_reply = 0,
allow_compressed_reply
};
#ifdef DEBUG_CURLIO
enum EDebugCurl {
debug_off = 0,
@@ -84,6 +97,20 @@ enum EDebugCurl {
class LLHTTPClient {
public:
/**
* @brief This enumeration is for specifying the type of request.
*/
enum ERequestAction
{
INVALID,
HTTP_HEAD,
HTTP_GET,
HTTP_PUT,
HTTP_POST,
HTTP_DELETE,
HTTP_MOVE, // Caller will need to set 'Destination' header
REQUEST_ACTION_COUNT
};
/** @name Responder base classes */
//@{
@@ -363,7 +390,7 @@ public:
}
public:
LegacyPolledResponder(void) : mStatus(HTTP_INTERNAL_ERROR) { }
LegacyPolledResponder(void) : mStatus(HTTP_INTERNAL_ERROR_OTHER) { }
// Accessors.
U32 http_status(void) const { return mStatus; }
@@ -393,6 +420,21 @@ public:
//@}
/** General API to request a transfer. */
static void request(
std::string const& url,
ERequestAction method,
Injector* body_injector,
ResponderPtr responder,
AIHTTPHeaders& headers/*,*/
DEBUG_CURLIO_PARAM(EDebugCurl debug),
EKeepAlive keepalive = keep_alive,
EDoesAuthentication does_auth = no_does_authentication,
EAllowCompressedReply allow_compression = allow_compressed_reply,
AIStateMachine* parent = NULL,
/*AIStateMachine::state_type*/ U32 new_parent_state = 0,
AIEngine* default_engine = &gMainThreadEngine);
/** @name non-blocking API */
//@{
static void head(std::string const& url, ResponderHeadersOnly* responder, AIHTTPHeaders& headers/*,*/ DEBUG_CURLIO_PARAM(EDebugCurl debug = debug_off));

View File

@@ -46,7 +46,6 @@
#include "llscopedvolatileaprpool.h"
#include "llfasttimer.h"
#include "message.h"
static const U32 HTTP_STATUS_PIPE_ERROR = 499;
/**
* String constants
@@ -60,7 +59,7 @@ const std::string CONTEXT_TRANSFERED_BYTES("transfered_bytes");
// static
std::string LLURLRequest::actionAsVerb(LLURLRequest::ERequestAction action)
{
static int const array_size = HTTP_MOVE + 1; // INVALID == 0
static int const array_size = LLHTTPClient::REQUEST_ACTION_COUNT; // INVALID == 0
static char const* const VERBS[array_size] =
{
"(invalid)",
@@ -71,14 +70,14 @@ std::string LLURLRequest::actionAsVerb(LLURLRequest::ERequestAction action)
"DELETE",
"MOVE"
};
return VERBS[action >= array_size ? INVALID : action];
return VERBS[action >= array_size ? LLHTTPClient::INVALID : action];
}
// This might throw AICurlNoEasyHandle.
LLURLRequest::LLURLRequest(LLURLRequest::ERequestAction action, std::string const& url, Injector* body,
LLHTTPClient::ResponderPtr responder, AIHTTPHeaders& headers, bool keepalive, bool is_auth, bool no_compression) :
mAction(action), mURL(url), mKeepAlive(keepalive), mIsAuth(is_auth), mNoCompression(no_compression),
mBody(body), mResponder(responder), mHeaders(headers)
LLHTTPClient::ResponderPtr responder, AIHTTPHeaders& headers, bool keepalive, bool is_auth, bool compression) :
mAction(action), mURL(url), mKeepAlive(keepalive), mIsAuth(is_auth), mNoCompression(!compression),
mBody(body), mResponder(responder), mHeaders(headers), mResponderNameCache(responder ? responder->getName() : "<uninitialized>")
{
}
@@ -93,13 +92,13 @@ void LLURLRequest::initialize_impl(void)
useProxy(false);
}
if (mAction == HTTP_PUT || mAction == HTTP_POST)
if (mAction == LLHTTPClient::HTTP_PUT || mAction == LLHTTPClient::HTTP_POST)
{
// If the Content-Type header was passed in we defer to the caller's wisdom,
// but if they did not specify a Content-Type, then ask the injector.
mHeaders.addHeader("Content-Type", mBody->contentType(), AIHTTPHeaders::keep_existing_header);
}
else if (mAction != HTTP_HEAD)
else if (mAction != LLHTTPClient::HTTP_HEAD)
{
// Check to see if we have already set Accept or not. If no one
// set it, set it to application/llsd+xml since that's what we
@@ -107,7 +106,7 @@ void LLURLRequest::initialize_impl(void)
mHeaders.addHeader("Accept", "application/llsd+xml", AIHTTPHeaders::keep_existing_header);
}
if (mAction == HTTP_POST && gMessageSystem)
if (mAction == LLHTTPClient::HTTP_POST && gMessageSystem)
{
mHeaders.addHeader("X-SecondLife-UDP-Listen-Port", llformat("%d", gMessageSystem->mPort));
}
@@ -199,12 +198,12 @@ bool LLURLRequest::configure(AICurlEasyRequest_wat const& curlEasyRequest_w)
{
switch(mAction)
{
case HTTP_HEAD:
case LLHTTPClient::HTTP_HEAD:
curlEasyRequest_w->setopt(CURLOPT_NOBODY, 1);
rv = true;
break;
case HTTP_GET:
case LLHTTPClient::HTTP_GET:
curlEasyRequest_w->setopt(CURLOPT_HTTPGET, 1);
// Set Accept-Encoding to allow response compression
@@ -212,18 +211,18 @@ bool LLURLRequest::configure(AICurlEasyRequest_wat const& curlEasyRequest_w)
rv = true;
break;
case HTTP_PUT:
{
// Disable the expect http 1.1 extension. POST and PUT default
// to using this, causing the broken server to get confused.
curlEasyRequest_w->addHeader("Expect:");
curlEasyRequest_w->setopt(CURLOPT_UPLOAD, 1);
curlEasyRequest_w->setopt(CURLOPT_INFILESIZE, mBodySize);
case LLHTTPClient::HTTP_PUT:
// Set the handle for an http put
curlEasyRequest_w->setPut(mBodySize, mKeepAlive);
// Set Accept-Encoding to allow response compression
curlEasyRequest_w->setoptString(CURLOPT_ENCODING, mNoCompression ? "identity" : "");
rv = true;
break;
}
case HTTP_POST:
{
case LLHTTPClient::HTTP_POST:
// Set the handle for an http post
curlEasyRequest_w->setPost(mBodySize, mKeepAlive);
@@ -231,14 +230,14 @@ bool LLURLRequest::configure(AICurlEasyRequest_wat const& curlEasyRequest_w)
curlEasyRequest_w->setoptString(CURLOPT_ENCODING, mNoCompression ? "identity" : "");
rv = true;
break;
}
case HTTP_DELETE:
case LLHTTPClient::HTTP_DELETE:
// Set the handle for an http post
curlEasyRequest_w->setoptString(CURLOPT_CUSTOMREQUEST, "DELETE");
rv = true;
break;
case HTTP_MOVE:
case LLHTTPClient::HTTP_MOVE:
// Set the handle for an http post
curlEasyRequest_w->setoptString(CURLOPT_CUSTOMREQUEST, "MOVE");
rv = true;
@@ -259,3 +258,33 @@ bool LLURLRequest::configure(AICurlEasyRequest_wat const& curlEasyRequest_w)
}
return rv;
}
// Called from AIStateMachine::mainloop, but put here because we don't want to include llurlrequest.h there of course.
void print_statemachine_diagnostics(U64 total_clocks, U64 max_delta, AIEngine::queued_type::const_reference slowest_element)
{
AIStateMachine const& slowest_state_machine = slowest_element.statemachine();
LLURLRequest const* request = dynamic_cast<LLURLRequest const*>(&slowest_state_machine);
F64 const tfactor = 1000 / calc_clock_frequency();
std::ostringstream msg;
if (total_clocks > max_delta)
{
msg << "AIStateMachine::mainloop did run for " << (total_clocks * tfactor) << " ms. The slowest ";
}
else
{
msg << "AIStateMachine::mainloop: A ";
}
msg << "state machine ";
if (request)
{
msg << "(" << request->getResponderName() << ") ";
}
msg << "ran for " << (max_delta * tfactor) << " ms";
if (slowest_state_machine.getRuntime() > max_delta)
{
msg << " (" << (slowest_state_machine.getRuntime() * tfactor) << " ms in total now)";
}
msg << ".";
llwarns << msg.str() << llendl;
}

View File

@@ -51,20 +51,7 @@ class Injector
class LLURLRequest : public AICurlEasyRequestStateMachine {
public:
/**
* @brief This enumeration is for specifying the type of request.
*/
enum ERequestAction
{
INVALID,
HTTP_HEAD,
HTTP_GET,
HTTP_PUT,
HTTP_POST,
HTTP_DELETE,
HTTP_MOVE, // Caller will need to set 'Destination' header
REQUEST_ACTION_COUNT
};
typedef LLHTTPClient::ERequestAction ERequestAction;
/**
* @brief Turn the request action into an http verb.
@@ -79,6 +66,11 @@ class LLURLRequest : public AICurlEasyRequestStateMachine {
*/
LLURLRequest(ERequestAction action, std::string const& url, Injector* body, LLHTTPClient::ResponderPtr responder, AIHTTPHeaders& headers, bool keepalive, bool is_auth, bool no_compression);
/**
* @brief Cached value of responder->getName() as passed to the constructor.
*/
char const* getResponderName(void) const { return mResponderNameCache; }
protected:
// Call abort(), not delete.
/*virtual*/ ~LLURLRequest() { }
@@ -118,6 +110,7 @@ class LLURLRequest : public AICurlEasyRequestStateMachine {
U32 mBodySize;
LLHTTPClient::ResponderPtr mResponder;
AIHTTPHeaders mHeaders;
char const* mResponderNameCache;
protected:
// Handle initializing the object.