Timer, time out, and clean up improvements.

This commit is contained in:
Aleric Inglewood
2012-07-11 19:25:29 +02:00
parent 0419f8bee9
commit 7416d2aaf1
13 changed files with 254 additions and 78 deletions

View File

@@ -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");

View File

@@ -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

View File

@@ -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);

View File

@@ -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);

View File

@@ -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

View File

@@ -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);

View File

@@ -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();

View File

@@ -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);

View File

@@ -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)

View File

@@ -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();
}

View File

@@ -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:

View File

@@ -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();
}
}
}

View File

@@ -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.