Merge branch 'curlthreading2' of git://github.com/AlericInglewood/SingularityViewer

Conflicts:
	indra/llplugin/slplugin/CMakeLists.txt
This commit is contained in:
Siana Gearz
2012-09-19 23:30:48 +02:00
116 changed files with 7974 additions and 6018 deletions

View File

@@ -132,6 +132,7 @@ set(viewer_SOURCE_FILES
llconfirmationmanager.cpp
llconsole.cpp
llcontainerview.cpp
llcurlrequest.cpp
llcurrencyuimanager.cpp
llcylinder.cpp
lldaycyclemanager.cpp
@@ -633,6 +634,7 @@ set(viewer_HEADER_FILES
llconfirmationmanager.h
llconsole.h
llcontainerview.h
llcurlrequest.h
llcurrencyuimanager.h
llcylinder.h
lldaycyclemanager.h

View File

@@ -4227,17 +4227,6 @@
<key>Value</key>
<real>120.0</real>
</map>
<key>CurlUseMultipleThreads</key>
<map>
<key>Comment</key>
<string>Use background threads for executing curl_multi_perform (requires restart)</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<integer>1</integer>
</map>
<key>Cursor3D</key>
<map>
<key>Comment</key>

View File

@@ -6,15 +6,18 @@
#ifndef CURL_STATICLIB
#define CURL_STATICLIB 1
#endif
#include <curl/curl.h>
#include <stdtypes.h>
#include <llbufferstream.h>
#include <llerror.h>
#include <llhttpclient.h>
#include <llurlrequest.h>
#include <llxmltree.h>
#include "llbufferstream.h"
#include "llerror.h"
#include "llhttpclient.h"
#include "llurlrequest.h"
#include "llxmltree.h"
#include <curl/curl.h>
#ifdef DEBUG_CURLIO
#include "debug_libcurl.h"
#endif
// ********************************************************************
@@ -255,7 +258,16 @@ static void request(const std::string &url,
}
LLPumpIO::chain_t chain;
LLURLRequest *req = new LLURLRequest(method, url);
LLURLRequest *req;
try
{
req = new LLURLRequest(method, url);
}
catch(AICurlNoEasyHandle const& error)
{
llwarns << "Failed to create LLURLRequest: " << error.what() << llendl;
return;
}
req->checkRootCertificate(true);
/*
@@ -324,10 +336,11 @@ int HippoRestRequest::getBlocking(const std::string &url, std::string *result)
char curlErrorBuffer[CURL_ERROR_SIZE];
CURL* curlp = curl_easy_init();
llassert_always(curlp);
curl_easy_setopt(curlp, CURLOPT_NOSIGNAL, 1); // don't use SIGALRM for timeouts
curl_easy_setopt(curlp, CURLOPT_TIMEOUT, 5); // seconds
curl_easy_setopt(curlp, CURLOPT_CAINFO, gDirUtilp->getCAFile().c_str());
curl_easy_setopt(curlp, CURLOPT_CAINFO, gDirUtilp->getCAFile().c_str());
curl_easy_setopt(curlp, CURLOPT_WRITEFUNCTION, curlWrite);
curl_easy_setopt(curlp, CURLOPT_WRITEDATA, result);
@@ -337,7 +350,7 @@ int HippoRestRequest::getBlocking(const std::string &url, std::string *result)
*result = "";
S32 curlSuccess = curl_easy_perform(curlp);
S32 httpStatus = 499;
long httpStatus = 499L; // curl_easy_getinfo demands pointer to long.
curl_easy_getinfo(curlp, CURLINFO_RESPONSE_CODE, &httpStatus);
if (curlSuccess != 0) {

View File

@@ -139,15 +139,11 @@ fi
export VIEWER_BINARY='singularity-do-not-run-directly'
BINARY_TYPE=$(expr match "$(file -b bin/$VIEWER_BINARY)" '\(.*executable\)')
QPP=qt4/plugins/imageformats/
if [ "${BINARY_TYPE}" == "ELF 64-bit LSB executable" ]; then
QTPLUGINS=/usr/lib64/$QPP:/lib64/$QPP:/usr/local/lib64/$QPP
SL_ENV+='LD_LIBRARY_PATH="`pwd`/lib64:`pwd`/lib32:$QTPLUGINS:$LD_LIBRARY_PATH"'
SL_ENV+='LD_LIBRARY_PATH="`pwd`/lib64:`pwd`/lib32:$LD_LIBRARY_PATH"'
else
QTPLUGINS=/usr/lib/$QPP:/lib/$QPP:/usr/local/lib/$QPP
SL_ENV+='LD_LIBRARY_PATH="`pwd`/lib:$QTPLUGINS:$LD_LIBRARY_PATH"'
SL_ENV+='LD_LIBRARY_PATH="`pwd`/lib:$LD_LIBRARY_PATH"'
fi
export SL_CMD='$LL_WRAPPER bin/$VIEWER_BINARY'
export SL_OPT="`cat gridargs.dat` $@"

View File

@@ -2401,7 +2401,7 @@ bool LLAgent::sendMaturityPreferenceToServer(int preferredMaturity)
body["access_prefs"] = access_prefs;
llinfos << "Sending access prefs update to " << (access_prefs["max"].asString()) << " via capability to: "
<< url << llendl;
LLHTTPClient::post(url, body, new LLHTTPClient::Responder()); // Ignore response
LLHTTPClient::post(url, body, new LLHTTPClient::ResponderIgnore); // Ignore response
return true;
}
return false;

View File

@@ -61,7 +61,7 @@ bool LLAgentLanguage::update()
body["language"] = language;
body["language_is_public"] = gSavedSettings.getBOOL("LanguageIsPublic");
LLHTTPClient::post(url, body, new LLHTTPClient::Responder);
LLHTTPClient::post(url, body, new LLHTTPClient::ResponderIgnore);
}
return true;
}

View File

@@ -63,7 +63,6 @@
#include "llviewerjoystick.h"
#include "llfloaterjoystick.h"
#include "llares.h"
#include "llcurl.h"
#include "llfloatersnapshot.h"
#include "lltexturestats.h"
#include "llviewerwindow.h"
@@ -611,6 +610,9 @@ bool LLAppViewer::init()
initLogging();
// Curl must be initialized before any thread is running.
AICurlInterface::initCurl(&AIStateMachine::flush);
// Logging is initialized. Now it's safe to start the error thread.
startErrorThread();
@@ -635,11 +637,6 @@ bool LLAppViewer::init()
LLPrivateMemoryPoolManager::initClass((BOOL)gSavedSettings.getBOOL("MemoryPrivatePoolEnabled"), (U32)gSavedSettings.getU32("MemoryPrivatePoolSize")) ;
mAlloc.setProfilingEnabled(gSavedSettings.getBOOL("MemProfiling"));
// *NOTE:Mani - LLCurl::initClass is not thread safe.
// Called before threads are created.
LLCurl::initClass(gSavedSettings.getF32("CurlRequestTimeOut"));
LL_INFOS("InitInfo") << "LLCurl initialized." << LL_ENDL ;
initThreads();
LL_INFOS("InitInfo") << "Threads initialized." << LL_ENDL ;
@@ -1739,22 +1736,20 @@ bool LLAppViewer::cleanup()
delete gVFS;
gVFS = NULL;
// Cleanup settings last in case other clases reference them
gSavedSettings.cleanup();
gColors.cleanup();
gCrashSettings.cleanup();
LLWatchdog::getInstance()->cleanup();
llinfos << "Shutting down message system" << llendflush;
end_messaging_system();
llinfos << "Message system deleted." << llendflush;
LLUserAuth::getInstance()->reset(); //reset before LLCurl::cleanupClass, else LLCURL::sHandleMutex == NULL
// *NOTE:Mani - The following call is not thread safe.
LLCurl::cleanupClass();
llinfos << "LLCurl cleaned up." << llendflush;
LLApp::stopErrorThread(); // The following call is not thread-safe. Have to stop all threads.
AICurlInterface::cleanupCurl();
// Cleanup settings last in case other classes reference them.
gSavedSettings.cleanup();
gColors.cleanup();
gCrashSettings.cleanup();
// If we're exiting to launch an URL, do that here so the screen
// is at the right resolution before we launch IE.
if (!gLaunchFileOnQuit.empty())
@@ -1822,6 +1817,8 @@ bool LLAppViewer::initThreads()
LLWatchdog::getInstance()->init(watchdog_killer_callback);
}
AICurlInterface::startCurlThread();
LLImage::initClass();
LLVFSThread::initClass(enable_threads && false);

View File

@@ -0,0 +1,146 @@
/**
* @file llcurlrequest.cpp
* @brief Implementation of Request.
*
* Copyright (c) 2012, Aleric Inglewood.
* Copyright (C) 2010, Linden Research, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution.
*
* CHANGELOG
* and additional copyright holders.
*
* 17/03/2012
* Initial version, written by Aleric Inglewood @ SL
*
* 20/03/2012
* Added copyright notice for Linden Lab for those parts that were
* copied or derived from llcurl.cpp. The code of those parts are
* already in their own llcurl.cpp, so they do not ever need to
* even look at this file; the reason I added the copyright notice
* is to make clear that I am not the author of 100% of this code
* and hence I cannot change the license of it.
*/
#include "llviewerprecompiledheaders.h"
#include "llsdserialize.h"
#include "llcurlrequest.h"
#include "llbuffer.h"
#include "llbufferstream.h"
#include "statemachine/aicurleasyrequeststatemachine.h"
//-----------------------------------------------------------------------------
// class Request
//
namespace AICurlInterface {
bool Request::get(std::string const& url, ResponderPtr responder)
{
return getByteRange(url, headers_t(), 0, -1, responder);
}
bool Request::getByteRange(std::string const& url, headers_t const& headers, S32 offset, S32 length, ResponderPtr responder)
{
DoutEntering(dc::curl, "Request::getByteRange(" << url << ", ...)");
// This might throw AICurlNoEasyHandle.
AICurlEasyRequestStateMachine* buffered_easy_request = new AICurlEasyRequestStateMachine(true);
{
AICurlEasyRequest_wat buffered_easy_request_w(*buffered_easy_request->mCurlEasyRequest);
AICurlResponderBuffer_wat(*buffered_easy_request->mCurlEasyRequest)->prepRequest(buffered_easy_request_w, headers, responder);
buffered_easy_request_w->setopt(CURLOPT_HTTPGET, 1);
if (length > 0)
{
std::string range = llformat("Range: bytes=%d-%d", offset, offset + length - 1);
buffered_easy_request_w->addHeader(range.c_str());
}
buffered_easy_request_w->finalizeRequest(url);
}
buffered_easy_request->run();
return true; // We throw in case of problems.
}
bool Request::post(std::string const& url, headers_t const& headers, std::string const& data, ResponderPtr responder, S32 time_out)
{
DoutEntering(dc::curl, "Request::post(" << url << ", ...)");
// This might throw AICurlNoEasyHandle.
AICurlEasyRequestStateMachine* buffered_easy_request = new AICurlEasyRequestStateMachine(true);
{
AICurlEasyRequest_wat buffered_easy_request_w(*buffered_easy_request->mCurlEasyRequest);
AICurlResponderBuffer_wat buffer_w(*buffered_easy_request->mCurlEasyRequest);
buffer_w->prepRequest(buffered_easy_request_w, headers, responder);
U32 bytes = data.size();
bool success = buffer_w->getInput()->append(buffer_w->sChannels.out(), (U8 const*)data.data(), bytes);
llassert_always(success); // AIFIXME: Maybe throw an error.
if (!success)
return false;
buffered_easy_request_w->setPost(bytes);
buffered_easy_request_w->addHeader("Content-Type: application/octet-stream");
buffered_easy_request_w->finalizeRequest(url);
lldebugs << "POSTING: " << bytes << " bytes." << llendl;
}
buffered_easy_request->run();
return true; // We throw in case of problems.
}
bool Request::post(std::string const& url, headers_t const& headers, LLSD const& data, ResponderPtr responder, S32 time_out)
{
DoutEntering(dc::curl, "Request::post(" << url << ", ...)");
// This might throw AICurlNoEasyHandle.
AICurlEasyRequestStateMachine* buffered_easy_request = new AICurlEasyRequestStateMachine(true);
{
AICurlEasyRequest_wat buffered_easy_request_w(*buffered_easy_request->mCurlEasyRequest);
AICurlResponderBuffer_wat buffer_w(*buffered_easy_request->mCurlEasyRequest);
buffer_w->prepRequest(buffered_easy_request_w, headers, responder);
LLBufferStream buffer_stream(buffer_w->sChannels, buffer_w->getInput().get());
LLSDSerialize::toXML(data, buffer_stream);
S32 bytes = buffer_w->getInput()->countAfter(buffer_w->sChannels.out(), NULL);
buffered_easy_request_w->setPost(bytes);
buffered_easy_request_w->addHeader("Content-Type: application/llsd+xml");
buffered_easy_request_w->finalizeRequest(url);
lldebugs << "POSTING: " << bytes << " bytes." << llendl;
}
buffered_easy_request->run();
return true; // We throw in case of problems.
}
} // namespace AICurlInterface
//==================================================================================

View File

@@ -0,0 +1,57 @@
/**
* @file llcurlrequest.h
* @brief Declaration of class Request
*
* Copyright (c) 2012, Aleric Inglewood.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution.
*
* CHANGELOG
* and additional copyright holders.
*
* 17/03/2012
* Initial version, written by Aleric Inglewood @ SL
*/
#ifndef AICURLREQUEST_H
#define AICURLREQUEST_H
#include <string>
#include <vector>
#include <boost/intrusive_ptr.hpp>
// Things defined in this namespace are called from elsewhere in the viewer code.
namespace AICurlInterface {
// Forward declaration.
class Responder;
typedef boost::intrusive_ptr<Responder> ResponderPtr;
class Request {
public:
typedef std::vector<std::string> headers_t;
bool get(std::string const& url, ResponderPtr responder);
bool getByteRange(std::string const& url, headers_t const& headers, S32 offset, S32 length, ResponderPtr responder);
bool post(std::string const& url, headers_t const& headers, std::string const& data, ResponderPtr responder, S32 time_out = 0);
bool post(std::string const& url, headers_t const& headers, LLSD const& data, ResponderPtr responder, S32 time_out = 0);
};
} // namespace AICurlInterface
#endif

View File

@@ -266,7 +266,7 @@ bool LLCurrencyUIManager::Impl::checkTransaction()
return false;
}
if (!mTransaction->process())
if (!mTransaction->is_finished())
{
return false;
}

View File

@@ -866,7 +866,7 @@ bool LLFloaterBuyLandUI::checkTransaction()
return false;
}
if (!mTransaction->process())
if (!mTransaction->is_finished())
{
return false;
}

View File

@@ -762,7 +762,7 @@ BOOL LLPanelRegionGeneralInfo::sendUpdate()
body["allow_parcel_changes"] = childGetValue("allow_parcel_changes_check");
body["block_parcel_search"] = childGetValue("block_parcel_search_check");
LLHTTPClient::post(url, body, new LLHTTPClient::Responder());
LLHTTPClient::post(url, body, new LLHTTPClient::ResponderIgnore);
}
else
{

View File

@@ -43,6 +43,7 @@
#include "lluictrlfactory.h"
#include "llwindow.h"
#include "llviewerwindow.h"
#include "llhttpclient.h"
static LLFloaterURLEntry* sInstance = NULL;

View File

@@ -99,7 +99,7 @@ std::string LLLogChat::timestamp(bool withdate)
//static
void LLLogChat::saveHistory(std::string filename, std::string line)
void LLLogChat::saveHistory(std::string const& filename, std::string line)
{
if(!filename.size())
{
@@ -120,7 +120,7 @@ void LLLogChat::saveHistory(std::string filename, std::string line)
}
}
void LLLogChat::loadHistory(std::string filename , void (*callback)(ELogLineType,std::string,void*), void* userdata)
void LLLogChat::loadHistory(std::string const& filename , void (*callback)(ELogLineType,std::string,void*), void* userdata)
{
if(!filename.size())
{

View File

@@ -34,6 +34,8 @@
#ifndef LL_LLLOGCHAT_H
#define LL_LLLOGCHAT_H
#include <string>
class LLLogChat
{
public:
@@ -44,9 +46,9 @@ public:
LOG_END
};
static std::string timestamp(bool withdate = false);
static std::string makeLogFileName(std::string(filename));
static void saveHistory(std::string filename, std::string line);
static void loadHistory(std::string filename,
static std::string makeLogFileName(std::string filename);
static void saveHistory(std::string const& filename, std::string line);
static void loadHistory(std::string const& filename,
void (*callback)(ELogLineType,std::string,void*),
void* userdata);
private:

View File

@@ -35,7 +35,7 @@
#include "llappviewer.h"
#include "llbufferstream.h"
#include "llcallbacklist.h"
#include "llcurl.h"
#include "llcurlrequest.h"
#include "lldatapacker.h"
#include "llfasttimer.h"
#if MESH_IMPORT
@@ -465,7 +465,6 @@ public:
LLMeshRepoThread::LLMeshRepoThread()
: LLThread("mesh repo")
{
mWaiting = false;
mMutex = new LLMutex();
mHeaderMutex = new LLMutex();
mSignal = new LLCondition();
@@ -483,7 +482,7 @@ LLMeshRepoThread::~LLMeshRepoThread()
void LLMeshRepoThread::run()
{
mCurlRequest = new LLCurlRequest();
mCurlRequest = new AICurlInterface::Request;
#if MESH_IMPORT
LLCDResult res = LLConvexDecomposition::initThread();
if (res != LLCD_OK)
@@ -492,13 +491,10 @@ void LLMeshRepoThread::run()
}
#endif //MESH_IMPORT
mSignal->lock();
while (!LLApp::isQuitting())
{
mWaiting = true;
mSignal->wait();
mWaiting = false;
if (!LLApp::isQuitting())
// Left braces in order not to change the indentation.
{
static U32 count = 0;
@@ -511,38 +507,53 @@ void LLMeshRepoThread::run()
}
// NOTE: throttling intentionally favors LOD requests over header requests
while (!mLODReqQ.empty() && count < MAX_MESH_REQUESTS_PER_SECOND && sActiveLODRequests < (S32)sMaxConcurrentRequests)
{
if (mMutex)
{
mMutex->lock();
LODRequest req = mLODReqQ.front();
mLODReqQ.pop();
LLMeshRepository::sLODProcessing--;
mMutex->unlock();
if (!fetchMeshLOD(req.mMeshParams, req.mLOD, count))//failed, resubmit
try
{
fetchMeshLOD(req.mMeshParams, req.mLOD, count);
}
catch(AICurlNoEasyHandle const& error)
{
llwarns << "fetchMeshLOD() failed: " << error.what() << llendl;
mMutex->lock();
mLODReqQ.push(req) ;
LLMeshRepository::sLODProcessing++;
mLODReqQ.push(req);
mMutex->unlock();
break;
}
}
}
while (!mHeaderReqQ.empty() && count < MAX_MESH_REQUESTS_PER_SECOND && sActiveHeaderRequests < (S32)sMaxConcurrentRequests)
{
if (mMutex)
{
mMutex->lock();
HeaderRequest req = mHeaderReqQ.front();
mHeaderReqQ.pop();
mMutex->unlock();
if (!fetchMeshHeader(req.mMeshParams, count))//failed, resubmit
bool success = false;
try
{
success = fetchMeshHeader(req.mMeshParams, count);
}
catch(AICurlNoEasyHandle const& error)
{
llwarns << "fetchMeshHeader() failed: " << error.what() << llendl;
}
if (!success)
{
mMutex->lock();
mHeaderReqQ.push(req) ;
mMutex->unlock();
break;
}
}
}
@@ -552,7 +563,16 @@ void LLMeshRepoThread::run()
for (std::set<LLUUID>::iterator iter = mSkinRequests.begin(); iter != mSkinRequests.end(); ++iter)
{
LLUUID mesh_id = *iter;
if (!fetchMeshSkinInfo(mesh_id))
bool success = false;
try
{
success = fetchMeshSkinInfo(mesh_id);
}
catch(AICurlNoEasyHandle const& error)
{
llwarns << "fetchMeshSkinInfo(" << mesh_id << ") failed: " << error.what() << llendl;
}
if (!success)
{
incomplete.insert(mesh_id);
}
@@ -565,7 +585,16 @@ void LLMeshRepoThread::run()
for (std::set<LLUUID>::iterator iter = mDecompositionRequests.begin(); iter != mDecompositionRequests.end(); ++iter)
{
LLUUID mesh_id = *iter;
if (!fetchMeshDecomposition(mesh_id))
bool success = false;
try
{
success = fetchMeshDecomposition(mesh_id);
}
catch(AICurlNoEasyHandle const& error)
{
llwarns << "fetchMeshDecomposition(" << mesh_id << ") failed: " << error.what() << llendl;
}
if (!success)
{
incomplete.insert(mesh_id);
}
@@ -578,7 +607,16 @@ void LLMeshRepoThread::run()
for (std::set<LLUUID>::iterator iter = mPhysicsShapeRequests.begin(); iter != mPhysicsShapeRequests.end(); ++iter)
{
LLUUID mesh_id = *iter;
if (!fetchMeshPhysicsShape(mesh_id))
bool success = false;
try
{
success = fetchMeshPhysicsShape(mesh_id);
}
catch(AICurlNoEasyHandle const& error)
{
llwarns << "fetchMeshPhysicsShape(" << mesh_id << ") failed: " << error.what() << llendl;
}
if (!success)
{
incomplete.insert(mesh_id);
}
@@ -586,14 +624,11 @@ void LLMeshRepoThread::run()
mPhysicsShapeRequests = incomplete;
}
mCurlRequest->process();
}
mSignal->wait();
}
if (mSignal->isLocked())
{ //make sure to let go of the mutex associated with the given signal before shutting down
mSignal->unlock();
}
mSignal->unlock();
#if MESH_IMPORT
res = LLConvexDecomposition::quitThread();
@@ -645,7 +680,7 @@ void LLMeshRepoThread::loadMeshLOD(const LLVolumeParams& mesh_params, S32 lod)
if (pending != mPendingLOD.end())
{ //append this lod request to existing header request
pending->second.push_back(lod);
llassert(pending->second.size() <= LLModel::NUM_LODS)
llassert(pending->second.size() <= LLModel::NUM_LODS);
}
else
{ //if no header request is pending, fetch header
@@ -681,21 +716,15 @@ std::string LLMeshRepoThread::constructUrl(LLUUID mesh_id)
bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id)
{ //protected by mMutex
if (!mHeaderMutex)
{
return false;
}
mHeaderMutex->lock();
if (mMeshHeader.find(mesh_id) == mMeshHeader.end())
{ //we have no header info for this mesh, do nothing
{
// We have no header info for this mesh, try again later.
mHeaderMutex->unlock();
return false;
}
bool ret = true ;
U32 header_size = mMeshHeaderSize[mesh_id];
if (header_size > 0)
@@ -743,12 +772,10 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id)
std::string http_url = constructUrl(mesh_id);
if (!http_url.empty())
{
ret = mCurlRequest->getByteRange(http_url, headers, offset, size,
new LLMeshSkinInfoResponder(mesh_id, offset, size));
if(ret)
{
LLMeshRepository::sHTTPRequestCount++;
}
// This might throw AICurlNoEasyHandle.
mCurlRequest->getByteRange(http_url, headers, offset, size,
new LLMeshSkinInfoResponder(mesh_id, offset, size));
LLMeshRepository::sHTTPRequestCount++;
}
}
}
@@ -758,26 +785,22 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id)
}
//early out was not hit, effectively fetched
return ret;
return true;
}
//return false if failed to get header
bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id)
{ //protected by mMutex
if (!mHeaderMutex)
{
return false;
}
mHeaderMutex->lock();
if (mMeshHeader.find(mesh_id) == mMeshHeader.end())
{ //we have no header info for this mesh, do nothing
{
// We have no header info for this mesh, try again later.
mHeaderMutex->unlock();
return false;
}
U32 header_size = mMeshHeaderSize[mesh_id];
bool ret = true ;
if (header_size > 0)
{
@@ -824,12 +847,10 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id)
std::string http_url = constructUrl(mesh_id);
if (!http_url.empty())
{
ret = mCurlRequest->getByteRange(http_url, headers, offset, size,
new LLMeshDecompositionResponder(mesh_id, offset, size));
if(ret)
{
LLMeshRepository::sHTTPRequestCount++;
}
// This might throw AICurlNoEasyHandle.
mCurlRequest->getByteRange(http_url, headers, offset, size,
new LLMeshDecompositionResponder(mesh_id, offset, size));
LLMeshRepository::sHTTPRequestCount++;
}
}
}
@@ -839,26 +860,22 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id)
}
//early out was not hit, effectively fetched
return ret;
return true;
}
//return false if failed to get header
bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id)
{ //protected by mMutex
if (!mHeaderMutex)
{
return false;
}
mHeaderMutex->lock();
if (mMeshHeader.find(mesh_id) == mMeshHeader.end())
{ //we have no header info for this mesh, do nothing
{
// We have no header info for this mesh, retry later.
mHeaderMutex->unlock();
return false;
}
U32 header_size = mMeshHeaderSize[mesh_id];
bool ret = true ;
if (header_size > 0)
{
@@ -905,13 +922,10 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id)
std::string http_url = constructUrl(mesh_id);
if (!http_url.empty())
{
ret = mCurlRequest->getByteRange(http_url, headers, offset, size,
new LLMeshPhysicsShapeResponder(mesh_id, offset, size));
if(ret)
{
LLMeshRepository::sHTTPRequestCount++;
}
// This might throw AICurlNoEasyHandle.
mCurlRequest->getByteRange(http_url, headers, offset, size,
new LLMeshPhysicsShapeResponder(mesh_id, offset, size));
LLMeshRepository::sHTTPRequestCount++;
}
}
else
@@ -925,7 +939,7 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id)
}
//early out was not hit, effectively fetched
return ret;
return true;
}
//return false if failed to get header
@@ -944,14 +958,14 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, U32& c
LLMeshRepository::sCacheBytesRead += bytes;
file.read(buffer, bytes);
if (headerReceived(mesh_params, buffer, bytes))
{ //did not do an HTTP request, return false
{
// Already have header, no need to retry.
return true;
}
}
}
//either cache entry doesn't exist or is corrupt, request header from simulator
bool retval = true ;
std::vector<std::string> headers;
headers.push_back("Accept: application/octet-stream");
@@ -961,29 +975,19 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, U32& c
//grab first 4KB if we're going to bother with a fetch. Cache will prevent future fetches if a full mesh fits
//within the first 4KB
//NOTE -- this will break of headers ever exceed 4KB
retval = mCurlRequest->getByteRange(http_url, headers, 0, 4096, new LLMeshHeaderResponder(mesh_params));
if(retval)
{
LLMeshRepository::sHTTPRequestCount++;
}
// This might throw AICurlNoEasyHandle.
mCurlRequest->getByteRange(http_url, headers, 0, 4096, new LLMeshHeaderResponder(mesh_params));
LLMeshRepository::sHTTPRequestCount++;
count++;
}
return retval;
return true;
}
//return false if failed to get mesh lod.
bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, U32& count)
void LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, U32& count)
{ //protected by mMutex
if (!mHeaderMutex)
{
return false;
}
mHeaderMutex->lock();
bool retval = true;
LLUUID mesh_id = mesh_params.getSculptID();
U32 header_size = mMeshHeaderSize[mesh_id];
@@ -1019,7 +1023,7 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod,
if (lodReceived(mesh_params, lod, buffer, size))
{
delete[] buffer;
return true;
return;
}
}
@@ -1033,13 +1037,10 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod,
std::string http_url = constructUrl(mesh_id);
if (!http_url.empty())
{
retval = mCurlRequest->getByteRange(constructUrl(mesh_id), headers, offset, size,
// This might throw AICurlNoEasyHandle.
mCurlRequest->getByteRange(constructUrl(mesh_id), headers, offset, size,
new LLMeshLODResponder(mesh_params, lod, offset, size));
if(retval)
{
LLMeshRepository::sHTTPRequestCount++;
}
LLMeshRepository::sHTTPRequestCount++;
count++;
}
else
@@ -1056,8 +1057,6 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod,
{
mHeaderMutex->unlock();
}
return retval;
}
bool LLMeshRepoThread::headerReceived(const LLVolumeParams& mesh_params, U8* data, S32 data_size)
@@ -1102,7 +1101,7 @@ bool LLMeshRepoThread::headerReceived(const LLVolumeParams& mesh_params, U8* dat
LLMutexLock lock(mHeaderMutex);
mMeshHeaderSize[mesh_id] = header_size;
mMeshHeader[mesh_id] = header;
}
}
LLMutexLock lock(mMutex); // <FS:ND/> FIRE-7182, make sure only one thread access mPendingLOD at the same time.
@@ -1604,7 +1603,7 @@ void LLMeshUploadThread::generateHulls()
void LLMeshUploadThread::doWholeModelUpload()
{
mCurlRequest = new LLCurlRequest();
mCurlRequest = new AICurlInterface::Request();
if (mWholeModelUploadURL.empty())
{
@@ -1619,11 +1618,14 @@ 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;
// This might throw AICurlNoEasyHandle.
mCurlRequest->post(mWholeModelUploadURL, headers, body,
new LLWholeModelUploadResponder(this, full_model_data, mUploadObserverHandle), mMeshUploadTimeOut);
do
{
mCurlRequest->process();
mCurlRequest->process(); // FIXME: This function does not exist anymore. The post() gets CPU time from AICurlEasyRequestStateMachine.
// Therefore, if we do not want to continue here unless this upload is done... no wait, that would
// be blocking and we don't want blocking...
//sleep for 10ms to prevent eating a whole core
apr_sleep(10000);
} while (mCurlRequest->getQueued() > 0);
@@ -1640,7 +1642,7 @@ void LLMeshUploadThread::requestWholeModelFee()
{
dump_num++;
mCurlRequest = new LLCurlRequest();
mCurlRequest = new AICurlInterface::Request;
generateHulls();
@@ -1650,6 +1652,7 @@ void LLMeshUploadThread::requestWholeModelFee()
mPendingUploads++;
LLCurlRequest::headers_t headers;
// This might throw AICurlNoEasyHandle.
mCurlRequest->post(mWholeModelFeeCapability, headers, model_data,
new LLWholeModelFeeResponder(this,model_data, mFeeObserverHandle), mMeshUploadTimeOut);
@@ -1670,11 +1673,6 @@ void LLMeshUploadThread::requestWholeModelFee()
void LLMeshRepoThread::notifyLoadedMeshes()
{//called via gMeshRepo.notifyLoadedMeshes(). mMutex already locked
if (!mMutex)
{
return;
}
while (!mLoadedQ.empty())
{
mMutex->lock();
@@ -2378,17 +2376,17 @@ void LLMeshRepository::notifyLoadedMeshes()
mInventoryQ.pop();
}
}
#endif //MESH_IMPORT
//call completed callbacks on finished decompositions
mDecompThread->notifyCompleted();
if (!mThread->mWaiting)
{ //curl thread is churning, wait for it to go idle
if (!mThread->mSignal->tryLock())
{
// Curl thread is churning, wait for it to go idle.
return;
}
mThread->mSignal->unlock();
static std::string region_name("never name a region this");
@@ -3334,6 +3332,7 @@ void LLPhysicsDecomp::run()
mStageID[stages[i].mName] = i;
}
mSignal->lock();
while (!mQuitting)
{
mSignal->wait();
@@ -3362,14 +3361,10 @@ void LLPhysicsDecomp::run()
}
}
}
mSignal->unlock();
decomp->quitThread();
if (mSignal->isLocked())
{ //let go of mSignal's associated mutex
mSignal->unlock();
}
mDone = true;
#endif //MESH_IMPORT
}

View File

@@ -32,6 +32,7 @@
#include "lluuid.h"
#include "llviewertexture.h"
#include "llvolume.h"
#include "llcurlrequest.h"
#if MESH_IMPORT
#define LLCONVEXDECOMPINTER_STATIC 1
@@ -67,7 +68,6 @@ struct LLCDHull
class LLVOVolume;
class LLMeshResponder;
class LLCurlRequest;
class LLMutex;
class LLCondition;
class LLVFS;
@@ -246,13 +246,11 @@ public:
static S32 sActiveLODRequests;
static U32 sMaxConcurrentRequests;
LLCurlRequest* mCurlRequest;
AICurlInterface::Request* mCurlRequest;
LLMutex* mMutex;
LLMutex* mHeaderMutex;
LLCondition* mSignal;
bool mWaiting;
//map of known mesh headers
typedef std::map<LLUUID, LLSD> mesh_header_map;
mesh_header_map mMeshHeader;
@@ -351,7 +349,7 @@ public:
void loadMeshLOD(const LLVolumeParams& mesh_params, S32 lod);
bool fetchMeshHeader(const LLVolumeParams& mesh_params, U32& count);
bool fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, U32& count);
void fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, U32& count);
bool headerReceived(const LLVolumeParams& mesh_params, U8* data, S32 data_size);
bool lodReceived(const LLVolumeParams& mesh_params, S32 lod, U8* data, S32 data_size);
bool skinInfoReceived(const LLUUID& mesh_id, U8* data, S32 data_size);

View File

@@ -999,7 +999,7 @@ void LLPanelClassified::sendClassifiedClickMessage(const std::string& type)
std::string url = gAgent.getRegion()->getCapability("SearchStatTracking");
llinfos << "LLPanelClassified::sendClassifiedClickMessage via capability" << llendl;
LLHTTPClient::post(url, body, new LLHTTPClient::Responder());
LLHTTPClient::post(url, body, new LLHTTPClient::ResponderIgnore);
}
////////////////////////////////////////////////////////////////////////////////////////////

View File

@@ -49,7 +49,6 @@
#include "llcheckboxctrl.h"
#include "llcommandhandler.h" // for secondlife:///app/login/
#include "llcombobox.h"
#include "llcurl.h"
#include "llviewercontrol.h"
#include "llfloaterabout.h"
#include "llfloatertest.h"

View File

@@ -1998,7 +1998,7 @@ public:
virtual void processGroup(LLSpatialGroup* group)
{
llassert(!group->isState(LLSpatialGroup::DIRTY) && !group->isEmpty())
llassert(!group->isState(LLSpatialGroup::DIRTY) && !group->isEmpty());
if (mRes < 2)
{

View File

@@ -218,6 +218,7 @@
#include "llfloaterblacklist.h"
#include "scriptcounter.h"
#include "shfloatermediaticker.h"
#include "llpacketring.h"
// </edit>
#include "llpathfindingmanager.h"
@@ -256,7 +257,9 @@ extern S32 gStartImageHeight;
// local globals
//
#if defined(CWDEBUG) || defined(DEBUG_CURLIO)
static bool gCurlIo;
#endif
static LLHost gAgentSimHost;
static BOOL gSkipOptionalUpdate = FALSE;
@@ -628,21 +631,21 @@ bool idle_startup()
F32 dropPercent = gSavedSettings.getF32("PacketDropPercentage");
msg->mPacketRing.setDropPercentage(dropPercent);
msg->mPacketRing->setDropPercentage(dropPercent);
F32 inBandwidth = gSavedSettings.getF32("InBandwidth");
F32 outBandwidth = gSavedSettings.getF32("OutBandwidth");
if (inBandwidth != 0.f)
{
LL_DEBUGS("AppInit") << "Setting packetring incoming bandwidth to " << inBandwidth << LL_ENDL;
msg->mPacketRing.setUseInThrottle(TRUE);
msg->mPacketRing.setInBandwidth(inBandwidth);
msg->mPacketRing->setUseInThrottle(TRUE);
msg->mPacketRing->setInBandwidth(inBandwidth);
}
if (outBandwidth != 0.f)
{
LL_DEBUGS("AppInit") << "Setting packetring outgoing bandwidth to " << outBandwidth << LL_ENDL;
msg->mPacketRing.setUseOutThrottle(TRUE);
msg->mPacketRing.setOutBandwidth(outBandwidth);
msg->mPacketRing->setUseOutThrottle(TRUE);
msg->mPacketRing->setOutBandwidth(outBandwidth);
}
}
@@ -1348,6 +1351,9 @@ bool idle_startup()
llinfos << "Authenticating with " << grid_uri << llendl;
// Always write curl I/O debug info for the login attempt.
Debug(gCurlIo = dc::curl.is_on() && !dc::curlio.is_on(); if (gCurlIo) dc::curlio.on());
// TODO if statement here to use web_login_key
// OGPX : which routine would this end up in? the LLSD or XMLRPC, or ....?
LLUserAuth::getInstance()->authenticate(
@@ -1422,6 +1428,7 @@ bool idle_startup()
LL_DEBUGS("AppInit") << "downloading..." << LL_ENDL;
return FALSE;
}
Debug(if (gCurlIo) dc::curlio.off()); // Login succeeded: restore dc::curlio to original state.
LLStartUp::setStartupState( STATE_LOGIN_PROCESS_RESPONSE );
progress += 0.01f;
set_startup_status(progress, LLTrans::getString("LoginProcessingResponse"), auth_message);
@@ -3607,6 +3614,7 @@ std::string LLStartUp::startupStateToString(EStartupState state)
#define RTNENUM(E) case E: return #E
switch(state){
RTNENUM( STATE_FIRST );
RTNENUM( STATE_BROWSER_INIT );
RTNENUM( STATE_LOGIN_SHOW );
RTNENUM( STATE_LOGIN_WAIT );
RTNENUM( STATE_LOGIN_CLEANUP );
@@ -3614,10 +3622,13 @@ std::string LLStartUp::startupStateToString(EStartupState state)
RTNENUM( STATE_UPDATE_CHECK );
RTNENUM( STATE_LOGIN_AUTH_INIT );
RTNENUM( STATE_LOGIN_AUTHENTICATE );
RTNENUM( STATE_WAIT_LEGACY_LOGIN );
RTNENUM( STATE_XMLRPC_LEGACY_LOGIN );
RTNENUM( STATE_LOGIN_NO_DATA_YET );
RTNENUM( STATE_LOGIN_DOWNLOADING );
RTNENUM( STATE_LOGIN_PROCESS_RESPONSE );
RTNENUM( STATE_WORLD_INIT );
RTNENUM( STATE_MULTIMEDIA_INIT );
RTNENUM( STATE_FONT_INIT );
RTNENUM( STATE_SEED_GRANTED_WAIT );
RTNENUM( STATE_SEED_CAP_GRANTED );
@@ -3630,10 +3641,10 @@ std::string LLStartUp::startupStateToString(EStartupState state)
RTNENUM( STATE_WEARABLES_WAIT );
RTNENUM( STATE_CLEANUP );
RTNENUM( STATE_STARTED );
default:
return llformat("(state #%d)", state);
}
#undef RTNENUM
// Never reached.
return llformat("(state #%d)", state);
}

View File

@@ -1320,8 +1320,15 @@ bool LLTextureFetchWorker::doWork(S32 param)
// Will call callbackHttpGet when curl request completes
std::vector<std::string> headers;
headers.push_back("Accept: image/x-j2c");
res = mFetcher->mCurlGetRequest->getByteRange(mUrl, headers, offset, mRequestedSize,
new HTTPGetResponder(mFetcher, mID, LLTimer::getTotalTime(), mRequestedSize, offset, true));
try
{
res = mFetcher->mCurlGetRequest->getByteRange(mUrl, headers, offset, mRequestedSize,
new HTTPGetResponder(mFetcher, mID, LLTimer::getTotalTime(), mRequestedSize, offset, true));
}
catch(AICurlNoEasyHandle const& error)
{
llwarns << error.what() << llendl;
}
}
if (!res)
{
@@ -2357,14 +2364,6 @@ void LLTextureFetch::commonUpdate()
// Run a cross-thread command, if any.
cmdDoWork();
#endif
// Update Curl on same thread as mCurlGetRequest was constructed
llassert_always(mCurlGetRequest);
S32 processed = mCurlGetRequest->process();
if (processed > 0)
{
lldebugs << "processed: " << processed << " messages." << llendl;
}
}
@@ -2429,7 +2428,7 @@ void LLTextureFetch::shutDownImageDecodeThread()
void LLTextureFetch::startThread()
{
// Construct mCurlGetRequest from Worker Thread
mCurlGetRequest = new LLCurlRequest();
mCurlGetRequest = new AICurlInterface::Request;
}
// WORKER THREAD

View File

@@ -37,7 +37,7 @@
#include "llimage.h"
#include "lluuid.h"
#include "llworkerthread.h"
#include "llcurl.h"
#include "llcurlrequest.h"
#include "lltextureinfo.h"
#include "llapr.h"
@@ -107,7 +107,7 @@ public:
LLViewerAssetStats * main_stats);
void commandDataBreak();
LLCurlRequest & getCurlRequest() { return *mCurlGetRequest; }
AICurlInterface::Request& getCurlRequest() { return *mCurlGetRequest; }
bool isQAMode() const { return mQAMode; }
@@ -178,7 +178,7 @@ private:
LLTextureCache* mTextureCache;
LLImageDecodeThread* mImageDecodeThread;
LLCurlRequest* mCurlGetRequest;
AICurlInterface::Request* mCurlGetRequest;
// Map of all requests by UUID
typedef std::map<LLUUID,LLTextureFetchWorker*> map_t;

View File

@@ -33,6 +33,7 @@
#include "llviewerprecompiledheaders.h"
#include "lltexturestatsuploader.h"
#include "llhttpclient.h"
LLTextureStatsUploader::LLTextureStatsUploader()
{

View File

@@ -38,7 +38,10 @@
#include "llpanellogin.h"
#include "llviewercontrol.h"
#include "curl/curl.h"
#include <curl/curl.h> // curl_unescape, curl_free
#ifdef DEBUG_CURLIO
#include "debug_libcurl.h"
#endif
//static
LLURLSimString LLURLSimString::sInstance;

View File

@@ -47,9 +47,12 @@
#include "stringize.h"
// NOTE: MUST include these after otherincludes since queue gets redefined!?!!
#include <curl/curl.h>
#include <xmlrpc-epi/xmlrpc.h>
#include <curl/curl.h>
#ifdef DEBUG_CURLIO
#include "debug_libcurl.h"
#endif
// Don't define PLATFORM_STRING for unknown platforms - they need
// to get added to the login cgi script, so we want this to cause an
@@ -270,7 +273,7 @@ LLUserAuth::UserAuthcode LLUserAuth::authResponse()
return mAuthResponse;
}
bool done = mTransaction->process();
bool done = mTransaction->is_finished();
if (!done) {
if (LLXMLRPCTransaction::StatusDownloading == mTransaction->status(0))

View File

@@ -34,7 +34,6 @@
#define LLVIEWERASSETSTORAGE_H
#include "llassetstorage.h"
//#include "curl/curl.h"
class LLVFile;

View File

@@ -250,6 +250,7 @@
#include "llfloatervfs.h"
#include "llfloatervfsexplorer.h"
#include "shfloatermediaticker.h"
#include "llpacketring.h"
// </edit>
#include "scriptcounter.h"
@@ -4048,7 +4049,7 @@ void print_packets_lost(void*)
void drop_packet(void*)
{
gMessageSystem->mPacketRing.dropPackets(1);
gMessageSystem->mPacketRing->dropPackets(1);
}

View File

@@ -467,7 +467,7 @@ void LLViewerParcelMedia::sendMediaNavigateMessage(const std::string& url)
body["agent-id"] = gAgent.getID();
body["local-id"] = LLViewerParcelMgr::getInstance()->getAgentParcel()->getLocalID();
body["url"] = url;
LLHTTPClient::post(region_url, body, new LLHTTPClient::Responder);
LLHTTPClient::post(region_url, body, new LLHTTPClient::ResponderIgnore);
}
else
{

View File

@@ -1310,7 +1310,7 @@ void LLViewerParcelMgr::sendParcelPropertiesUpdate(LLParcel* parcel, bool use_ag
parcel->packMessage(body);
llinfos << "Sending parcel properties update via capability to: "
<< url << llendl;
LLHTTPClient::post(url, body, new LLHTTPClient::Responder());
LLHTTPClient::post(url, body, new LLHTTPClient::ResponderIgnore);
}
else
{

View File

@@ -151,7 +151,7 @@
#include "llnamevalue.h"
#include "llpacketack.h"
#include "llpacketbuffer.h"
#include "llpacketring.h"
//#include "llpacketring.h"
#include "llpartdata.h"
#include "llregionhandle.h"
#include "lltaskname.h"

View File

@@ -277,7 +277,7 @@ void LLViewerTextureList::shutdown()
break;
}
if (count > 0 && !gDirUtilp->getLindenUserDir(true).empty())
if (count > 0 && !gDirUtilp->getLindenUserDir(true).empty())
{
std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, get_texture_list_name());
llofstream file;

View File

@@ -76,8 +76,6 @@
#include "llagentcamera.h"
#include "curl/curl.h"
LLWaterParamManager::LLWaterParamManager() :
mFogColor(22.f/255.f, 43.f/255.f, 54.f/255.f, 0.0f, 0.0f, "waterFogColor", "WaterFogColor"),
mFogDensity(4, "waterFogDensity", 2),

View File

@@ -77,7 +77,6 @@
#include "llviewerregion.h"
#include "llassetuploadresponders.h"
#include "curl/curl.h"
#include "llstreamtools.h"
// [RLVa:KB] - Checked: 2011-09-04 (RLVa-1.4.1a) | Added: RLVa-1.4.1a

View File

@@ -61,6 +61,7 @@
#include "message.h"
#include "pipeline.h"
#include "llappviewer.h" // for do_disconnect()
#include "llpacketring.h"
#include <deque>
#include <queue>
@@ -779,8 +780,8 @@ void LLWorld::updateNetStats()
S32 packets_out = gMessageSystem->mPacketsOut - mLastPacketsOut;
S32 packets_lost = gMessageSystem->mDroppedPackets - mLastPacketsLost;
S32 actual_in_bits = gMessageSystem->mPacketRing.getAndResetActualInBits();
S32 actual_out_bits = gMessageSystem->mPacketRing.getAndResetActualOutBits();
S32 actual_in_bits = gMessageSystem->mPacketRing->getAndResetActualInBits();
S32 actual_out_bits = gMessageSystem->mPacketRing->getAndResetActualOutBits();
LLViewerStats::getInstance()->mActualInKBitStat.addValue(actual_in_bits/1024.f);
LLViewerStats::getInstance()->mActualOutKBitStat.addValue(actual_out_bits/1024.f);
LLViewerStats::getInstance()->mKBitStat.addValue(bits/1024.f);

View File

@@ -43,6 +43,11 @@
#include "llappviewer.h"
#include "hippogridmanager.h"
#include "statemachine/aicurleasyrequeststatemachine.h"
#ifdef CWDEBUG
#include <libcwd/buf2str.h>
#endif
LLXMLRPCValue LLXMLRPCValue::operator[](const char* id) const
{
@@ -154,7 +159,7 @@ class LLXMLRPCTransaction::Impl
public:
typedef LLXMLRPCTransaction::Status Status;
LLCurlEasyRequest* mCurlRequest;
AICurlEasyRequestStateMachine* mCurlEasyRequestStateMachinePtr;
Status mStatus;
CURLcode mCurlCode;
@@ -163,8 +168,6 @@ public:
LLCurl::TransferInfo mTransferInfo;
std::string mURI;
char* mRequestText;
int mRequestTextSize;
std::string mProxyAddress;
@@ -176,7 +179,8 @@ public:
const std::string& method, LLXMLRPCValue params, bool useGzip);
~Impl();
bool process();
bool is_finished(void) const;
void curlEasyRequestCallback(bool success);
void setStatus(Status code,
const std::string& message = "", const std::string& uri = "");
@@ -191,10 +195,9 @@ private:
LLXMLRPCTransaction::Impl::Impl(const std::string& uri,
XMLRPC_REQUEST request, bool useGzip)
: mCurlRequest(0),
: mCurlEasyRequestStateMachinePtr(NULL),
mStatus(LLXMLRPCTransaction::StatusNotStarted),
mURI(uri),
mRequestText(0),
mResponse(0)
{
init(request, useGzip);
@@ -203,10 +206,9 @@ LLXMLRPCTransaction::Impl::Impl(const std::string& uri,
LLXMLRPCTransaction::Impl::Impl(const std::string& uri,
const std::string& method, LLXMLRPCValue params, bool useGzip)
: mCurlRequest(0),
: mCurlEasyRequestStateMachinePtr(NULL),
mStatus(LLXMLRPCTransaction::StatusNotStarted),
mURI(uri),
mRequestText(0),
mResponse(0)
{
XMLRPC_REQUEST request = XMLRPC_RequestNew();
@@ -217,177 +219,170 @@ LLXMLRPCTransaction::Impl::Impl(const std::string& uri,
init(request, useGzip);
}
// Store pointer to data allocated with XMLRPC_REQUEST_ToXML and call XMLRPC_Free to free it upon destruction.
class AIXMLRPCData : public AIPostField
{
public:
AIXMLRPCData(char const* allocated_data) : AIPostField(allocated_data) { }
/*virtual*/ ~AIXMLRPCData() { XMLRPC_Free(const_cast<char*>(mData)); mData = NULL; }
};
void LLXMLRPCTransaction::Impl::init(XMLRPC_REQUEST request, bool useGzip)
{
if (!mCurlRequest)
{
mCurlRequest = new LLCurlEasyRequest();
try
{
mCurlEasyRequestStateMachinePtr = new AICurlEasyRequestStateMachine(false);
}
catch(AICurlNoEasyHandle const& error)
{
llwarns << "Failed to initialize LLXMLRPCTransaction: " << error.what() << llendl;
setStatus(StatusOtherError, "No curl easy handle");
return;
}
AICurlEasyRequest_wat curlEasyRequest_w(*mCurlEasyRequestStateMachinePtr->mCurlEasyRequest);
curlEasyRequest_w->setWriteCallback(&curlDownloadCallback, (void*)this);
BOOL vefifySSLCert = !gSavedSettings.getBOOL("NoVerifySSLCert");
curlEasyRequest_w->setopt(CURLOPT_SSL_VERIFYPEER, vefifySSLCert);
curlEasyRequest_w->setopt(CURLOPT_SSL_VERIFYHOST, vefifySSLCert ? 2 : 0);
// Be a little impatient about establishing connections.
curlEasyRequest_w->setopt(CURLOPT_CONNECTTIMEOUT, 40L);
/* Setting the DNS cache timeout to -1 disables it completely.
This might help with bug #503 */
curlEasyRequest_w->setopt(CURLOPT_DNS_CACHE_TIMEOUT, -1L);
curlEasyRequest_w->addHeader("Content-Type: text/xml");
if (useGzip)
{
curlEasyRequest_w->setoptString(CURLOPT_ENCODING, "");
}
int requestTextSize;
char* requestText = XMLRPC_REQUEST_ToXML(request, &requestTextSize);
if (requestText)
{
curlEasyRequest_w->setPost(new AIXMLRPCData(requestText), requestTextSize);
}
else
{
setStatus(StatusOtherError);
}
curlEasyRequest_w->finalizeRequest(mURI);
}
LLProxy::getInstance()->applyProxySettings(mCurlRequest);
// mCurlRequest->setopt(CURLOPT_VERBOSE, 1); // usefull for debugging
mCurlRequest->setopt(CURLOPT_NOSIGNAL, 1);
mCurlRequest->setWriteCallback(&curlDownloadCallback, (void*)this);
BOOL vefifySSLCert = !gSavedSettings.getBOOL("NoVerifySSLCert");
mCurlRequest->setopt(CURLOPT_SSL_VERIFYPEER, vefifySSLCert);
mCurlRequest->setopt(CURLOPT_SSL_VERIFYHOST, vefifySSLCert ? 2 : 0);
// Be a little impatient about establishing connections.
mCurlRequest->setopt(CURLOPT_CONNECTTIMEOUT, 40L);
/* Setting the DNS cache timeout to -1 disables it completely.
This might help with bug #503 */
mCurlRequest->setopt(CURLOPT_DNS_CACHE_TIMEOUT, -1);
mCurlRequest->slist_append("Content-Type: text/xml");
if (useGzip)
if (mStatus == LLXMLRPCTransaction::StatusNotStarted) // It could be LLXMLRPCTransaction::StatusOtherError.
{
mCurlRequest->setoptString(CURLOPT_ENCODING, "");
}
mRequestText = XMLRPC_REQUEST_ToXML(request, &mRequestTextSize);
if (mRequestText)
{
mCurlRequest->setoptString(CURLOPT_POSTFIELDS, mRequestText);
mCurlRequest->setopt(CURLOPT_POSTFIELDSIZE, mRequestTextSize);
mCurlEasyRequestStateMachinePtr->run(boost::bind(&LLXMLRPCTransaction::Impl::curlEasyRequestCallback, this, _1));
setStatus(LLXMLRPCTransaction::StatusStarted);
}
else
{
setStatus(StatusOtherError);
// This deletes the statemachine immediately.
mCurlEasyRequestStateMachinePtr->kill();
mCurlEasyRequestStateMachinePtr = NULL;
}
mCurlRequest->sendRequest(mURI);
}
LLXMLRPCTransaction::Impl::~Impl()
{
if (mCurlEasyRequestStateMachinePtr && mCurlEasyRequestStateMachinePtr->running())
{
llwarns << "Calling LLXMLRPCTransaction::Impl::~Impl while mCurlEasyRequestStateMachinePtr is still running" << llendl;
mCurlEasyRequestStateMachinePtr->abort();
}
if (mResponse)
{
XMLRPC_RequestFree(mResponse, 1);
}
if (mRequestText)
{
XMLRPC_Free(mRequestText);
}
delete mCurlRequest;
mCurlRequest = NULL ;
}
bool LLXMLRPCTransaction::Impl::process()
bool LLXMLRPCTransaction::Impl::is_finished(void) const
{
if(!mCurlRequest || !mCurlRequest->getEasy())
{
llwarns << "transaction failed." << llendl ;
// Nothing to process anymore. Just wait till the statemachine finished.
return mStatus != LLXMLRPCTransaction::StatusNotStarted &&
mStatus != LLXMLRPCTransaction::StatusStarted &&
mStatus != LLXMLRPCTransaction::StatusDownloading;
}
delete mCurlRequest ;
mCurlRequest = NULL ;
return true ; //failed, quit.
void LLXMLRPCTransaction::Impl::curlEasyRequestCallback(bool success)
{
llassert(mStatus == LLXMLRPCTransaction::StatusStarted || mStatus == LLXMLRPCTransaction::StatusDownloading);
AICurlEasyRequestStateMachine* state_machine = mCurlEasyRequestStateMachinePtr;
// We're done with the statemachine, one way or another.
// Set mCurlEasyRequestStateMachinePtr to NULL so we won't call mCurlEasyRequestStateMachinePtr->running() in the destructor.
// Note that the state machine auto-cleaning: it will be deleted by the main-thread after this function returns.
mCurlEasyRequestStateMachinePtr = NULL;
if (!success)
{
// AICurlEasyRequestStateMachine did abort.
// This currently only happens when libcurl didn't finish before the timer expired.
std::ostringstream msg;
F32 timeout_value = gSavedSettings.getF32("CurlRequestTimeOut");
msg << "Connection to " << mURI << " timed out (" << timeout_value << " s)!";
if (timeout_value < 40)
{
msg << "\nTry increasing CurlRequestTimeOut in Debug Settings.";
}
setStatus(LLXMLRPCTransaction::StatusOtherError, msg.str());
return;
}
switch(mStatus)
AICurlEasyRequest_wat curlEasyRequest_w(*state_machine->mCurlEasyRequest);
CURLcode result;
curlEasyRequest_w->getResult(&result, &mTransferInfo);
if (result != CURLE_OK)
{
case LLXMLRPCTransaction::StatusComplete:
case LLXMLRPCTransaction::StatusCURLError:
case LLXMLRPCTransaction::StatusXMLRPCError:
case LLXMLRPCTransaction::StatusOtherError:
{
return true;
}
case LLXMLRPCTransaction::StatusNotStarted:
{
setStatus(LLXMLRPCTransaction::StatusStarted);
break;
}
default:
{
// continue onward
}
setCurlStatus(result);
llwarns << "LLXMLRPCTransaction CURL error "
<< mCurlCode << ": " << curlEasyRequest_w->getErrorString() << llendl;
llwarns << "LLXMLRPCTransaction request URI: "
<< mURI << llendl;
return;
}
//const F32 MAX_PROCESSING_TIME = 0.05f;
//LLTimer timer;
setStatus(LLXMLRPCTransaction::StatusComplete);
mCurlRequest->perform();
mResponse = XMLRPC_REQUEST_FromXML(
mResponseText.data(), mResponseText.size(), NULL);
/*while (mCurlRequest->perform() > 0)
bool hasError = false;
bool hasFault = false;
int faultCode = 0;
std::string faultString;
LLXMLRPCValue error(XMLRPC_RequestGetError(mResponse));
if (error.isValid())
{
if (timer.getElapsedTimeF32() >= MAX_PROCESSING_TIME)
{
return false;
}
}*/
while(1)
{
CURLcode result;
bool newmsg = mCurlRequest->getResult(&result, &mTransferInfo);
if (newmsg)
{
if (result != CURLE_OK)
{
setCurlStatus(result);
llwarns << "LLXMLRPCTransaction CURL error "
<< mCurlCode << ": " << mCurlRequest->getErrorString() << llendl;
llwarns << "LLXMLRPCTransaction request URI: "
<< mURI << llendl;
return true;
}
setStatus(LLXMLRPCTransaction::StatusComplete);
mResponse = XMLRPC_REQUEST_FromXML(
mResponseText.data(), mResponseText.size(), NULL);
bool hasError = false;
bool hasFault = false;
int faultCode = 0;
std::string faultString;
LLXMLRPCValue error(XMLRPC_RequestGetError(mResponse));
if (error.isValid())
{
hasError = true;
faultCode = error["faultCode"].asInt();
faultString = error["faultString"].asString();
}
else if (XMLRPC_ResponseIsFault(mResponse))
{
hasFault = true;
faultCode = XMLRPC_GetResponseFaultCode(mResponse);
faultString = XMLRPC_GetResponseFaultString(mResponse);
}
if (hasError || hasFault)
{
setStatus(LLXMLRPCTransaction::StatusXMLRPCError);
llwarns << "LLXMLRPCTransaction XMLRPC "
<< (hasError ? "error " : "fault ")
<< faultCode << ": "
<< faultString << llendl;
llwarns << "LLXMLRPCTransaction request URI: "
<< mURI << llendl;
}
return true;
}
else
{
break; // done
}
hasError = true;
faultCode = error["faultCode"].asInt();
faultString = error["faultString"].asString();
}
else if (XMLRPC_ResponseIsFault(mResponse))
{
hasFault = true;
faultCode = XMLRPC_GetResponseFaultCode(mResponse);
faultString = XMLRPC_GetResponseFaultString(mResponse);
}
if (hasError || hasFault)
{
setStatus(LLXMLRPCTransaction::StatusXMLRPCError);
llwarns << "LLXMLRPCTransaction XMLRPC "
<< (hasError ? "error " : "fault ")
<< faultCode << ": "
<< faultString << llendl;
llwarns << "LLXMLRPCTransaction request URI: "
<< mURI << llendl;
}
return false;
}
void LLXMLRPCTransaction::Impl::setStatus(Status status,
@@ -483,6 +478,13 @@ size_t LLXMLRPCTransaction::Impl::curlDownloadCallback(
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)
@@ -511,9 +513,9 @@ LLXMLRPCTransaction::~LLXMLRPCTransaction()
delete &impl;
}
bool LLXMLRPCTransaction::process()
bool LLXMLRPCTransaction::is_finished(void) const
{
return impl.process();
return impl.is_finished();
}
LLXMLRPCTransaction::Status LLXMLRPCTransaction::status(int* curlCode)

View File

@@ -110,8 +110,8 @@ public:
StatusOtherError
} Status;
bool process();
// run the request a little, returns true when done
bool is_finished(void) const;
// Returns true when done.
Status status(int* curlCode);
// return status, and extended CURL code, if code isn't null
@@ -127,7 +127,7 @@ public:
// retains ownership of the result object, don't free it
F64 transferRate();
// only valid if StsatusComplete, otherwise 0.0
// only valid if StatusComplete, otherwise 0.0
private:
class Impl;

View File

@@ -43,6 +43,7 @@
#include "llviewercontrol.h"
#include "llviewernetwork.h"
#include "llviewerobject.h"
#include "llpacketring.h"
#include <sstream>
ScriptCounter* ScriptCounter::sInstance;
@@ -201,8 +202,8 @@ void ScriptCounter::serializeSelection(bool delScript)
F32 throttle = gSavedSettings.getF32("OutBandwidth");
if((throttle == 0.f) || (throttle > 128000.f))
{
gMessageSystem->mPacketRing.setOutBandwidth(128000);
gMessageSystem->mPacketRing.setUseOutThrottle(TRUE);
gMessageSystem->mPacketRing->setOutBandwidth(128000);
gMessageSystem->mPacketRing->setUseOutThrottle(TRUE);
}
showResult(llformat("Counting scripts, please wait..."));
if((objectCount == 1) && !(foo->isAvatar()))
@@ -340,13 +341,13 @@ void ScriptCounter::completechk()
F32 throttle = gSavedSettings.getF32("OutBandwidth");
if(throttle != 0.f)
{
gMessageSystem->mPacketRing.setOutBandwidth(throttle);
gMessageSystem->mPacketRing.setUseOutThrottle(TRUE);
gMessageSystem->mPacketRing->setOutBandwidth(throttle);
gMessageSystem->mPacketRing->setUseOutThrottle(TRUE);
}
else
{
gMessageSystem->mPacketRing.setOutBandwidth(0.0);
gMessageSystem->mPacketRing.setUseOutThrottle(FALSE);
gMessageSystem->mPacketRing->setOutBandwidth(0.0);
gMessageSystem->mPacketRing->setUseOutThrottle(FALSE);
}
llinfos << "Sending readout to chat..." << llendl;
showResult(user_msg);

View File

@@ -5,7 +5,7 @@ project(statemachine)
include(00-Common)
include(LLCommon)
include(LLPlugin)
include(LLMessage) # This is needed by LLPlugin.
include(LLMessage)
include(LLMath)
include(LLVFS)
include(LLXML)
@@ -36,6 +36,7 @@ include_directories(
set(statemachine_SOURCE_FILES
aistatemachine.cpp
aicurleasyrequeststatemachine.cpp
aifilepicker.cpp
aifetchinventoryfolder.cpp
aievent.cpp
@@ -45,6 +46,7 @@ set(statemachine_SOURCE_FILES
set(statemachine_HEADER_FILES
CMakeLists.txt
aistatemachine.h
aicurleasyrequeststatemachine.h
aifilepicker.h
aidirpicker.h
aifetchinventoryfolder.h

View File

@@ -0,0 +1,249 @@
/**
* @file aicurleasyrequeststatemachine.cpp
* @brief Implementation of AICurlEasyRequestStateMachine
*
* Copyright (c) 2012, Aleric Inglewood.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution.
*
* CHANGELOG
* and additional copyright holders.
*
* 06/05/2012
* Initial version, written by Aleric Inglewood @ SL
*/
#include "linden_common.h"
#include "aicurleasyrequeststatemachine.h"
#include "llcontrol.h"
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_removed_after_finished
};
char const* AICurlEasyRequestStateMachine::state_str_impl(state_type run_state) const
{
switch(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);
}
return "UNKNOWN STATE";
}
void AICurlEasyRequestStateMachine::initialize_impl(void)
{
{
AICurlEasyRequest_wat curlEasyRequest_w(*mCurlEasyRequest);
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_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(mFinished ? AICurlEasyRequestStateMachine_removed_after_finished : AICurlEasyRequestStateMachine_removed);
}
void AICurlEasyRequestStateMachine::multiplex_impl(void)
{
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.
// 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
// 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
// in libcurl; but lets be sure and set a timer for inactivity.
static LLCachedControl<F32> CurlRequestTimeOut("CurlRequestTimeOut", 40.f);
mTimer = new AIPersistentTimer; // Do not delete timer upon expiration.
mTimer->setInterval(CurlRequestTimeOut);
mTimer->run(this, AICurlEasyRequestStateMachine_timedOut, false, false);
break;
}
case AICurlEasyRequestStateMachine_added:
{
// 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:
{
// 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 (!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->timed_out();
}
// 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;
}
}
}
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 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();
}
}
void AICurlEasyRequestStateMachine::finish_impl(void)
{
DoutEntering(dc::curl, "AICurlEasyRequestStateMachine::finish_impl() [" << (void*)this << "] [" << (void*)mCurlEasyRequest.get() << "]");
// Revoke callbacks.
{
AICurlEasyRequest_wat curl_easy_request_w(*mCurlEasyRequest);
curl_easy_request_w->send_events_to(NULL);
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, if it's still running.
if (!mHandled)
mTimer->abort();
// Auto clean up ourselves.
kill();
}
AICurlEasyRequestStateMachine::AICurlEasyRequestStateMachine(bool buffered) : mBuffered(buffered), mCurlEasyRequest(buffered)
{
Dout(dc::statemachine, "Calling AICurlEasyRequestStateMachine(" << (buffered ? "true" : "false") << ") [" << (void*)this << "] [" << (void*)mCurlEasyRequest.get() << "]");
}
AICurlEasyRequestStateMachine::~AICurlEasyRequestStateMachine()
{
Dout(dc::statemachine, "Calling ~AICurlEasyRequestStateMachine() [" << (void*)this << "] [" << (void*)mCurlEasyRequest.get() << "]");
}

View File

@@ -0,0 +1,102 @@
/**
* @file aicurleasyrequest.h
* @brief Perform a curl easy request.
*
* Copyright (c) 2012, Aleric Inglewood.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution.
*
* CHANGELOG
* and additional copyright holders.
*
* 06/05/2012
* Initial version, written by Aleric Inglewood @ SL
*/
#ifndef AICURLEASYREQUEST_H
#define AICURLEASYREQUEST_H
#include "aistatemachine.h"
#include "aitimer.h"
#include "aicurl.h"
// A curl easy request state machine.
//
// Before calling cersm.run() initialize the object (cersm) as follows:
//
// AICurlEasyRequest_wat cersm_w(cersm);
// cersm_w->setopt(...); // etc, see the interface of AICurlPrivate::CurlEasyRequest and it's base class AICurlPrivate::CurlEasyHandle.
//
// When the state machine finishes, call aborted() to check
// whether or not the statemachine succeeded in fetching
// the URL or not.
//
// Objects of this type can be reused multiple times, see
// also the documentation of AIStateMachine.
//
// Construction of a AICurlEasyRequestStateMachine might throw AICurlNoEasyHandle.
class AICurlEasyRequestStateMachine : public AIStateMachine, public AICurlEasyHandleEvents {
public:
AICurlEasyRequestStateMachine(bool buffered);
// Transparent access.
AICurlEasyRequest mCurlEasyRequest;
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:
// AICurlEasyRequest Events.
// Called when this curl easy handle was added to a multi handle.
/*virtual*/ void added_to_multi_handle(AICurlEasyRequest_wat&);
// Called when this curl easy handle finished processing (right before it is removed from the multi handle).
/*virtual*/ void finished(AICurlEasyRequest_wat&);
// Called after this curl easy handle was removed from a multi handle.
/*virtual*/ void removed_from_multi_handle(AICurlEasyRequest_wat&);
protected:
// AIStateMachine implementations.
// Call finish() (or abort()), not delete.
/*virtual*/ ~AICurlEasyRequestStateMachine();
// Handle initializing the object.
/*virtual*/ void initialize_impl(void);
// Handle mRunState.
/*virtual*/ void multiplex_impl(void);
// Handle aborting from current bs_run state.
/*virtual*/ void abort_impl(void);
// Handle cleaning up from initialization (or post abort) state.
/*virtual*/ void finish_impl(void);
// Implemenation of state_str for run states.
/*virtual*/ char const* state_str_impl(state_type run_state) const;
};
#endif

View File

@@ -44,35 +44,35 @@ extern LLControlGroup gSavedSettings;
// Local variables.
namespace {
struct QueueElementComp;
struct QueueElementComp;
class QueueElement {
private:
AIStateMachine* mStateMachine;
U64 mRuntime;
class QueueElement {
private:
AIStateMachine* mStateMachine;
U64 mRuntime;
public:
QueueElement(AIStateMachine* statemachine) : mStateMachine(statemachine), mRuntime(0) { }
friend bool operator==(QueueElement const& e1, QueueElement const& e2) { return e1.mStateMachine == e2.mStateMachine; }
friend struct QueueElementComp;
public:
QueueElement(AIStateMachine* statemachine) : mStateMachine(statemachine), mRuntime(0) { }
friend bool operator==(QueueElement const& e1, QueueElement const& e2) { return e1.mStateMachine == e2.mStateMachine; }
friend struct QueueElementComp;
AIStateMachine& statemachine(void) const { return *mStateMachine; }
void add(U64 count) { mRuntime += count; }
};
AIStateMachine& statemachine(void) const { return *mStateMachine; }
void add(U64 count) { mRuntime += count; }
};
struct QueueElementComp {
bool operator()(QueueElement const& e1, QueueElement const& e2) const { return e1.mRuntime < e2.mRuntime; }
};
struct QueueElementComp {
bool operator()(QueueElement const& e1, QueueElement const& e2) const { return e1.mRuntime < e2.mRuntime; }
};
typedef std::vector<QueueElement> active_statemachines_type;
active_statemachines_type active_statemachines;
typedef std::vector<AIStateMachine*> continued_statemachines_type;
struct cscm_type
{
continued_statemachines_type continued_statemachines;
bool calling_mainloop;
};
AIThreadSafeDC<cscm_type> continued_statemachines_and_calling_mainloop;
typedef std::vector<QueueElement> active_statemachines_type;
active_statemachines_type active_statemachines;
typedef std::vector<AIStateMachine*> continued_statemachines_type;
struct cscm_type
{
continued_statemachines_type continued_statemachines;
bool calling_mainloop;
};
AIThreadSafeDC<cscm_type> continued_statemachines_and_calling_mainloop;
}
// static
@@ -80,8 +80,14 @@ AIThreadSafeSimpleDC<U64> AIStateMachine::sMaxCount;
void AIStateMachine::updateSettings(void)
{
static const LLCachedControl<U32> StateMachineMaxTime("StateMachineMaxTime", 20);
static U32 last_StateMachineMaxTime = 0;
if (last_StateMachineMaxTime != StateMachineMaxTime)
{
Dout(dc::statemachine, "Initializing AIStateMachine::sMaxCount");
*AIAccess<U64>(sMaxCount) = calc_clock_frequency() * gSavedSettings.getU32("StateMachineMaxTime") / 1000;
*AIAccess<U64>(sMaxCount) = calc_clock_frequency() * StateMachineMaxTime / 1000;
last_StateMachineMaxTime = StateMachineMaxTime;
}
}
//----------------------------------------------------------------------------
@@ -89,228 +95,366 @@ 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.
llassert(!mParent || mState == bs_callback);
llassert(!mCallback || mState == bs_callback);
// Can only be run when in this state.
llassert(mState == bs_initialize || mState == bs_callback);
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.
llassert(!mParent || mState == bs_callback);
llassert(!mCallback || mState == bs_callback);
// Can only be run when in this state.
llassert(mState == bs_initialize || mState == bs_callback);
// Allow NULL to be passed as parent to signal that we want to reuse the old one.
if (parent)
{
mParent = parent;
// In that case remove any old callback!
if (mCallback)
{
delete mCallback;
mCallback = NULL;
}
// Allow NULL to be passed as parent to signal that we want to reuse the old one.
if (parent)
{
mParent = parent;
// In that case remove any old callback!
if (mCallback)
{
delete mCallback;
mCallback = NULL;
}
mNewParentState = new_parent_state;
mAbortParent = abort_parent;
}
mNewParentState = new_parent_state;
mAbortParent = abort_parent;
mOnAbortSignalParent = on_abort_signal_parent;
}
// If abort_parent is requested then a parent must be provided.
llassert(!abort_parent || mParent);
// If a parent is provided, it must be running.
llassert(!mParent || mParent->mState == bs_run);
// If abort_parent is requested then a parent must be provided.
llassert(!abort_parent || mParent);
// If a parent is provided, it must be running.
llassert(!mParent || mParent->mState == bs_run);
// Mark that run() has been called, in case we're being called from a callback function.
mState = bs_initialize;
// Mark that run() has been called, in case we're being called from a callback function.
mState = bs_initialize;
cont();
// Set mIdle to false and add statemachine to continued_statemachines.
mSetStateLock.lock();
locked_cont();
}
void AIStateMachine::run(callback_type::signal_type::slot_type const& slot)
{
DoutEntering(dc::statemachine, "AIStateMachine::run(<slot>) [" << (void*)this << "]");
// Must be the first time we're being run, or we must be called from a callback function.
llassert(!mParent || mState == bs_callback);
llassert(!mCallback || mState == bs_callback);
// Can only be run when in this state.
llassert(mState == bs_initialize || mState == bs_callback);
DoutEntering(dc::statemachine, "AIStateMachine::run(<slot>) [" << (void*)this << "]");
// Must be the first time we're being run, or we must be called from a callback function.
llassert(!mParent || mState == bs_callback);
llassert(!mCallback || mState == bs_callback);
// Can only be run when in this state.
llassert(mState == bs_initialize || mState == bs_callback);
// Clean up any old callbacks.
mParent = NULL;
if (mCallback)
{
delete mCallback;
mCallback = NULL;
}
// Clean up any old callbacks.
mParent = NULL;
if (mCallback)
{
delete mCallback;
mCallback = NULL;
}
mCallback = new callback_type(slot);
mCallback = new callback_type(slot);
// Mark that run() has been called, in case we're being called from a callback function.
mState = bs_initialize;
// Mark that run() has been called, in case we're being called from a callback function.
mState = bs_initialize;
cont();
// Set mIdle to false and add statemachine to continued_statemachines.
mSetStateLock.lock();
locked_cont();
}
void AIStateMachine::idle(void)
{
DoutEntering(dc::statemachine, "AIStateMachine::idle() [" << (void*)this << "]");
llassert(!mIdle);
mIdle = true;
mSleep = 0;
DoutEntering(dc::statemachine, "AIStateMachine::idle() [" << (void*)this << "]");
llassert(is_main_thread());
llassert(!mIdle);
mIdle = true;
mSleep = 0;
#ifdef SHOW_ASSERT
mCalledThreadUnsafeIdle = true;
#endif
}
void AIStateMachine::idle(state_type current_run_state)
{
DoutEntering(dc::statemachine, "AIStateMachine::idle(" << state_str(current_run_state) << ") [" << (void*)this << "]");
llassert(is_main_thread());
llassert(!mIdle);
mSetStateLock.lock();
// Only go idle if the run state is (still) what we expect it to be.
// Otherwise assume that another thread called set_state() and continue running.
if (current_run_state == mRunState)
{
mIdle = true;
mSleep = 0;
}
mSetStateLock.unlock();
}
// About thread safeness:
//
// The main thread initializes a statemachine and calls run, so a statemachine
// runs in the main thread. However, it is allowed that a state calls idle()
// and then allows one and only one other thread to call cont() upon some
// and then allows one or more other threads to call cont() upon some
// event (only once, of course, as idle() has to be called before cont()
// can be called again-- and another thread is not allowed to call idle()).
// Instead of cont(), the other thread may also call set_state().
void AIStateMachine::cont(void)
// can be called again-- and a non-main thread is not allowed to call idle()).
// Instead of cont() one may also call set_state().
// Of course, this may give rise to a race condition; if that happens then
// the thread that calls cont() (set_state()) first is serviced, and the other
// thread(s) are ignored, as if they never called cont().
void AIStateMachine::locked_cont(void)
{
DoutEntering(dc::statemachine, "AIStateMachine::cont() [" << (void*)this << "]");
llassert(mIdle);
// Atomic test mActive and change mIdle.
mIdleActive.lock();
mIdle = false;
bool not_active = mActive == as_idle;
mIdleActive.unlock();
if (not_active)
{
AIWriteAccess<cscm_type> cscm_w(continued_statemachines_and_calling_mainloop);
// We only get here when the statemachine was idle (set by the main thread),
// see first assertion. Hence, the main thread is not changing this, as the
// statemachine is not running. Thus, mActive can have changed when a THIRD
// thread called cont(), which is not allowed: if two threads can call cont()
// at any moment then the first assertion can't hold.
llassert_always(mActive == as_idle);
cscm_w->continued_statemachines.push_back(this);
if (!cscm_w->calling_mainloop)
{
Dout(dc::statemachine, "Adding AIStateMachine::mainloop to gIdleCallbacks");
cscm_w->calling_mainloop = true;
gIdleCallbacks.addFunction(&AIStateMachine::mainloop);
}
mActive = as_queued;
llassert_always(!mIdle); // It should never happen that one thread calls cont() while another calls idle() concurrently.
}
DoutEntering(dc::statemachine, "AIStateMachine::locked_cont() [" << (void*)this << "]");
llassert(mIdle);
// Atomic test mActive and change mIdle.
mIdleActive.lock();
#ifdef SHOW_ASSERT
mContThread.reset();
#endif
mIdle = false;
bool not_active = mActive == as_idle;
mIdleActive.unlock();
// mActive is only changed in AIStateMachine::mainloop, by the main-thread, and
// here, possibly by any thread. However, after setting mIdle to false above, it
// is impossible for any thread to come here, until after the main-thread called
// idle(). So, if this is the main thread then that certainly isn't going to
// happen until we left this function, while if this is another thread and the
// state machine is already running in the main thread then not_active is false
// and we're already at the end of this function.
// If not_active is true then main-thread is not running this statemachine.
// It might call cont() (or set_state()) but never locked_cont(), and will never
// start actually running until we are done here and release the lock on
// continued_statemachines_and_calling_mainloop again. It is therefore safe
// to release mSetStateLock here, with as advantage that if we're not the main-
// thread and not_active is true, then the main-thread won't block when it has
// a timer running that times out and calls set_state().
mSetStateLock.unlock();
if (not_active)
{
AIWriteAccess<cscm_type> cscm_w(continued_statemachines_and_calling_mainloop);
// See above: it is not possible that mActive was changed since not_active
// was set to true above.
llassert_always(mActive == as_idle);
Dout(dc::statemachine, "Adding " << (void*)this << " to continued_statemachines");
cscm_w->continued_statemachines.push_back(this);
if (!cscm_w->calling_mainloop)
{
Dout(dc::statemachine, "Adding AIStateMachine::mainloop to gIdleCallbacks");
cscm_w->calling_mainloop = true;
gIdleCallbacks.addFunction(&AIStateMachine::mainloop);
}
mActive = as_queued;
llassert_always(!mIdle); // It should never happen that the main thread calls idle(), while another thread calls cont() concurrently.
}
}
void AIStateMachine::set_state(state_type state)
{
DoutEntering(dc::statemachine, "AIStateMachine::set_state(" << state_str(state) << ") [" << (void*)this << "]");
llassert(mState == bs_run);
if (mRunState != state)
{
mRunState = state;
Dout(dc::statemachine, "mRunState set to " << state_str(mRunState));
}
if (mIdle)
cont();
DoutEntering(dc::statemachine, "AIStateMachine::set_state(" << state_str(state) << ") [" << (void*)this << "]");
// Stop race condition of multiple threads calling cont() or set_state() here.
mSetStateLock.lock();
// Do not call set_state() unless running.
llassert(mState == bs_run || !is_main_thread());
// If this function is called from another thread than the main thread, then we have to ignore
// it if we're not idle and the state is less than the current state. The main thread must
// be able to change the state to anything (also smaller values). Note that that only can work
// if the main thread itself at all times cancels thread callbacks that call set_state()
// before calling idle() again!
//
// Thus: main thead calls idle(), and tells one or more threads to do callbacks on events,
// which (might) call set_state(). If the main thread calls set_state first (currently only
// possible as a result of the use of a timer) it will set mIdle to false (here) then cancel
// the call backs from the other threads and only then call idle() again.
// Thus if you want other threads get here while mIdle is false to be ignored then the
// main thread should use a large value for the new run state.
//
// If a non-main thread calls set_state first, then the state is changed but the main thread
// can still override it if it calls set_state before handling the new state; in the latter
// case it would still be as if the call from the non-main thread was ignored.
//
// Concurrent calls from non-main threads however, always result in the largest state
// to prevail.
// If the state machine is already running, and we are not the main-thread and the new
// 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 && state <= mRunState && !AIThreadID::in_main_thread()))
{
#ifdef SHOW_ASSERT
// It's a bit weird if the same thread does two calls on a row where the second call
// has a smaller value: warn about that.
if (mState == bs_run && mContThread.equals_current_thread())
{
llwarns << "Ignoring call to set_state(" << state_str(state) <<
") by non-main thread before main-thread could react on previous call, "
"because new state is smaller than old state (" << state_str(mRunState) << ")." << llendl;
}
#endif
mSetStateLock.unlock();
return; // Ignore.
}
// Do not call idle() when set_state is called from another thread; use idle(state_type) instead.
llassert(!mCalledThreadUnsafeIdle || is_main_thread());
// Change mRunState to the requested value.
if (mRunState != state)
{
mRunState = state;
Dout(dc::statemachine, "mRunState set to " << state_str(mRunState));
}
// Continue the state machine if appropriate.
if (mIdle)
locked_cont(); // This unlocks mSetStateLock.
else
mSetStateLock.unlock();
// If we get here then mIdle is false, so only mRunState can still be changed but we won't
// call locked_cont() anymore. When the main thread finally picks up on the state change,
// it will cancel any possible callbacks from other threads and process the largest state
// that this function was called with in the meantime.
}
void AIStateMachine::abort(void)
{
DoutEntering(dc::statemachine, "AIStateMachine::abort() [" << (void*)this << "]");
llassert(mState == bs_run);
mState = bs_abort;
abort_impl();
mAborted = true;
finish();
DoutEntering(dc::statemachine, "AIStateMachine::abort() [" << (void*)this << "]");
// It's possible that abort() is called before calling AIStateMachine::multiplex.
// In that case the statemachine wasn't initialized yet and we should just kill() it.
if (LL_UNLIKELY(mState == bs_initialize))
{
// It's ok to use the thread-unsafe idle() here, because if the statemachine
// wasn't started yet, then other threads won't call set_state() on it.
if (!mIdle)
idle();
// run() calls locked_cont() after which the top of the mainloop adds this
// state machine to active_statemachines. Therefore, if the following fails
// then either the same statemachine called run() immediately followed by abort(),
// which is not allowed; or there were two active statemachines running,
// the first created a new statemachine and called run() on it, and then
// the other (before reaching the top of the mainloop) called abort() on
// that freshly created statemachine. Obviously, this is highly unlikely,
// but if that is the case then here we bump the statemachine into
// continued_statemachines to prevent kill() to delete this statemachine:
// the caller of abort() does not expect that.
if (LL_UNLIKELY(mActive == as_idle))
{
mSetStateLock.lock();
locked_cont();
idle();
}
kill();
}
else
{
llassert(mState == bs_run);
mSetStateLock.lock();
mState = bs_abort; // Causes additional calls to set_state to be ignored.
mSetStateLock.unlock();
abort_impl();
mAborted = true;
finish();
}
}
void AIStateMachine::finish(void)
{
DoutEntering(dc::statemachine, "AIStateMachine::finish() [" << (void*)this << "]");
llassert(mState == bs_run || mState == bs_abort);
// It is possible that mIdle is true when abort or finish was called from
// outside multiplex_impl. However, that only may be done by the main thread.
llassert(!mIdle || is_main_thread());
if (!mIdle)
idle();
mState = bs_finish;
finish_impl();
// Did finish_impl call kill()? Then that is only the default. Remember it.
bool default_delete = (mState == bs_killed);
mState = bs_finish;
if (mParent)
{
// It is possible that the parent is not running when the parent is in fact aborting and called
// abort on this object from it's abort_impl function. It that case we don't want to recursively
// call abort again (or change it's state).
if (mParent->running())
{
if (mAborted && mAbortParent)
{
mParent->abort();
mParent = NULL;
}
else
{
mParent->set_state(mNewParentState);
}
}
}
// After this (bool)*this evaluates to true and we can call the callback, which then is allowed to call run().
mState = bs_callback;
if (mCallback)
{
// This can/may call kill() that sets mState to bs_kill and in which case the whole AIStateMachine
// will be deleted from the mainloop, or it may call run() that sets mState is set to bs_initialize
// and might change or reuse mCallback or mParent.
mCallback->callback(!mAborted);
if (mState != bs_initialize)
{
delete mCallback;
mCallback = NULL;
mParent = NULL;
}
}
else
{
// Not restarted by callback. Allow run() to be called later on.
mParent = NULL;
}
// Fix the final state.
if (mState == bs_callback)
mState = default_delete ? bs_killed : bs_initialize;
if (mState == bs_killed && mActive == as_idle)
{
// Bump the statemachine onto the active statemachine list, or else it won't be deleted.
cont();
idle();
}
DoutEntering(dc::statemachine, "AIStateMachine::finish() [" << (void*)this << "]");
mSetStateLock.lock();
llassert(mState == bs_run || mState == bs_abort);
// It is possible that mIdle is true when abort or finish was called from
// outside multiplex_impl. However, that only may be done by the main thread.
llassert(!mIdle || is_main_thread());
if (!mIdle)
idle(); // After calling this, we don't want other threads to call set_state() anymore.
mState = bs_finish; // Causes additional calls to set_state to be ignored.
mSetStateLock.unlock();
finish_impl();
// Did finish_impl call kill()? Then that is only the default. Remember it.
bool default_delete = (mState == bs_killed);
mState = bs_finish;
if (mParent)
{
// It is possible that the parent is not running when the parent is in fact aborting and called
// abort on this object from it's abort_impl function. It that case we don't want to recursively
// call abort again (or change it's state).
if (mParent->running())
{
if (mAborted && mAbortParent)
{
mParent->abort();
mParent = NULL;
}
else if (!mAborted || mOnAbortSignalParent)
{
mParent->set_state(mNewParentState);
}
}
}
// After this (bool)*this evaluates to true and we can call the callback, which then is allowed to call run().
mState = bs_callback;
if (mCallback)
{
// This can/may call kill() that sets mState to bs_kill and in which case the whole AIStateMachine
// will be deleted from the mainloop, or it may call run() that sets mState is set to bs_initialize
// and might change or reuse mCallback or mParent.
mCallback->callback(!mAborted);
if (mState != bs_initialize)
{
delete mCallback;
mCallback = NULL;
mParent = NULL;
}
}
else
{
// Not restarted by callback. Allow run() to be called later on.
mParent = NULL;
}
// Fix the final state.
if (mState == bs_callback)
mState = default_delete ? bs_killed : bs_initialize;
if (mState == bs_killed && mActive == as_idle)
{
// Bump the statemachine onto the active statemachine list, or else it won't be deleted.
mSetStateLock.lock();
locked_cont();
idle();
}
}
void AIStateMachine::kill(void)
{
// Should only be called from finish() (or when not running (bs_initialize)).
llassert(mIdle && (mState == bs_callback || mState == bs_finish || mState == bs_initialize));
base_state_type prev_state = mState;
mState = bs_killed;
if (prev_state == bs_initialize)
{
// We're not running (ie being deleted by a parent statemachine), delete it immediately.
delete this;
}
DoutEntering(dc::statemachine, "AIStateMachine::kill() [" << (void*)this << "]");
// Should only be called from finish() (or when not running (bs_initialize)).
// However, also allow multiple calls to kill() on a row (bs_killed) (which effectively don't do anything).
llassert(mIdle && (mState == bs_callback || mState == bs_finish || mState == bs_initialize || mState == bs_killed));
base_state_type prev_state = mState;
mState = bs_killed;
if (prev_state == bs_initialize && mActive == as_idle)
{
// We're not running (ie being deleted by a parent statemachine), delete it immediately.
delete this;
}
}
// Return stringified 'state'.
char const* AIStateMachine::state_str(state_type state)
{
if (state >= min_state && state < max_state)
{
switch (state)
{
AI_CASE_RETURN(bs_initialize);
AI_CASE_RETURN(bs_run);
AI_CASE_RETURN(bs_abort);
AI_CASE_RETURN(bs_finish);
AI_CASE_RETURN(bs_callback);
AI_CASE_RETURN(bs_killed);
}
}
return state_str_impl(state);
if (state >= min_state && state < max_state)
{
switch (state)
{
AI_CASE_RETURN(bs_initialize);
AI_CASE_RETURN(bs_run);
AI_CASE_RETURN(bs_abort);
AI_CASE_RETURN(bs_finish);
AI_CASE_RETURN(bs_callback);
AI_CASE_RETURN(bs_killed);
}
}
return state_str_impl(state);
}
//----------------------------------------------------------------------------
@@ -320,135 +464,183 @@ char const* AIStateMachine::state_str(state_type state)
void AIStateMachine::multiplex(U64 current_time)
{
// Return immediately when this state machine is sleeping.
// A negative value of mSleep means we're counting frames,
// a positive value means we're waiting till a certain
// amount of time has passed.
if (mSleep != 0)
{
if (mSleep < 0)
{
if (++mSleep)
return;
}
else
{
if (current_time < (U64)mSleep)
return;
mSleep = 0;
}
}
// Return immediately when this state machine is sleeping.
// A negative value of mSleep means we're counting frames,
// a positive value means we're waiting till a certain
// amount of time has passed.
if (mSleep != 0)
{
if (mSleep < 0)
{
if (++mSleep)
return;
}
else
{
if (current_time < (U64)mSleep)
return;
mSleep = 0;
}
}
DoutEntering(dc::statemachine, "AIStateMachine::multiplex() [" << (void*)this << "] [with state: " << state_str(mState == bs_run ? mRunState : mState) << "]");
llassert(mState == bs_initialize || mState == bs_run);
DoutEntering(dc::statemachine, "AIStateMachine::multiplex() [" << (void*)this << "] [with state: " << state_str(mState == bs_run ? mRunState : mState) << "]");
llassert(mState == bs_initialize || mState == bs_run);
// Real state machine starts here.
if (mState == bs_initialize)
{
mAborted = false;
mState = bs_run;
initialize_impl();
if (mAborted || mState != bs_run)
return;
}
multiplex_impl();
// Real state machine starts here.
if (mState == bs_initialize)
{
mAborted = false;
mState = bs_run;
initialize_impl();
if (mAborted || mState != bs_run)
return;
}
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();
}
llassert(!active_statemachines.empty());
// Run one or more state machines.
U64 total_clocks = 0;
U64 max_count = *AIAccess<U64>(sMaxCount);
for (active_statemachines_type::iterator iter = active_statemachines.begin(); iter != active_statemachines.end(); ++iter)
{
AIStateMachine& statemachine(iter->statemachine());
if (!statemachine.mIdle)
{
U64 start = get_clock_count();
// 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);
U64 delta = get_clock_count() - start;
iter->add(delta);
total_clocks += delta;
if (total_clocks >= max_count)
{
LLFastTimer t(FTM_STATEMACHINE);
add_continued_statemachines();
llassert(!active_statemachines.empty());
// Run one or more state machines.
U64 total_clocks = 0;
U64 max_count = *AIAccess<U64>(sMaxCount);
for (active_statemachines_type::iterator iter = active_statemachines.begin(); iter != active_statemachines.end(); ++iter)
{
AIStateMachine& statemachine(iter->statemachine());
if (!statemachine.mIdle)
{
U64 start = get_clock_count();
// 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.
statemachine.multiplex(start);
U64 delta = get_clock_count() - start;
iter->add(delta);
total_clocks += delta;
if (total_clocks >= max_count)
{
#ifndef LL_RELEASE_FOR_DOWNLOAD
llwarns << "AIStateMachine::mainloop did run for " << (total_clocks * 1000 / calc_clock_frequency()) << " ms." << llendl;
llwarns << "AIStateMachine::mainloop did run for " << (total_clocks * 1000 / calc_clock_frequency()) << " ms." << llendl;
#endif
std::sort(active_statemachines.begin(), active_statemachines.end(), QueueElementComp());
break;
}
}
std::sort(active_statemachines.begin(), active_statemachines.end(), QueueElementComp());
break;
}
}
// Remove idle state machines from the loop.
active_statemachines_type::iterator iter = active_statemachines.begin();
while (iter != active_statemachines.end())
}
// Remove idle state machines from the loop.
active_statemachines_type::iterator iter = active_statemachines.begin();
while (iter != active_statemachines.end())
{
AIStateMachine& statemachine(iter->statemachine());
// Atomic test mIdle and change mActive.
bool locked = statemachine.mIdleActive.tryLock();
// If the lock failed, then another thread is in the middle of calling cont(),
// thus mIdle will end up false. So, there is no reason to block here; just
// treat mIdle as false already.
if (locked && statemachine.mIdle)
{
AIStateMachine& statemachine(iter->statemachine());
// Atomic test mIdle and change mActive.
bool locked = statemachine.mIdleActive.tryLock();
// If the lock failed, then another thread is in the middle of calling cont(),
// thus mIdle will end up false. So, there is no reason to block here; just
// treat mIdle as false already.
if (locked && statemachine.mIdle)
{
// Without the lock, it would be possible that another thread called cont() right here,
// changing mIdle to false again but NOT adding the statemachine to continued_statemachines,
// thinking it is in active_statemachines (and it is), while immediately below it is
// erased from active_statemachines.
statemachine.mActive = as_idle;
// Now, calling cont() is ok -- as that will cause the statemachine to be added to
// continued_statemachines, so it's fine in that case-- even necessary-- to remove it from
// active_statemachines regardless, and we can release the lock here.
statemachine.mIdleActive.unlock();
Dout(dc::statemachine, "Erasing " << (void*)&statemachine << " from active_statemachines");
iter = active_statemachines.erase(iter);
if (statemachine.mState == bs_killed)
{
Dout(dc::statemachine, "Deleting " << (void*)&statemachine);
delete &statemachine;
}
}
else
{
if (locked)
{
statemachine.mIdleActive.unlock();
}
llassert(statemachine.mActive == as_active); // It should not be possible that another thread called cont() and changed this when we are we are not idle.
llassert(statemachine.mState == bs_run || statemachine.mState == bs_initialize);
++iter;
}
// Without the lock, it would be possible that another thread called cont() right here,
// changing mIdle to false again but NOT adding the statemachine to continued_statemachines,
// thinking it is in active_statemachines (and it is), while immediately below it is
// erased from active_statemachines.
statemachine.mActive = as_idle;
// Now, calling cont() is ok -- as that will cause the statemachine to be added to
// continued_statemachines, so it's fine in that case-- even necessary-- to remove it from
// active_statemachines regardless, and we can release the lock here.
statemachine.mIdleActive.unlock();
Dout(dc::statemachine, "Erasing " << (void*)&statemachine << " from active_statemachines");
iter = active_statemachines.erase(iter);
if (statemachine.mState == bs_killed)
{
Dout(dc::statemachine, "Deleting " << (void*)&statemachine);
delete &statemachine;
}
}
if (active_statemachines.empty())
else
{
// If this was the last state machine, remove mainloop from the IdleCallbacks.
AIReadAccess<cscm_type> cscm_r(continued_statemachines_and_calling_mainloop);
if (cscm_r->continued_statemachines.empty() && cscm_r->calling_mainloop)
{
Dout(dc::statemachine, "Removing AIStateMachine::mainloop from gIdleCallbacks");
AIWriteAccess<cscm_type>(cscm_r)->calling_mainloop = false;
gIdleCallbacks.deleteFunction(&AIStateMachine::mainloop);
}
if (locked)
{
statemachine.mIdleActive.unlock();
}
llassert(statemachine.mActive == as_active); // It should not be possible that another thread called cont() and changed this when we are we are not idle.
llassert(statemachine.mState == bs_run || statemachine.mState == bs_initialize);
++iter;
}
}
if (active_statemachines.empty())
{
// If this was the last state machine, remove mainloop from the IdleCallbacks.
AIReadAccess<cscm_type> cscm_r(continued_statemachines_and_calling_mainloop);
if (cscm_r->continued_statemachines.empty() && cscm_r->calling_mainloop)
{
Dout(dc::statemachine, "Removing AIStateMachine::mainloop from gIdleCallbacks");
AIWriteAccess<cscm_type>(cscm_r)->calling_mainloop = false;
gIdleCallbacks.deleteFunction(&AIStateMachine::mainloop);
}
}
}
// 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.abortable())
{
// We can't safely call abort() here for non-running (run() was called, but they we're initialized yet) statemachines,
// because that might call kill() which in some cases is undesirable (ie, when it is owned by a partent that will
// also call abort() on it when it is aborted itself).
if (statemachine.running())
statemachine.abort();
else
statemachine.idle(); // Stop the statemachine from starting, in the next loop with batch == 0.
}
}
for (int batch = 0;; ++batch)
{
// Run mainloop until all state machines are idle (batch == 0) or deleted (batch == 1).
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,12 +207,17 @@ 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.
#ifdef SHOW_ASSERT
AIThreadID mContThread; //!< Thread that last called locked_cont().
bool mCalledThreadUnsafeIdle; //!< Set to true when idle() is called.
#endif
// Callback facilities.
// From within an other state machine:
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;
@@ -228,21 +233,30 @@ 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.
AIStateMachine(void) : mState(bs_initialize), mIdle(true), mAborted(true), mActive(as_idle), mSleep(0), mParent(NULL), mCallback(NULL) { updateSettings(); }
AIStateMachine(void) : mState(bs_initialize), mIdle(true), mAborted(true), mActive(as_idle), mSleep(0), mParent(NULL), mCallback(NULL)
#ifdef SHOW_ASSERT
, mContThread(AIThreadID::none), mCalledThreadUnsafeIdle(false)
#endif
{ updateSettings(); }
protected:
//! The user should call 'kill()', not delete a AIStateMachine (derived) directly.
virtual ~AIStateMachine() { llassert(mState == bs_killed && mActive == as_idle); }
virtual ~AIStateMachine() { llassert((mState == bs_killed && mActive == as_idle) || mState == bs_initialize); }
public:
//! Halt the state machine until cont() is called.
//! Halt the state machine until cont() is called (not thread-safe).
void idle(void);
//! Halt the state machine until cont() is called, provided it is still in 'cur_run_state'.
void idle(state_type current_run_state);
//! Temporarily halt the state machine.
void yield_frame(unsigned int frames) { mSleep = -(S64)frames; }
@@ -250,15 +264,25 @@ class AIStateMachine {
void yield_ms(unsigned int ms) { mSleep = get_clock_count() + calc_clock_frequency() * ms / 1000; }
//! Continue running after calling idle.
void cont(void);
void cont(void)
{
mSetStateLock.lock();
// Ignore calls to cont() if the statemachine isn't idle. See comments in set_state().
// Calling cont() twice or after calling set_state(), without first calling idle(), is an error.
if (mState != bs_run || !mIdle) { llassert(mState != bs_run || !mContThread.equals_current_thread()); mSetStateLock.unlock(); return; }
locked_cont();
}
private:
void locked_cont(void);
public:
//---------------------------------------
// Changing the state.
//! 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.
@@ -326,11 +350,14 @@ class AIStateMachine {
//! Return true if the derived class is running (also when we are idle).
bool running(void) const { return mState == bs_run; }
//! Return true if it's safe to call abort.
bool abortable(void) const { return mState == bs_run || mState == bs_initialize; }
//! Return true if the derived class is running but idle.
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; }
@@ -338,9 +365,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.

View File

@@ -70,7 +70,6 @@ class AITimer : public AIStateMachine {
* @brief Set the interval after which the timer should expire.
*
* @param interval Amount of time in seconds before the timer will expire.
* @param True if the timer should be deleted after it expires; false means it will keep firing at regular intervals.
*
* Call abort() at any time to stop the timer (and delete the AITimer object).
*/