Timer, time out, and clean up improvements.
This commit is contained in:
@@ -371,6 +371,9 @@ void LLCrashLogger::updateApplication(const std::string& message)
|
||||
|
||||
bool LLCrashLogger::init()
|
||||
{
|
||||
// Initialized curl
|
||||
AICurlInterface::initCurl();
|
||||
|
||||
// We assume that all the logs we're looking for reside on the current drive
|
||||
gDirUtilp->initAppDirs("SecondLife");
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ enum gSSLlib_type {
|
||||
// No locking needed: initialized before threads are created, and subsequently only read.
|
||||
gSSLlib_type gSSLlib;
|
||||
bool gSetoptParamsNeedDup;
|
||||
void (*statemachines_flush_hook)(void);
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -264,9 +265,9 @@ static unsigned int encoded_version(int major, int minor, int patch)
|
||||
namespace AICurlInterface {
|
||||
|
||||
// MAIN-THREAD
|
||||
void initCurl(F32 curl_request_timeout, S32 max_number_handles)
|
||||
void initCurl(void (*flush_hook)())
|
||||
{
|
||||
DoutEntering(dc::curl, "AICurlInterface::initCurl(" << curl_request_timeout << ", " << max_number_handles << ")");
|
||||
DoutEntering(dc::curl, "AICurlInterface::initCurl(" << (void*)flush_hook << ")");
|
||||
|
||||
llassert(LLThread::getRunning() == 0); // We must not call curl_global_init unless we are the only thread.
|
||||
CURLcode res = curl_global_init(CURL_GLOBAL_ALL);
|
||||
@@ -340,17 +341,24 @@ void initCurl(F32 curl_request_timeout, S32 max_number_handles)
|
||||
}
|
||||
llassert_always(!gSetoptParamsNeedDup); // Might add support later.
|
||||
}
|
||||
|
||||
// Called in cleanupCurl.
|
||||
statemachines_flush_hook = flush_hook;
|
||||
}
|
||||
|
||||
// MAIN-THREAD
|
||||
void cleanupCurl(void)
|
||||
{
|
||||
using AICurlPrivate::stopCurlThread;
|
||||
using AICurlPrivate::curlThreadIsRunning;
|
||||
using namespace AICurlPrivate;
|
||||
|
||||
DoutEntering(dc::curl, "AICurlInterface::cleanupCurl()");
|
||||
|
||||
stopCurlThread();
|
||||
if (CurlMultiHandle::getTotalMultiHandles() != 0)
|
||||
llwarns << "Not all CurlMultiHandle objects were destroyed!" << llendl;
|
||||
if (statemachines_flush_hook)
|
||||
(*statemachines_flush_hook)();
|
||||
Stats::print();
|
||||
ssl_cleanup();
|
||||
|
||||
llassert(LLThread::getRunning() <= (curlThreadIsRunning() ? 1 : 0)); // We must not call curl_global_cleanup unless we are the only thread left.
|
||||
@@ -547,6 +555,9 @@ void CurlEasyHandle::handle_easy_error(CURLcode code)
|
||||
|
||||
// Throws AICurlNoEasyHandle.
|
||||
CurlEasyHandle::CurlEasyHandle(void) : mActiveMultiHandle(NULL), mErrorBuffer(NULL)
|
||||
#ifdef SHOW_ASSERT
|
||||
, mRemovedPerCommand(true)
|
||||
#endif
|
||||
{
|
||||
mEasyHandle = curl_easy_init();
|
||||
#if 0
|
||||
@@ -566,6 +577,22 @@ CurlEasyHandle::CurlEasyHandle(void) : mActiveMultiHandle(NULL), mErrorBuffer(NU
|
||||
}
|
||||
}
|
||||
|
||||
#if 0 // Not used
|
||||
CurlEasyHandle::CurlEasyHandle(CurlEasyHandle const& orig) : mActiveMultiHandle(NULL), mErrorBuffer(NULL)
|
||||
#ifdef SHOW_ASSERT
|
||||
, mRemovedPerCommand(true)
|
||||
#endif
|
||||
{
|
||||
mEasyHandle = curl_easy_duphandle(orig.mEasyHandle);
|
||||
Stats::easy_init_calls++;
|
||||
if (!mEasyHandle)
|
||||
{
|
||||
Stats::easy_init_errors++;
|
||||
throw AICurlNoEasyHandle("curl_easy_duphandle() returned NULL");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
CurlEasyHandle::~CurlEasyHandle()
|
||||
{
|
||||
llassert(!mActiveMultiHandle);
|
||||
@@ -1075,24 +1102,25 @@ CurlResponderBuffer::~CurlResponderBuffer()
|
||||
curl_easy_request_w->revokeCallbacks();
|
||||
if (mResponder)
|
||||
{
|
||||
llwarns << "Calling ~CurlResponderBuffer() with active responder!" << llendl;
|
||||
llassert(false); // Does this ever happen? And if so, what does it mean?
|
||||
// FIXME: Does this really mean it timed out?
|
||||
mResponder->completedRaw(HTTP_REQUEST_TIME_OUT, "Request timeout, aborted.", sChannels, mOutput);
|
||||
mResponder = NULL;
|
||||
// If the responder is still alive, then that means that CurlResponderBuffer::processOutput was
|
||||
// never called, which means that the removed_from_multi_handle event never happened.
|
||||
// This is definitely an internal error as it can only happen when libcurl is too slow,
|
||||
// in which case AICurlEasyRequestStateMachine::mTimer times out, but that already
|
||||
// calls CurlResponderBuffer::timed_out(). So, this really should never happen.
|
||||
llerrs << "Calling ~CurlResponderBuffer() with active responder!" << llendl;
|
||||
timed_out();
|
||||
}
|
||||
}
|
||||
|
||||
void CurlResponderBuffer::timed_out(void)
|
||||
{
|
||||
mResponder->completedRaw(HTTP_INTERNAL_ERROR, "Request timeout, aborted.", sChannels, mOutput);
|
||||
mResponder = NULL;
|
||||
}
|
||||
|
||||
void CurlResponderBuffer::resetState(AICurlEasyRequest_wat& curl_easy_request_w)
|
||||
{
|
||||
if (mResponder)
|
||||
{
|
||||
llwarns << "Calling CurlResponderBuffer::resetState() for active easy handle!" << llendl;
|
||||
llassert(false); // Does this ever happen? And if so, what does it mean?
|
||||
// FIXME: Does this really mean it timed out?
|
||||
mResponder->completedRaw(HTTP_REQUEST_TIME_OUT, "Request timeout, aborted.", sChannels, mOutput);
|
||||
mResponder = NULL;
|
||||
}
|
||||
llassert(!mResponder);
|
||||
|
||||
curl_easy_request_w->resetState();
|
||||
|
||||
@@ -1287,8 +1315,6 @@ CurlMultiHandle::~CurlMultiHandle()
|
||||
Stats::multi_calls++;
|
||||
int total = --sTotalMultiHandles;
|
||||
Dout(dc::curl, "Called CurlMultiHandle::~CurlMultiHandle() [" << (void*)this << "], " << total << " remaining.");
|
||||
if (total == 0)
|
||||
Stats::print();
|
||||
}
|
||||
|
||||
} // namespace AICurlPrivate
|
||||
|
||||
@@ -90,7 +90,7 @@ struct TransferInfo {
|
||||
|
||||
// Called once at start of application (from newview/llappviewer.cpp by main thread (before threads are created)),
|
||||
// with main purpose to initialize curl.
|
||||
void initCurl(F32 curl_request_timeout = 120.f, S32 max_number_handles = 256);
|
||||
void initCurl(void (*)(void) = NULL);
|
||||
|
||||
// Called once at start of application (from LLAppViewer::initThreads), starts AICurlThread.
|
||||
void startCurlThread(void);
|
||||
|
||||
@@ -79,7 +79,7 @@ class CurlEasyHandle : public boost::noncopyable, protected AICurlEasyHandleEven
|
||||
CURLcode setopt(CURLoption option, BUILTIN parameter);
|
||||
|
||||
// Clone a libcurl session handle using all the options previously set.
|
||||
CurlEasyHandle(CurlEasyHandle const& orig) : mEasyHandle(curl_easy_duphandle(orig.mEasyHandle)), mActiveMultiHandle(NULL), mErrorBuffer(NULL) { }
|
||||
//CurlEasyHandle(CurlEasyHandle const& orig);
|
||||
|
||||
// URL encode/decode the given string.
|
||||
char* escape(char* url, int length);
|
||||
@@ -102,6 +102,10 @@ class CurlEasyHandle : public boost::noncopyable, protected AICurlEasyHandleEven
|
||||
CURL* mEasyHandle;
|
||||
CURLM* mActiveMultiHandle;
|
||||
char* mErrorBuffer;
|
||||
#ifdef SHOW_ASSERT
|
||||
public:
|
||||
bool mRemovedPerCommand; // Set if mActiveMultiHandle was reset as per command from the main thread.
|
||||
#endif
|
||||
|
||||
private:
|
||||
// This should only be called from MultiHandle; add/remove an easy handle to/from a multi handle.
|
||||
@@ -279,6 +283,9 @@ class CurlResponderBuffer : protected AICurlEasyHandleEvents {
|
||||
std::stringstream& getHeaderOutput() { return mHeaderOutput; }
|
||||
LLIOPipe::buffer_ptr_t& getOutput() { return mOutput; }
|
||||
|
||||
// Called if libcurl doesn't deliver within CurlRequestTimeOut seconds.
|
||||
void timed_out(void);
|
||||
|
||||
// Called after removed_from_multi_handle was called.
|
||||
void processOutput(AICurlEasyRequest_wat& curl_easy_request_w);
|
||||
|
||||
|
||||
@@ -702,7 +702,7 @@ void AICurlThread::wakeup(AICurlMultiHandle_wat const& multi_handle_w)
|
||||
multi_handle_w->add_easy_request(AICurlEasyRequest(command_being_processed_r->easy_request()));
|
||||
break;
|
||||
case cmd_remove:
|
||||
multi_handle_w->remove_easy_request(AICurlEasyRequest(command_being_processed_r->easy_request()));
|
||||
multi_handle_w->remove_easy_request(AICurlEasyRequest(command_being_processed_r->easy_request()), true);
|
||||
break;
|
||||
}
|
||||
// Done processing.
|
||||
@@ -858,6 +858,8 @@ MultiHandle::MultiHandle(void) : mHandleAddedOrRemoved(false), mPrevRunningHandl
|
||||
|
||||
MultiHandle::~MultiHandle()
|
||||
{
|
||||
llinfos << "Destructing MultiHandle with " << mAddedEasyRequests.size() << " active curl easy handles." << llendl;
|
||||
|
||||
// This thread was terminated.
|
||||
// Curl demands that all handles are removed from the multi session handle before calling curl_multi_cleanup.
|
||||
for(addedEasyRequests_type::iterator iter = mAddedEasyRequests.begin(); iter != mAddedEasyRequests.end(); iter = mAddedEasyRequests.begin())
|
||||
@@ -958,7 +960,7 @@ CURLMcode MultiHandle::add_easy_request(AICurlEasyRequest const& easy_request)
|
||||
return ret;
|
||||
}
|
||||
|
||||
CURLMcode MultiHandle::remove_easy_request(AICurlEasyRequest const& easy_request)
|
||||
CURLMcode MultiHandle::remove_easy_request(AICurlEasyRequest const& easy_request, bool as_per_command)
|
||||
{
|
||||
addedEasyRequests_type::iterator iter = mAddedEasyRequests.find(easy_request);
|
||||
if (iter == mAddedEasyRequests.end())
|
||||
@@ -967,6 +969,9 @@ CURLMcode MultiHandle::remove_easy_request(AICurlEasyRequest const& easy_request
|
||||
{
|
||||
AICurlEasyRequest_wat curl_easy_request_w(**iter);
|
||||
res = curl_easy_request_w->remove_handle_from_multi(curl_easy_request_w, mMultiHandle);
|
||||
#ifdef SHOW_ASSERT
|
||||
curl_easy_request_w->mRemovedPerCommand = as_per_command;
|
||||
#endif
|
||||
}
|
||||
mAddedEasyRequests.erase(iter);
|
||||
mHandleAddedOrRemoved = true;
|
||||
@@ -1173,8 +1178,11 @@ void AICurlEasyRequest::removeRequest(void)
|
||||
}
|
||||
else
|
||||
{
|
||||
// May not already have been removed from multi session handle.
|
||||
llassert(AICurlEasyRequest_wat(*get())->active());
|
||||
// May not already have been removed from multi session handle as per command from the main thread (through this function thus).
|
||||
{
|
||||
AICurlEasyRequest_wat curl_easy_request_w(*get());
|
||||
llassert(curl_easy_request_w->active() || !curl_easy_request_w->mRemovedPerCommand);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -123,7 +123,7 @@ class MultiHandle : public CurlMultiHandle
|
||||
|
||||
// Add/remove an easy handle to/from a multi session.
|
||||
CURLMcode add_easy_request(AICurlEasyRequest const& easy_request);
|
||||
CURLMcode remove_easy_request(AICurlEasyRequest const& easy_request);
|
||||
CURLMcode remove_easy_request(AICurlEasyRequest const& easy_request, bool as_per_command = false);
|
||||
|
||||
// Reads/writes available data from a particular socket (non-blocking).
|
||||
CURLMcode socket_action(curl_socket_t sockfd, int ev_bitmask);
|
||||
|
||||
@@ -612,7 +612,7 @@ bool LLAppViewer::init()
|
||||
initLogging();
|
||||
|
||||
// Curl must be initialized before any thread is running.
|
||||
AICurlInterface::initCurl();
|
||||
AICurlInterface::initCurl(&AIStateMachine::flush);
|
||||
|
||||
// Logging is initialized. Now it's safe to start the error thread.
|
||||
startErrorThread();
|
||||
|
||||
@@ -1613,7 +1613,6 @@ void LLMeshUploadThread::doWholeModelUpload()
|
||||
LLSD body = full_model_data["asset_resources"];
|
||||
dump_llsd_to_file(body,make_dump_name("whole_model_body_",dump_num));
|
||||
LLCurlRequest::headers_t headers;
|
||||
//FIXME: this might throw AICurlNoEasyHandle
|
||||
mCurlRequest->post(mWholeModelUploadURL, headers, body,
|
||||
new LLWholeModelUploadResponder(this, full_model_data, mUploadObserverHandle), mMeshUploadTimeOut);
|
||||
do
|
||||
@@ -1645,7 +1644,6 @@ void LLMeshUploadThread::requestWholeModelFee()
|
||||
|
||||
mPendingUploads++;
|
||||
LLCurlRequest::headers_t headers;
|
||||
//FIXME: this might throw AICurlNoEasyHandle
|
||||
mCurlRequest->post(mWholeModelFeeCapability, headers, model_data,
|
||||
new LLWholeModelFeeResponder(this,model_data, mFeeObserverHandle), mMeshUploadTimeOut);
|
||||
|
||||
|
||||
@@ -479,12 +479,17 @@ void LLXMLRPCTransaction::Impl::setCurlStatus(CURLcode code)
|
||||
size_t LLXMLRPCTransaction::Impl::curlDownloadCallback(
|
||||
char* data, size_t size, size_t nmemb, void* user_data)
|
||||
{
|
||||
DoutEntering(dc::curl, "LLXMLRPCTransaction::Impl::curlDownloadCallback(\"" << buf2str(data, size * nmemb) << "\", " << size << ", " << nmemb << ", " << user_data << ")");
|
||||
|
||||
Impl& impl(*(Impl*)user_data);
|
||||
|
||||
size_t n = size * nmemb;
|
||||
|
||||
#ifdef CWDEBUG
|
||||
if (n < 80)
|
||||
Dout(dc::curl, "Entering LLXMLRPCTransaction::Impl::curlDownloadCallback(\"" << buf2str(data, n) << "\", " << size << ", " << nmemb << ", " << user_data << ")");
|
||||
else
|
||||
Dout(dc::curl, "Entering LLXMLRPCTransaction::Impl::curlDownloadCallback(\"" << buf2str(data, 40) << "\"...\"" << buf2str(data + n - 40, 40) << "\", " << size << ", " << nmemb << ", " << user_data << ")");
|
||||
#endif
|
||||
|
||||
impl.mResponseText.append(data, n);
|
||||
|
||||
if (impl.mStatus == LLXMLRPCTransaction::StatusStarted)
|
||||
|
||||
@@ -35,9 +35,11 @@
|
||||
enum curleasyrequeststatemachine_state_type {
|
||||
AICurlEasyRequestStateMachine_addRequest = AIStateMachine::max_state,
|
||||
AICurlEasyRequestStateMachine_waitAdded,
|
||||
AICurlEasyRequestStateMachine_waitRemoved,
|
||||
AICurlEasyRequestStateMachine_timedOut, // Only _finished has a higher priority than _timedOut.
|
||||
AICurlEasyRequestStateMachine_finished
|
||||
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_removed_after_finished
|
||||
};
|
||||
|
||||
char const* AICurlEasyRequestStateMachine::state_str_impl(state_type run_state) const
|
||||
@@ -46,9 +48,11 @@ char const* AICurlEasyRequestStateMachine::state_str_impl(state_type run_state)
|
||||
{
|
||||
AI_CASE_RETURN(AICurlEasyRequestStateMachine_addRequest);
|
||||
AI_CASE_RETURN(AICurlEasyRequestStateMachine_waitAdded);
|
||||
AI_CASE_RETURN(AICurlEasyRequestStateMachine_waitRemoved);
|
||||
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);
|
||||
}
|
||||
return "UNKNOWN STATE";
|
||||
}
|
||||
@@ -60,38 +64,57 @@ void AICurlEasyRequestStateMachine::initialize_impl(void)
|
||||
llassert(curlEasyRequest_w->is_finalized()); // Call finalizeRequest(url) before calling run().
|
||||
curlEasyRequest_w->send_events_to(this);
|
||||
}
|
||||
mAdded = false;
|
||||
mTimedOut = false;
|
||||
mFinished = false;
|
||||
mHandled = false;
|
||||
set_state(AICurlEasyRequestStateMachine_addRequest);
|
||||
}
|
||||
|
||||
// CURL-THREAD
|
||||
void AICurlEasyRequestStateMachine::added_to_multi_handle(AICurlEasyRequest_wat&)
|
||||
{
|
||||
set_state(AICurlEasyRequestStateMachine_waitRemoved);
|
||||
set_state(AICurlEasyRequestStateMachine_added);
|
||||
}
|
||||
|
||||
// CURL-THREAD
|
||||
void AICurlEasyRequestStateMachine::finished(AICurlEasyRequest_wat&)
|
||||
{
|
||||
mFinished = true;
|
||||
set_state(AICurlEasyRequestStateMachine_finished);
|
||||
}
|
||||
|
||||
// CURL-THREAD
|
||||
void AICurlEasyRequestStateMachine::removed_from_multi_handle(AICurlEasyRequest_wat&)
|
||||
{
|
||||
set_state(AICurlEasyRequestStateMachine_finished);
|
||||
set_state(mFinished ? AICurlEasyRequestStateMachine_removed_after_finished : AICurlEasyRequestStateMachine_removed);
|
||||
}
|
||||
|
||||
void AICurlEasyRequestStateMachine::multiplex_impl(void)
|
||||
{
|
||||
switch (mRunState)
|
||||
mSetStateLock.lock();
|
||||
state_type current_state = mRunState;
|
||||
mSetStateLock.unlock();
|
||||
switch (current_state)
|
||||
{
|
||||
case AICurlEasyRequestStateMachine_addRequest:
|
||||
{
|
||||
set_state(AICurlEasyRequestStateMachine_waitAdded);
|
||||
idle(AICurlEasyRequestStateMachine_waitAdded); // Wait till AICurlEasyRequestStateMachine::added_to_multi_handle() is called.
|
||||
idle(AICurlEasyRequestStateMachine_waitAdded); // 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.
|
||||
mCurlEasyRequest.addRequest();
|
||||
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
|
||||
// 1) AICurlEasyRequestStateMachine_waitAdded (idle)
|
||||
// 2) AICurlEasyRequestStateMachine_added (running)
|
||||
// 3) AICurlEasyRequestStateMachine_finished (running)
|
||||
// 4) AICurlEasyRequestStateMachine_removed_after_finished (running)
|
||||
|
||||
// Set an inactivity timer.
|
||||
// This shouldn't really be necessary, except in the case of a bug
|
||||
@@ -99,31 +122,85 @@ void AICurlEasyRequestStateMachine::multiplex_impl(void)
|
||||
static LLCachedControl<F32> CurlRequestTimeOut("CurlRequestTimeOut", 40.f);
|
||||
mTimer = new AIPersistentTimer; // Do not delete timer upon expiration.
|
||||
mTimer->setInterval(CurlRequestTimeOut);
|
||||
mTimer->run(this, AICurlEasyRequestStateMachine_timedOut);
|
||||
|
||||
mTimer->run(this, AICurlEasyRequestStateMachine_timedOut, false, false);
|
||||
break;
|
||||
}
|
||||
case AICurlEasyRequestStateMachine_waitRemoved:
|
||||
case AICurlEasyRequestStateMachine_added:
|
||||
{
|
||||
idle(AICurlEasyRequestStateMachine_waitRemoved); // Wait till AICurlEasyRequestStateMachine::removed_from_multi_handle() is called.
|
||||
// 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)
|
||||
break;
|
||||
}
|
||||
case AICurlEasyRequestStateMachine_timedOut:
|
||||
{
|
||||
// Libcurl failed to end on error(?)... abort operation in order to free
|
||||
// this curl easy handle and to notify the application that it didn't work.
|
||||
abort();
|
||||
// It is possible that exactly at this point the state changes into
|
||||
// AICurlEasyRequestStateMachine_finished, with as result that mTimedOut
|
||||
// is set while we will continue with that state. Hence that mTimedOut
|
||||
// is explicitly reset in that state.
|
||||
|
||||
// Libcurl failed to deliver within a reasonable time... Abort operation in order
|
||||
// to free this curl easy handle and to notify the application that it didn't work.
|
||||
mTimedOut = true;
|
||||
llassert(mAdded);
|
||||
mAdded = false;
|
||||
mCurlEasyRequest.removeRequest();
|
||||
idle(current_state); // Wait till AICurlEasyRequestStateMachine::removed_from_multi_handle() is called.
|
||||
break;
|
||||
}
|
||||
case AICurlEasyRequestStateMachine_finished:
|
||||
case AICurlEasyRequestStateMachine_removed_after_finished:
|
||||
{
|
||||
if (mBuffered)
|
||||
if (!mHandled)
|
||||
{
|
||||
// Only do this once.
|
||||
mHandled = true;
|
||||
|
||||
// Stop the timer. Note that it's the main thread that generates timer events,
|
||||
// so we're certain that there will be no time out anymore if we reach this point.
|
||||
mTimer->abort();
|
||||
|
||||
// The request finished and either data or an error code is available.
|
||||
if (mBuffered)
|
||||
{
|
||||
AICurlEasyRequest_wat easy_request_w(*mCurlEasyRequest);
|
||||
AICurlResponderBuffer_wat buffered_easy_request_w(*mCurlEasyRequest);
|
||||
buffered_easy_request_w->processOutput(easy_request_w);
|
||||
}
|
||||
}
|
||||
|
||||
if (current_state == AICurlEasyRequestStateMachine_finished)
|
||||
{
|
||||
idle(current_state); // Wait till AICurlEasyRequestStateMachine::removed_from_multi_handle() is called.
|
||||
break;
|
||||
}
|
||||
|
||||
// See above.
|
||||
mTimedOut = false;
|
||||
/* Fall-Through */
|
||||
}
|
||||
case AICurlEasyRequestStateMachine_removed:
|
||||
{
|
||||
// The request was removed from the multi handle.
|
||||
if (mBuffered && mTimedOut)
|
||||
{
|
||||
AICurlEasyRequest_wat easy_request_w(*mCurlEasyRequest);
|
||||
AICurlResponderBuffer_wat buffered_easy_request_w(*mCurlEasyRequest);
|
||||
buffered_easy_request_w->processOutput(easy_request_w);
|
||||
buffered_easy_request_w->timed_out();
|
||||
}
|
||||
finish();
|
||||
|
||||
// We're done. If we timed out, abort -- or else the application will
|
||||
// think that getResult() will return a valid error code from libcurl.
|
||||
if (mTimedOut)
|
||||
abort();
|
||||
else
|
||||
finish();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -132,12 +209,13 @@ void AICurlEasyRequestStateMachine::multiplex_impl(void)
|
||||
void AICurlEasyRequestStateMachine::abort_impl(void)
|
||||
{
|
||||
DoutEntering(dc::curl, "AICurlEasyRequestStateMachine::abort_impl() [" << (void*)this << "] [" << (void*)mCurlEasyRequest.get() << "]");
|
||||
// Revert call to addRequest() if that was already called (and the request wasn't removed already again).
|
||||
if (AICurlEasyRequestStateMachine_waitAdded <= mRunState && mRunState < AICurlEasyRequestStateMachine_finished)
|
||||
// Revert call to addRequest() if that was already called (and the request wasn't removed again already).
|
||||
if (mAdded)
|
||||
{
|
||||
// Note that it's safe to call this even if the curl thread already removed it, or will removes it
|
||||
// after we called this, before processing the remove command; only the curl thread calls
|
||||
// MultiHandle::remove_easy_request, which is a no-op when called twice for the same easy request.
|
||||
mAdded = false;
|
||||
mCurlEasyRequest.removeRequest();
|
||||
}
|
||||
}
|
||||
@@ -152,10 +230,9 @@ void AICurlEasyRequestStateMachine::finish_impl(void)
|
||||
curl_easy_request_w->revokeCallbacks();
|
||||
}
|
||||
// Note that even if the timer expired, it wasn't deleted because we used AIPersistentTimer; so mTimer is still valid.
|
||||
// Stop the timer.
|
||||
mTimer->abort();
|
||||
// And delete it here.
|
||||
mTimer->kill();
|
||||
// Stop the timer, if it's still running.
|
||||
if (!mHandled)
|
||||
mTimer->abort();
|
||||
// Auto clean up ourselves.
|
||||
kill();
|
||||
}
|
||||
|
||||
@@ -59,6 +59,10 @@ class AICurlEasyRequestStateMachine : public AIStateMachine, public AICurlEasyHa
|
||||
|
||||
private:
|
||||
bool mBuffered; // Argument used for construction of mCurlEasyRequest.
|
||||
bool mAdded; // Set when the last command to the curl thread was to add the request.
|
||||
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.
|
||||
|
||||
protected:
|
||||
|
||||
@@ -89,7 +89,7 @@ void AIStateMachine::updateSettings(void)
|
||||
// Public methods
|
||||
//
|
||||
|
||||
void AIStateMachine::run(AIStateMachine* parent, state_type new_parent_state, bool abort_parent)
|
||||
void AIStateMachine::run(AIStateMachine* parent, state_type new_parent_state, bool abort_parent, bool on_abort_signal_parent)
|
||||
{
|
||||
DoutEntering(dc::statemachine, "AIStateMachine::run(" << (void*)parent << ", " << (parent ? parent->state_str(new_parent_state) : "NA") << ", " << abort_parent << ") [" << (void*)this << "]");
|
||||
// Must be the first time we're being run, or we must be called from a callback function.
|
||||
@@ -111,6 +111,7 @@ void AIStateMachine::run(AIStateMachine* parent, state_type new_parent_state, bo
|
||||
|
||||
mNewParentState = new_parent_state;
|
||||
mAbortParent = abort_parent;
|
||||
mOnAbortSignalParent = on_abort_signal_parent;
|
||||
}
|
||||
|
||||
// If abort_parent is requested then a parent must be provided.
|
||||
@@ -272,7 +273,7 @@ void AIStateMachine::set_state(state_type state)
|
||||
// state is less than the current state, ignore it.
|
||||
// Also, if abort() or finish() was called, then we should just ignore it.
|
||||
if (mState != bs_run ||
|
||||
(!mIdle && !LLThread::is_main_thread() && state <= mRunState))
|
||||
(!mIdle && state <= mRunState && !LLThread::is_main_thread()))
|
||||
{
|
||||
#ifdef SHOW_ASSERT
|
||||
// It's a bit weird if the same thread does two calls on a row where the second call
|
||||
@@ -379,7 +380,7 @@ void AIStateMachine::finish(void)
|
||||
mParent->abort();
|
||||
mParent = NULL;
|
||||
}
|
||||
else
|
||||
else if (!mAborted || mOnAbortSignalParent)
|
||||
{
|
||||
mParent->set_state(mNewParentState);
|
||||
}
|
||||
@@ -491,25 +492,28 @@ void AIStateMachine::multiplex(U64 current_time)
|
||||
multiplex_impl();
|
||||
}
|
||||
|
||||
//static
|
||||
void AIStateMachine::add_continued_statemachines(void)
|
||||
{
|
||||
AIReadAccess<cscm_type> cscm_r(continued_statemachines_and_calling_mainloop);
|
||||
bool nonempty = false;
|
||||
for (continued_statemachines_type::const_iterator iter = cscm_r->continued_statemachines.begin(); iter != cscm_r->continued_statemachines.end(); ++iter)
|
||||
{
|
||||
nonempty = true;
|
||||
active_statemachines.push_back(QueueElement(*iter));
|
||||
Dout(dc::statemachine, "Adding " << (void*)*iter << " to active_statemachines");
|
||||
(*iter)->mActive = as_active;
|
||||
}
|
||||
if (nonempty)
|
||||
AIWriteAccess<cscm_type>(cscm_r)->continued_statemachines.clear();
|
||||
}
|
||||
|
||||
static LLFastTimer::DeclareTimer FTM_STATEMACHINE("State Machine");
|
||||
// static
|
||||
void AIStateMachine::mainloop(void*)
|
||||
{
|
||||
LLFastTimer t(FTM_STATEMACHINE);
|
||||
// Add continued state machines.
|
||||
{
|
||||
AIReadAccess<cscm_type> cscm_r(continued_statemachines_and_calling_mainloop);
|
||||
bool nonempty = false;
|
||||
for (continued_statemachines_type::const_iterator iter = cscm_r->continued_statemachines.begin(); iter != cscm_r->continued_statemachines.end(); ++iter)
|
||||
{
|
||||
nonempty = true;
|
||||
active_statemachines.push_back(QueueElement(*iter));
|
||||
Dout(dc::statemachine, "Adding " << (void*)*iter << " to active_statemachines");
|
||||
(*iter)->mActive = as_active;
|
||||
}
|
||||
if (nonempty)
|
||||
AIWriteAccess<cscm_type>(cscm_r)->continued_statemachines.clear();
|
||||
}
|
||||
add_continued_statemachines();
|
||||
llassert(!active_statemachines.empty());
|
||||
// Run one or more state machines.
|
||||
U64 total_clocks = 0;
|
||||
@@ -523,7 +527,7 @@ void AIStateMachine::mainloop(void*)
|
||||
// This might call idle() and then pass the statemachine to another thread who then may call cont().
|
||||
// Hence, after this isn't not sure what mIdle is, and it can change from true to false at any moment,
|
||||
// if it is true after this function returns.
|
||||
iter->statemachine().multiplex(start);
|
||||
statemachine.multiplex(start);
|
||||
U64 delta = LLFastTimer::getCPUClockCount64() - start;
|
||||
iter->add(delta);
|
||||
total_clocks += delta;
|
||||
@@ -589,3 +593,40 @@ void AIStateMachine::mainloop(void*)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void AIStateMachine::flush(void)
|
||||
{
|
||||
DoutEntering(dc::curl, "AIStateMachine::flush(void)");
|
||||
add_continued_statemachines();
|
||||
// Abort all state machines.
|
||||
for (active_statemachines_type::iterator iter = active_statemachines.begin(); iter != active_statemachines.end(); ++iter)
|
||||
{
|
||||
AIStateMachine& statemachine(iter->statemachine());
|
||||
if (statemachine.running())
|
||||
statemachine.abort();
|
||||
}
|
||||
for (int batch = 0;; ++batch)
|
||||
{
|
||||
// Run mainloop until all state machines are idle.
|
||||
for(;;)
|
||||
{
|
||||
{
|
||||
AIReadAccess<cscm_type> cscm_r(continued_statemachines_and_calling_mainloop);
|
||||
if (!cscm_r->calling_mainloop)
|
||||
break;
|
||||
}
|
||||
mainloop(NULL);
|
||||
}
|
||||
if (batch == 1)
|
||||
break;
|
||||
add_continued_statemachines();
|
||||
// Kill all state machines.
|
||||
for (active_statemachines_type::iterator iter = active_statemachines.begin(); iter != active_statemachines.end(); ++iter)
|
||||
{
|
||||
AIStateMachine& statemachine(iter->statemachine());
|
||||
if (statemachine.running())
|
||||
statemachine.kill();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,7 +207,6 @@ class AIStateMachine {
|
||||
active_type mActive; //!< Whether statemachine is idle, queued to be added to the active list, or already on the active list.
|
||||
S64 mSleep; //!< Non-zero while the state machine is sleeping.
|
||||
LLMutex mIdleActive; //!< Used for atomic operations on the pair mIdle / mActive.
|
||||
LLMutex mSetStateLock; //!< For critical areas in set_state() and locked_cont().
|
||||
#ifdef SHOW_ASSERT
|
||||
apr_os_thread_t mContThread; //!< Thread that last called locked_cont().
|
||||
bool mCalledThreadUnsafeIdle; //!< Set to true when idle() is called.
|
||||
@@ -218,6 +217,7 @@ class AIStateMachine {
|
||||
AIStateMachine* mParent; //!< The parent object that started this state machine, or NULL if there isn't any.
|
||||
state_type mNewParentState; //!< The state at which the parent should continue upon a successful finish.
|
||||
bool mAbortParent; //!< If true, abort parent on abort(). Otherwise continue as normal.
|
||||
bool mOnAbortSignalParent; //!< If true and mAbortParent is false, change state of parent even on abort.
|
||||
// From outside a state machine:
|
||||
struct callback_type {
|
||||
typedef boost::signals2::signal<void (bool)> signal_type;
|
||||
@@ -233,8 +233,10 @@ class AIStateMachine {
|
||||
static AIThreadSafeSimpleDC<U64> sMaxCount; //!< Number of cpu clocks below which we start a new state machine within the same frame.
|
||||
|
||||
protected:
|
||||
LLMutex mSetStateLock; //!< For critical areas in set_state() and locked_cont().
|
||||
|
||||
//! State of the derived class. Only valid if mState == bs_run. Call set_state to change.
|
||||
state_type mRunState;
|
||||
volatile state_type mRunState;
|
||||
|
||||
public:
|
||||
//! Create a non-running state machine.
|
||||
@@ -280,7 +282,7 @@ class AIStateMachine {
|
||||
//! Change state to <code>bs_run</code>. May only be called after creation or after returning from finish().
|
||||
// If <code>parent</code> is non-NULL, change the parent state machine's state to <code>new_parent_state</code>
|
||||
// upon finish, or in the case of an abort and when <code>abort_parent</code> is true, call parent->abort() instead.
|
||||
void run(AIStateMachine* parent, state_type new_parent_state, bool abort_parent = true);
|
||||
void run(AIStateMachine* parent, state_type new_parent_state, bool abort_parent = true, bool on_abort_signal_parent = true);
|
||||
|
||||
//! Change state to 'bs_run'. May only be called after creation or after returning from finish().
|
||||
// Does not cause a callback.
|
||||
@@ -352,7 +354,7 @@ class AIStateMachine {
|
||||
bool waiting(void) const { return mState == bs_run && mIdle; }
|
||||
|
||||
// Use some safebool idiom (http://www.artima.com/cppsource/safebool.html) rather than operator bool.
|
||||
typedef state_type AIStateMachine::* const bool_type;
|
||||
typedef volatile state_type AIStateMachine::* const bool_type;
|
||||
//! Return true if state machine successfully finished.
|
||||
operator bool_type() const { return ((mState == bs_initialize || mState == bs_callback) && !mAborted) ? &AIStateMachine::mRunState : 0; }
|
||||
|
||||
@@ -360,9 +362,14 @@ class AIStateMachine {
|
||||
char const* state_str(state_type state);
|
||||
|
||||
private:
|
||||
static void add_continued_statemachines(void);
|
||||
static void mainloop(void*);
|
||||
void multiplex(U64 current_time);
|
||||
|
||||
public:
|
||||
//! Abort all running state machines and then run mainloop until all state machines are idle (called when application is exiting).
|
||||
static void flush(void);
|
||||
|
||||
protected:
|
||||
//---------------------------------------
|
||||
// Derived class implementations.
|
||||
|
||||
Reference in New Issue
Block a user