diff options
| author | Rider Linden <rider@lindenlab.com> | 2015-09-01 16:13:52 -0700 | 
|---|---|---|
| committer | Rider Linden <rider@lindenlab.com> | 2015-09-01 16:13:52 -0700 | 
| commit | 96e343b49b0b5a0951ffab0beb2e1d09c37bbdc5 (patch) | |
| tree | 2a4c332f6c476dff900baed36af879700254fbe0 /indra/llmessage | |
| parent | 4b3269c94d8b68c977598d2444ae04f7e1f9062c (diff) | |
MAINT-5575: Convert the Experience cache into a coro based singleton.
--HG--
branch : MAINT-5575
Diffstat (limited to 'indra/llmessage')
| -rwxr-xr-x | indra/llmessage/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | indra/llmessage/llcoproceduremanager.cpp | 414 | ||||
| -rw-r--r-- | indra/llmessage/llcoproceduremanager.h | 98 | ||||
| -rw-r--r-- | indra/llmessage/llexperiencecache.cpp | 569 | ||||
| -rw-r--r-- | indra/llmessage/llexperiencecache.h | 99 | 
5 files changed, 849 insertions, 333 deletions
| diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt index 9739f7c607..12fc1bbcfc 100755 --- a/indra/llmessage/CMakeLists.txt +++ b/indra/llmessage/CMakeLists.txt @@ -40,6 +40,7 @@ set(llmessage_SOURCE_FILES      llchainio.cpp      llcircuit.cpp      llclassifiedflags.cpp +    llcoproceduremanager.cpp      llcorehttputil.cpp      llcurl.cpp      lldatapacker.cpp @@ -128,6 +129,7 @@ set(llmessage_HEADER_FILES      llcipher.h      llcircuit.h      llclassifiedflags.h +    llcoproceduremanager.h      llcorehttputil.h      llcurl.h      lldatapacker.h diff --git a/indra/llmessage/llcoproceduremanager.cpp b/indra/llmessage/llcoproceduremanager.cpp new file mode 100644 index 0000000000..062f2e6e42 --- /dev/null +++ b/indra/llmessage/llcoproceduremanager.cpp @@ -0,0 +1,414 @@ +/** +* @file LLCoprocedurePool.cpp +* @author Rider Linden +* @brief Singleton class for managing asset uploads to the sim. +* +* $LicenseInfo:firstyear=2015&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2015, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library 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 +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA +* $/LicenseInfo$ +*/ + +#include "linden_common.h"  +#include "llcoproceduremanager.h" + +//========================================================================= +// Map of pool sizes for known pools +static std::map<std::string, U32> DefaultPoolSizes; + +// *TODO$: When C++11 this can be initialized here as follows: +// = {{"AIS", 25}, {"Upload", 1}} + +#define DEFAULT_POOL_SIZE 5 + +//========================================================================= +class LLCoprocedurePool: private boost::noncopyable +{ +public: +    typedef LLCoprocedureManager::CoProcedure_t CoProcedure_t; + +    LLCoprocedurePool(const std::string &name, size_t size); +    virtual ~LLCoprocedurePool(); + +    /// Places the coprocedure on the queue for processing.  +    ///  +    /// @param name Is used for debugging and should identify this coroutine. +    /// @param proc Is a bound function to be executed  +    ///  +    /// @return This method returns a UUID that can be used later to cancel execution. +    LLUUID enqueueCoprocedure(const std::string &name, CoProcedure_t proc); + +    /// Cancel a coprocedure. If the coprocedure is already being actively executed  +    /// this method calls cancelYieldingOperation() on the associated HttpAdapter +    /// If it has not yet been dequeued it is simply removed from the queue. +    bool cancelCoprocedure(const LLUUID &id); + +    /// Requests a shutdown of the upload manager. Passing 'true' will perform  +    /// an immediate kill on the upload coroutine. +    void shutdown(bool hardShutdown = false); + +    /// Returns the number of coprocedures in the queue awaiting processing. +    /// +    inline size_t countPending() const +    { +        return mPendingCoprocs.size(); +    } + +    /// Returns the number of coprocedures actively being processed. +    /// +    inline size_t countActive() const +    { +        return mActiveCoprocs.size(); +    } + +    /// Returns the total number of coprocedures either queued or in active processing. +    /// +    inline size_t count() const +    { +        return countPending() + countActive(); +    } + +private: +    struct QueuedCoproc +    { +        typedef boost::shared_ptr<QueuedCoproc> ptr_t; + +        QueuedCoproc(const std::string &name, const LLUUID &id, CoProcedure_t proc) : +            mName(name), +            mId(id), +            mProc(proc) +        {} + +        std::string mName; +        LLUUID mId; +        CoProcedure_t mProc; +    }; + +    // we use a deque here rather than std::queue since we want to be able to  +    // iterate through the queue and potentially erase an entry from the middle. +    typedef std::deque<QueuedCoproc::ptr_t>  CoprocQueue_t; +    typedef std::map<LLUUID, LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t> ActiveCoproc_t; + +    std::string     mPoolName; +    size_t          mPoolSize; +    CoprocQueue_t   mPendingCoprocs; +    ActiveCoproc_t  mActiveCoprocs; +    bool            mShutdown; +    LLEventStream   mWakeupTrigger; + +    typedef std::map<std::string, LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t> CoroAdapterMap_t; +    LLCore::HttpRequest::policy_t mHTTPPolicy; + +    CoroAdapterMap_t mCoroMapping; + +    void coprocedureInvokerCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter); + +}; + +//========================================================================= +LLCoprocedureManager::LLCoprocedureManager() +{ +    DefaultPoolSizes.insert(std::map<std::string, U32>::value_type("Upload", 1)); +    DefaultPoolSizes.insert(std::map<std::string, U32>::value_type("AIS", 25)); +} + +LLCoprocedureManager::~LLCoprocedureManager() +{ + +} + +LLCoprocedureManager::poolPtr_t LLCoprocedureManager::initializePool(const std::string &poolName) +{ +    // Attempt to look up a pool size in the configuration.  If found use that +    std::string keyName = "PoolSize" + poolName; +    int size = 0; + +    if (mPropertyQueryFn && !mPropertyQueryFn.empty()) +    { +        size = mPropertyQueryFn(keyName); +    } + +    if (size == 0) +    {   // if not found grab the know default... if there is no known  +        // default use a reasonable number like 5. +        std::map<std::string, U32>::iterator it = DefaultPoolSizes.find(poolName); +        if (it == DefaultPoolSizes.end()) +            size = DEFAULT_POOL_SIZE; +        else +            size = (*it).second; + +        if (mPropertyDefineFn && !mPropertyDefineFn.empty()) +            mPropertyDefineFn(keyName, size, "Coroutine Pool size for " + poolName); +        LL_WARNS() << "LLCoprocedureManager: No setting for \"" << keyName << "\" setting pool size to default of " << size << LL_ENDL; +    } + +    poolPtr_t pool = poolPtr_t(new LLCoprocedurePool(poolName, size)); +    mPoolMap.insert(poolMap_t::value_type(poolName, pool)); + +    return pool; +} + +//------------------------------------------------------------------------- +LLUUID LLCoprocedureManager::enqueueCoprocedure(const std::string &pool, const std::string &name, CoProcedure_t proc) +{ +    // Attempt to find the pool and enqueue the procedure.  If the pool does  +    // not exist, create it. +    poolPtr_t targetPool; +    poolMap_t::iterator it = mPoolMap.find(pool); + +    if (it == mPoolMap.end()) +    { +        targetPool = initializePool(pool); +    } +    else +    { +        targetPool = (*it).second; +    } + +    if (!targetPool) +    { +        LL_WARNS() << "LLCoprocedureManager unable to create coprocedure pool named \"" << pool << "\"" << LL_ENDL; +        return LLUUID::null; +    } + +    return targetPool->enqueueCoprocedure(name, proc); +} + +void LLCoprocedureManager::cancelCoprocedure(const LLUUID &id) +{ +    for (poolMap_t::const_iterator it = mPoolMap.begin(); it != mPoolMap.end(); ++it) +    { +        if ((*it).second->cancelCoprocedure(id)) +            return; +    } +    LL_INFOS() << "Coprocedure not found." << LL_ENDL; +} + +void LLCoprocedureManager::shutdown(bool hardShutdown) +{ +    for (poolMap_t::const_iterator it = mPoolMap.begin(); it != mPoolMap.end(); ++it) +    { +        (*it).second->shutdown(hardShutdown); +    } +    mPoolMap.clear(); +} + +void LLCoprocedureManager::setPropertyMethods(SettingQuery_t queryfn, SettingUpdate_t updatefn) +{ +    mPropertyQueryFn = queryfn; +    mPropertyDefineFn = updatefn; +} + +//------------------------------------------------------------------------- +size_t LLCoprocedureManager::countPending() const +{ +    size_t count = 0; +    for (poolMap_t::const_iterator it = mPoolMap.begin(); it != mPoolMap.end(); ++it) +    { +        count += (*it).second->countPending(); +    } +    return count; +} + +size_t LLCoprocedureManager::countPending(const std::string &pool) const +{ +    poolMap_t::const_iterator it = mPoolMap.find(pool); + +    if (it == mPoolMap.end()) +        return 0; +    return (*it).second->countPending(); +} + +size_t LLCoprocedureManager::countActive() const +{ +    size_t count = 0; +    for (poolMap_t::const_iterator it = mPoolMap.begin(); it != mPoolMap.end(); ++it) +    { +        count += (*it).second->countActive(); +    } +    return count; +} + +size_t LLCoprocedureManager::countActive(const std::string &pool) const +{ +    poolMap_t::const_iterator it = mPoolMap.find(pool); + +    if (it == mPoolMap.end()) +        return 0; +    return (*it).second->countActive(); +} + +size_t LLCoprocedureManager::count() const +{ +    size_t count = 0; +    for (poolMap_t::const_iterator it = mPoolMap.begin(); it != mPoolMap.end(); ++it) +    { +        count += (*it).second->count(); +    } +    return count; +} + +size_t LLCoprocedureManager::count(const std::string &pool) const +{ +    poolMap_t::const_iterator it = mPoolMap.find(pool); + +    if (it == mPoolMap.end()) +        return 0; +    return (*it).second->count(); +} + +//========================================================================= +LLCoprocedurePool::LLCoprocedurePool(const std::string &poolName, size_t size): +    mPoolName(poolName), +    mPoolSize(size), +    mPendingCoprocs(), +    mShutdown(false), +    mWakeupTrigger("CoprocedurePool" + poolName, true), +    mCoroMapping(), +    mHTTPPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID) +{ +    for (size_t count = 0; count < mPoolSize; ++count) +    { +        LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter = +            LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t( +            new LLCoreHttpUtil::HttpCoroutineAdapter( mPoolName + "Adapter", mHTTPPolicy)); + +        std::string uploadCoro = LLCoros::instance().launch("LLCoprocedurePool("+mPoolName+")::coprocedureInvokerCoro", +            boost::bind(&LLCoprocedurePool::coprocedureInvokerCoro, this, httpAdapter)); + +        mCoroMapping.insert(CoroAdapterMap_t::value_type(uploadCoro, httpAdapter)); +    } + +    LL_INFOS() << "Created coprocedure pool named \"" << mPoolName << "\" with " << size << " items." << LL_ENDL; + +    mWakeupTrigger.post(LLSD()); +} + +LLCoprocedurePool::~LLCoprocedurePool()  +{ +    shutdown(); +} + +//------------------------------------------------------------------------- +void LLCoprocedurePool::shutdown(bool hardShutdown) +{ +    CoroAdapterMap_t::iterator it; + +    for (it = mCoroMapping.begin(); it != mCoroMapping.end(); ++it) +    { +        if (!(*it).first.empty()) +        { +            if (hardShutdown) +            { +                LLCoros::instance().kill((*it).first); +            } +        } +        if ((*it).second) +        { +            (*it).second->cancelYieldingOperation(); +        } +    } + +    mShutdown = true; +    mCoroMapping.clear(); +    mPendingCoprocs.clear(); +} + +//------------------------------------------------------------------------- +LLUUID LLCoprocedurePool::enqueueCoprocedure(const std::string &name, LLCoprocedurePool::CoProcedure_t proc) +{ +    LLUUID id(LLUUID::generateNewID()); + +    mPendingCoprocs.push_back(QueuedCoproc::ptr_t(new QueuedCoproc(name, id, proc))); +    LL_INFOS() << "Coprocedure(" << name << ") enqueued with id=" << id.asString() << " in pool \"" << mPoolName << "\"" << LL_ENDL; + +    mWakeupTrigger.post(LLSD()); + +    return id; +} + +bool LLCoprocedurePool::cancelCoprocedure(const LLUUID &id) +{ +    // first check the active coroutines.  If there, remove it and return. +    ActiveCoproc_t::iterator itActive = mActiveCoprocs.find(id); +    if (itActive != mActiveCoprocs.end()) +    { +        LL_INFOS() << "Found and canceling active coprocedure with id=" << id.asString() << " in pool \"" << mPoolName << "\"" << LL_ENDL; +        (*itActive).second->cancelYieldingOperation(); +        mActiveCoprocs.erase(itActive); +        return true; +    } + +    for (CoprocQueue_t::iterator it = mPendingCoprocs.begin(); it != mPendingCoprocs.end(); ++it) +    { +        if ((*it)->mId == id) +        { +            LL_INFOS() << "Found and removing queued coroutine(" << (*it)->mName << ") with Id=" << id.asString() << " in pool \"" << mPoolName << "\"" << LL_ENDL; +            mPendingCoprocs.erase(it); +            return true; +        } +    } + +    LL_INFOS() << "Coprocedure with Id=" << id.asString() << " was not found." << " in pool \"" << mPoolName << "\"" << LL_ENDL; +    return false; +} + +//------------------------------------------------------------------------- +void LLCoprocedurePool::coprocedureInvokerCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter) +{ +    LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + +    while (!mShutdown) +    { +        llcoro::waitForEventOn(mWakeupTrigger); +        if (mShutdown) +            break; +         +        while (!mPendingCoprocs.empty()) +        { +            QueuedCoproc::ptr_t coproc = mPendingCoprocs.front(); +            mPendingCoprocs.pop_front(); +            mActiveCoprocs.insert(ActiveCoproc_t::value_type(coproc->mId, httpAdapter)); + +            LL_INFOS() << "Dequeued and invoking coprocedure(" << coproc->mName << ") with id=" << coproc->mId.asString() << " in pool \"" << mPoolName << "\"" << LL_ENDL; + +            try +            { +                coproc->mProc(httpAdapter, coproc->mId); +            } +            catch (std::exception &e) +            { +                LL_WARNS() << "Coprocedure(" << coproc->mName << ") id=" << coproc->mId.asString() << +                    " threw an exception! Message=\"" << e.what() << "\"" << LL_ENDL; +            } +            catch (...) +            { +                LL_WARNS() << "A non std::exception was thrown from " << coproc->mName << " with id=" << coproc->mId << "." << " in pool \"" << mPoolName << "\"" << LL_ENDL; +            } + +            LL_INFOS() << "Finished coprocedure(" << coproc->mName << ")" << " in pool \"" << mPoolName << "\"" << LL_ENDL; + +            ActiveCoproc_t::iterator itActive = mActiveCoprocs.find(coproc->mId); +            if (itActive != mActiveCoprocs.end()) +            { +                mActiveCoprocs.erase(itActive); +            } +        } +    } +} diff --git a/indra/llmessage/llcoproceduremanager.h b/indra/llmessage/llcoproceduremanager.h new file mode 100644 index 0000000000..497367b80c --- /dev/null +++ b/indra/llmessage/llcoproceduremanager.h @@ -0,0 +1,98 @@ +/** +* @file llcoproceduremanager.h +* @author Rider Linden +* @brief Singleton class for managing asset uploads to the sim. +* +* $LicenseInfo:firstyear=2015&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2015, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library 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 +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA +* $/LicenseInfo$ +*/ + +#ifndef LL_COPROCEDURE_MANAGER_H +#define LL_COPROCEDURE_MANAGER_H + +#include "lleventcoro.h" +#include "llcoros.h" +#include "llcorehttputil.h" +#include "lluuid.h" + +class LLCoprocedurePool; + +class LLCoprocedureManager : public LLSingleton < LLCoprocedureManager > +{ +    friend class LLSingleton < LLCoprocedureManager > ; + +public: +    typedef boost::function<U32(const std::string &)> SettingQuery_t; +    typedef boost::function<void(const std::string &, U32, const std::string &)> SettingUpdate_t; + +    typedef boost::function<void(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t &, const LLUUID &id)> CoProcedure_t; + +    LLCoprocedureManager(); +    virtual ~LLCoprocedureManager(); + +    /// Places the coprocedure on the queue for processing.  +    ///  +    /// @param name Is used for debugging and should identify this coroutine. +    /// @param proc Is a bound function to be executed  +    ///  +    /// @return This method returns a UUID that can be used later to cancel execution. +    LLUUID enqueueCoprocedure(const std::string &pool, const std::string &name, CoProcedure_t proc); + +    /// Cancel a coprocedure. If the coprocedure is already being actively executed  +    /// this method calls cancelYieldingOperation() on the associated HttpAdapter +    /// If it has not yet been dequeued it is simply removed from the queue. +    void cancelCoprocedure(const LLUUID &id); + +    /// Requests a shutdown of the upload manager. Passing 'true' will perform  +    /// an immediate kill on the upload coroutine. +    void shutdown(bool hardShutdown = false); + +    void setPropertyMethods(SettingQuery_t queryfn, SettingUpdate_t updatefn); + +    /// Returns the number of coprocedures in the queue awaiting processing. +    /// +    size_t countPending() const; +    size_t countPending(const std::string &pool) const; + +    /// Returns the number of coprocedures actively being processed. +    /// +    size_t countActive() const; +    size_t countActive(const std::string &pool) const; + +    /// Returns the total number of coprocedures either queued or in active processing. +    /// +    size_t count() const; +    size_t count(const std::string &pool) const; + +private: + +    typedef boost::shared_ptr<LLCoprocedurePool> poolPtr_t; +    typedef std::map<std::string, poolPtr_t> poolMap_t; + +    poolMap_t mPoolMap; + +    poolPtr_t initializePool(const std::string &poolName); + +    SettingQuery_t mPropertyQueryFn; +    SettingUpdate_t mPropertyDefineFn; +}; + +#endif diff --git a/indra/llmessage/llexperiencecache.cpp b/indra/llmessage/llexperiencecache.cpp index 34c4210359..36a4fc8823 100644 --- a/indra/llmessage/llexperiencecache.cpp +++ b/indra/llmessage/llexperiencecache.cpp @@ -26,53 +26,51 @@  #include "llexperiencecache.h"  #include "llavatarname.h" -#include "llframetimer.h"  #include "llhttpclient.h"  #include "llsdserialize.h" +#include "llcoros.h" +#include "lleventcoro.h" +#include "lleventfilter.h" +#include "llcoproceduremanager.h" +#include "lldir.h"  #include <set>  #include <map> -#include "boost/tokenizer.hpp" +#include <boost/tokenizer.hpp>  #include <boost/concept_check.hpp> - -typedef std::map<LLUUID, LLUUID> KeyMap; -KeyMap privateToPublicKeyMap; - - -std::string sLookupURL; - -typedef std::map<LLUUID, F64> pending_queue_t; -pending_queue_t sPendingQueue; - -int sMaximumLookups = 10; - -LLFrameTimer sRequestTimer; - -// Periodically clean out expired entries from the cache -LLFrameTimer sEraseExpiredTimer; - -  //=========================================================================  namespace LLExperienceCacheImpl  { -	bool max_age_from_cache_control(const std::string& cache_control, S32 *max_age);  	void mapKeys(const LLSD& legacyKeys); +    F64 getErrorRetryDeltaTime(S32 status, LLSD headers); +    bool maxAgeFromCacheControl(const std::string& cache_control, S32 *max_age); + +    static const std::string PRIVATE_KEY    = "private_id"; +    static const std::string EXPERIENCE_ID  = "public_id"; + +    static const std::string MAX_AGE("max-age"); +    static const boost::char_separator<char> EQUALS_SEPARATOR("="); +    static const boost::char_separator<char> COMMA_SEPARATOR(","); + +    // *TODO$: this seems to be tied to mapKeys which is used by bootstrap.... but I don't think that bootstrap is used. +    typedef std::map<LLUUID, LLUUID> KeyMap; +    KeyMap privateToPublicKeyMap;  }  //=========================================================================  const std::string LLExperienceCache::PRIVATE_KEY	= "private_id";  const std::string LLExperienceCache::MISSING       	= "DoesNotExist"; -const std::string LLExperienceCache::AGENT_ID      	= "agent_id"; -const std::string LLExperienceCache::GROUP_ID      	= "group_id"; +const std::string LLExperienceCache::AGENT_ID      = "agent_id"; +const std::string LLExperienceCache::GROUP_ID      = "group_id";  const std::string LLExperienceCache::EXPERIENCE_ID	= "public_id";  const std::string LLExperienceCache::NAME			= "name";  const std::string LLExperienceCache::PROPERTIES		= "properties";  const std::string LLExperienceCache::EXPIRES		= "expiration";    const std::string LLExperienceCache::DESCRIPTION	= "description";  const std::string LLExperienceCache::QUOTA         	= "quota"; -const std::string LLExperienceCache::MATURITY      	= "maturity"; -const std::string LLExperienceCache::METADATA      	= "extended_metadata"; +const std::string LLExperienceCache::MATURITY      = "maturity"; +const std::string LLExperienceCache::METADATA      = "extended_metadata";  const std::string LLExperienceCache::SLURL         	= "slurl";  // should be in sync with experience-api/experiences/models.py @@ -80,20 +78,51 @@ const int LLExperienceCache::PROPERTY_INVALID		= 1 << 0;  const int LLExperienceCache::PROPERTY_PRIVILEGED	= 1 << 3;  const int LLExperienceCache::PROPERTY_GRID			= 1 << 4;  const int LLExperienceCache::PROPERTY_PRIVATE		= 1 << 5; -const int LLExperienceCache::PROPERTY_DISABLED		= 1 << 6;   -const int LLExperienceCache::PROPERTY_SUSPENDED		= 1 << 7; +const int LLExperienceCache::PROPERTY_DISABLED	= 1 << 6;   +const int LLExperienceCache::PROPERTY_SUSPENDED	= 1 << 7;  // default values -const F64 LLExperienceCache::DEFAULT_EXPIRATION		= 600.0; +const F64 LLExperienceCache::DEFAULT_EXPIRATION	= 600.0;  const S32 LLExperienceCache::DEFAULT_QUOTA			= 128; // this is megabytes  //========================================================================= -LLExperienceCache::LLExperienceCache() +LLExperienceCache::LLExperienceCache(): +    mShutdown(false)  {  }  LLExperienceCache::~LLExperienceCache()  { + +} + +void LLExperienceCache::initSingleton() +{ +    mCacheFileName = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "experience_cache.xml"); + +    LL_INFOS("ExperienceCache") << "Loading " << mCacheFileName << LL_ENDL; +    llifstream cache_stream(mCacheFileName.c_str()); + +    if (cache_stream.is_open()) +    { +        cache_stream >> (*this); +    } + +    LLCoros::instance().launch("LLExperienceCache::idleCoro", +        boost::bind(&LLExperienceCache::idleCoro, this)); + +} + +void LLExperienceCache::cleanup() +{ +    LL_INFOS("ExperienceCache") << "Saving " << mCacheFileName << LL_ENDL; + +    llofstream cache_stream(mCacheFileName.c_str()); +    if (cache_stream.is_open()) +    { +        cache_stream << (*this); +    } +    mShutdown = true;  }  //------------------------------------------------------------------------- @@ -110,18 +139,18 @@ void LLExperienceCache::importFile(std::istream& istr)      for (; it != experiences.endMap(); ++it)      {          public_key.set(it->first); -        sCache[public_key] = it->second; +        mCache[public_key] = it->second;      } -    LL_DEBUGS("ExperienceCache") << "importFile() loaded " << sCache.size() << LL_ENDL; +    LL_DEBUGS("ExperienceCache") << "importFile() loaded " << mCache.size() << LL_ENDL;  }  void LLExperienceCache::exportFile(std::ostream& ostr) const  {      LLSD experiences; -    cache_t::const_iterator it = sCache.begin(); -    for (; it != sCache.end(); ++it) +    cache_t::const_iterator it = mCache.begin(); +    for (; it != mCache.end(); ++it)      {          if (!it->second.has(EXPERIENCE_ID) || it->second[EXPERIENCE_ID].asUUID().isNull() ||              it->second.has("DoesNotExist") || (it->second.has(PROPERTIES) && it->second[PROPERTIES].asInteger() & PROPERTY_INVALID)) @@ -136,7 +165,7 @@ void LLExperienceCache::exportFile(std::ostream& ostr) const      LLSDSerialize::toPrettyXML(data, ostr);  } -// *TODO$: Rider: These three functions not seem to be used... it may be useful in testing. +// *TODO$: Rider: This method does not seem to be used... it may be useful in testing.  void LLExperienceCache::bootstrap(const LLSD& legacyKeys, int initialExpiration)  {  	LLExperienceCacheImpl::mapKeys(legacyKeys); @@ -166,8 +195,8 @@ LLUUID LLExperienceCache::getExperienceId(const LLUUID& private_key, bool null_i      if (private_key.isNull())          return LLUUID::null; -    KeyMap::const_iterator it = privateToPublicKeyMap.find(private_key); -    if (it == privateToPublicKeyMap.end()) +    LLExperienceCacheImpl::KeyMap::const_iterator it = LLExperienceCacheImpl::privateToPublicKeyMap.find(private_key); +    if (it == LLExperienceCacheImpl::privateToPublicKeyMap.end())      {          if (null_if_not_found)          { @@ -182,8 +211,10 @@ LLUUID LLExperienceCache::getExperienceId(const LLUUID& private_key, bool null_i  //=========================================================================  void LLExperienceCache::processExperience(const LLUUID& public_key, const LLSD& experience)  { -	sCache[public_key]=experience; -	LLSD & row = sCache[public_key]; +    LL_INFOS("ExperienceCache") << "Processing experience \"" << experience[NAME] << "\" with key " << public_key.asString() << LL_ENDL; + +	mCache[public_key]=experience; +	LLSD & row = mCache[public_key];  	if(row.has(EXPIRES))  	{ @@ -192,233 +223,148 @@ void LLExperienceCache::processExperience(const LLUUID& public_key, const LLSD&  	if(row.has(EXPERIENCE_ID))  	{ -		sPendingQueue.erase(row[EXPERIENCE_ID].asUUID()); +		mPendingQueue.erase(row[EXPERIENCE_ID].asUUID());  	}  	//signal -	signal_map_t::iterator sig_it =	sSignalMap.find(public_key); -	if (sig_it != sSignalMap.end()) +	signal_map_t::iterator sig_it =	mSignalMap.find(public_key); +	if (sig_it != mSignalMap.end())  	{  		signal_ptr signal = sig_it->second;  		(*signal)(experience); -		sSignalMap.erase(public_key); +		mSignalMap.erase(public_key);  	}  }  const LLExperienceCache::cache_t& LLExperienceCache::getCached()  { -	return sCache; -} - -void LLExperienceCache::setMaximumLookups(int maximumLookups) -{ -	sMaximumLookups = maximumLookups; +	return mCache;  } - -bool LLExperienceCache::expirationFromCacheControl(LLSD headers, F64 *expires) +void LLExperienceCache::requestExperiencesCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t &httpAdapter, std::string url, RequestQueue_t requests)  { -	// Allow the header to override the default -	LLSD cache_control_header = headers["cache-control"]; -	if (cache_control_header.isDefined()) -	{ -		S32 max_age = 0; -		std::string cache_control = cache_control_header.asString(); -		if (max_age_from_cache_control(cache_control, &max_age)) -		{ -			LL_WARNS("ExperienceCache")  -				<< "got EXPIRES from headers, max_age " << max_age  -				<< LL_ENDL; -			F64 now = LLFrameTimer::getTotalSeconds(); -			*expires = now + (F64)max_age; -			return true; -		} -	} -	return false; -} +    LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest()); +    //LL_INFOS("requestExperiencesCoro") << "url: " << url << LL_ENDL; -static const std::string MAX_AGE("max-age"); -static const boost::char_separator<char> EQUALS_SEPARATOR("="); -static const boost::char_separator<char> COMMA_SEPARATOR(","); +    LLSD result = httpAdapter->getAndYield(httpRequest, url); +         +    LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; +    LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); +    if (!status) +    { +        F64 now = LLFrameTimer::getTotalSeconds(); -class LLExperienceResponder : public LLHTTPClient::Responder -{ -public: -	LLExperienceResponder(const ask_queue_t& keys) -		:mKeys(keys) -	{ - -	} - -	/*virtual*/ void httpCompleted() -	{ -		LLSD experiences = getContent()["experience_keys"]; -		LLSD::array_const_iterator it = experiences.beginArray(); -		for( /**/ ; it != experiences.endArray(); ++it) -		{ -			const LLSD& row = *it; -			LLUUID public_key = row[EXPERIENCE_ID].asUUID(); - - -			LL_DEBUGS("ExperienceCache") << "Received result for " << public_key  -				<< " display '" << row[LLExperienceCache::NAME].asString() << "'" << LL_ENDL ; - -			processExperience(public_key, row); -		} - -		LLSD error_ids = getContent()["error_ids"]; -		LLSD::array_const_iterator errIt = error_ids.beginArray(); -		for( /**/ ; errIt != error_ids.endArray() ; ++errIt ) -		{ -			LLUUID id = errIt->asUUID();		 -			LLSD exp; -			exp[EXPIRES]=DEFAULT_EXPIRATION; -			exp[EXPERIENCE_ID] = id; -			exp[PROPERTIES]=PROPERTY_INVALID; -			exp[MISSING]=true; -            exp[QUOTA] = DEFAULT_QUOTA; - -			processExperience(id, exp); -			LL_WARNS("ExperienceCache") << "LLExperienceResponder::result() error result for " << id << LL_ENDL ; -		} - -		LL_DEBUGS("ExperienceCache") << sCache.size() << " cached experiences" << LL_ENDL; -	} - -	/*virtual*/ void httpFailure() -	{ - 		LL_WARNS("ExperienceCache") << "Request failed "<<getStatus()<<" "<<getReason()<< LL_ENDL; - 		// We're going to construct a dummy record and cache it for a while, - 		// either briefly for a 503 Service Unavailable, or longer for other - 		// errors. - 		F64 retry_timestamp = errorRetryTimestamp(getStatus()); -  -  - 		// Add dummy records for all agent IDs in this request - 		ask_queue_t::const_iterator it = mKeys.begin(); - 		for ( ; it != mKeys.end(); ++it) -		{ - -			LLSD exp = get(it->first); +        LLSD headers = httpResults[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_HEADERS]; +        // build dummy entries for the failed requests +        for (RequestQueue_t::const_iterator it = requests.begin(); it != requests.end(); ++it) +        { +            LLSD exp = get(*it);              //leave the properties alone if we already have a cache entry for this xp -            if(exp.isUndefined()) +            if (exp.isUndefined())              { -                exp[PROPERTIES]=PROPERTY_INVALID; +                exp[PROPERTIES] = PROPERTY_INVALID;              } -			exp[EXPIRES]=retry_timestamp; -			exp[EXPERIENCE_ID] = it->first; -			exp["key_type"] = it->second; -			exp["uuid"] = it->first; -			exp["error"] = (LLSD::Integer)getStatus(); +            exp[EXPIRES] = now + LLExperienceCacheImpl::getErrorRetryDeltaTime(status, headers); +            exp[EXPERIENCE_ID] = *it; +            exp["key_type"] = EXPERIENCE_ID; +            exp["uuid"] = *it; +            exp["error"] = (LLSD::Integer)status.getType();              exp[QUOTA] = DEFAULT_QUOTA; - 			LLExperienceCache::processExperience(it->first, exp); - 		} - -	} - -	// Return time to retry a request that generated an error, based on -	// error type and headers.  Return value is seconds-since-epoch. -	F64 errorRetryTimestamp(S32 status) -	{ +            processExperience(*it, exp); +        } +        return; +    } -		// Retry-After takes priority -		LLSD retry_after = getResponseHeaders()["retry-after"]; -		if (retry_after.isDefined()) -		{ -			// We only support the delta-seconds type -			S32 delta_seconds = retry_after.asInteger(); -			if (delta_seconds > 0) -			{ -				// ...valid delta-seconds -				return F64(delta_seconds); -			} -		} +    LLSD experiences = result["experience_keys"]; +     +    for (LLSD::array_const_iterator it = experiences.beginArray();  +        it != experiences.endArray(); ++it) +    { +        const LLSD& row = *it; +        LLUUID public_key = row[EXPERIENCE_ID].asUUID(); -		// If no Retry-After, look for Cache-Control max-age -		F64 expires = 0.0; -		if (LLExperienceCache::expirationFromCacheControl(getResponseHeaders(), &expires)) -		{ -			return expires; -		} +        LL_DEBUGS("ExperienceCache") << "Received result for " << public_key +            << " display '" << row[LLExperienceCache::NAME].asString() << "'" << LL_ENDL; -		// No information in header, make a guess -		if (status == 503) -		{ -			// ...service unavailable, retry soon -			const F64 SERVICE_UNAVAILABLE_DELAY = 600.0; // 10 min -			return SERVICE_UNAVAILABLE_DELAY; -		} -		else if (status == 499) -		{ -			// ...we were probably too busy, retry quickly -			const F64 BUSY_DELAY = 10.0; // 10 seconds -			return BUSY_DELAY; +        processExperience(public_key, row); +    } -		} -		else -		{ -			// ...other unexpected error -			const F64 DEFAULT_DELAY = 3600.0; // 1 hour -			return DEFAULT_DELAY; -		} -	} +    LLSD error_ids = result["error_ids"]; +     +    for (LLSD::array_const_iterator errIt = error_ids.beginArray();  +        errIt != error_ids.endArray(); ++errIt) +    { +        LLUUID id = errIt->asUUID(); +        LLSD exp; +        exp[EXPIRES] = DEFAULT_EXPIRATION; +        exp[EXPERIENCE_ID] = id; +        exp[PROPERTIES] = PROPERTY_INVALID; +        exp[MISSING] = true; +        exp[QUOTA] = DEFAULT_QUOTA; + +        processExperience(id, exp); +        LL_WARNS("ExperienceCache") << "LLExperienceResponder::result() error result for " << id << LL_ENDL; +    } -private: -	ask_queue_t mKeys; -}; +}  void LLExperienceCache::requestExperiences()  { -	if(sAskQueue.empty() || sLookupURL.empty()) -		return; - -	F64 now = LLFrameTimer::getTotalSeconds(); +    if (mCapability.empty()) +    { +        LL_WARNS("ExperienceCache") << "Capability query method not set." << LL_ENDL; +        return; +    } -	const U32 EXP_URL_SEND_THRESHOLD = 3000; -	const U32 PAGE_SIZE = EXP_URL_SEND_THRESHOLD/UUID_STR_LENGTH; +    std::string urlBase = mCapability("GetExperienceInfo"); +    if (urlBase.empty()) +    { +        LL_WARNS("ExperienceCache") << "No Experience capability." << LL_ENDL; +        return; +    } -	std::ostringstream ostr; +    if (*urlBase.rbegin() != '/') +    { +        urlBase += "/"; +    } +    urlBase += "id/"; -	ask_queue_t keys; -	ostr << sLookupURL << "?page_size=" << PAGE_SIZE; +	F64 now = LLFrameTimer::getTotalSeconds(); -	int request_count = 0; -	while(!sAskQueue.empty() && request_count < sMaximumLookups) -	{ -		ask_queue_t::iterator it = sAskQueue.begin(); -		const LLUUID& key = it->first; -		const std::string& key_type = it->second; +    const U32 EXP_URL_SEND_THRESHOLD = 3000; +    const U32 PAGE_SIZE = EXP_URL_SEND_THRESHOLD / UUID_STR_LENGTH; -		ostr << '&' << key_type << '=' << key.asString() ; -		 -		keys[key]=key_type; -		request_count++; +    std::ostringstream ostr; +    ostr << urlBase << "?page_size=" << PAGE_SIZE; +    RequestQueue_t  requests; -		sPendingQueue[key] = now; -			 -		if(ostr.tellp() > EXP_URL_SEND_THRESHOLD) -		{ -			LL_DEBUGS("ExperienceCache") <<  "requestExperiences() query: " << ostr.str() << LL_ENDL; -			LLHTTPClient::get(ostr.str(), new LLExperienceResponder(keys)); -			ostr.clear(); -			ostr.str(sLookupURL); -			ostr << "?page_size=" << PAGE_SIZE; -			keys.clear(); -		} -		sAskQueue.erase(it); -	} +    while (!mRequestQueue.empty()) +    { +        RequestQueue_t::iterator it = mRequestQueue.begin(); +        LLUUID key = (*it); +        mRequestQueue.erase(it); +        requests.insert(key); + +        ostr << "&" << EXPERIENCE_ID << "=" << key.asString(); +        mPendingQueue[key] = now; +         +        if (mRequestQueue.empty() || (ostr.tellp() > EXP_URL_SEND_THRESHOLD)) +        {   // request is placed in the coprocedure pool for the ExpCache cache.  Throttling is done by the pool itself. +            LLCoprocedureManager::getInstance()->enqueueCoprocedure("ExpCache", "Request", +                boost::bind(&LLExperienceCache::requestExperiencesCoro, this, _1, ostr.str(), requests) ); + +            ostr.str(std::string()); +            ostr << urlBase << "?page_size=" << PAGE_SIZE; +            requests.clear(); +        } +    } -	if(ostr.tellp() > sLookupURL.size()) -	{ -		LL_DEBUGS("ExperienceCache") <<  "requestExperiences() query 2: " << ostr.str() << LL_ENDL; -		LLHTTPClient::get(ostr.str(), new LLExperienceResponder(keys)); -	}  } @@ -427,9 +373,9 @@ bool LLExperienceCache::isRequestPending(const LLUUID& public_key)  	bool isPending = false;  	const F64 PENDING_TIMEOUT_SECS = 5.0 * 60.0; -	pending_queue_t::const_iterator it = sPendingQueue.find(public_key); +    PendingQueue_t::const_iterator it = mPendingQueue.find(public_key); -	if(it != sPendingQueue.end()) +	if(it != mPendingQueue.end())  	{  		F64 expire_time = LLFrameTimer::getTotalSeconds() - PENDING_TIMEOUT_SECS;  		isPending = (it->second > expire_time); @@ -438,69 +384,70 @@ bool LLExperienceCache::isRequestPending(const LLUUID& public_key)  	return isPending;  } - -void LLExperienceCache::setLookupURL(const std::string& lookup_url) +void LLExperienceCache::setCapabilityQuery(LLExperienceCache::CapabilityQuery_t queryfn)  { -	sLookupURL = lookup_url; -	if(!sLookupURL.empty()) -	{ -		sLookupURL += "id/"; -	} +    mCapability = queryfn;  } -bool LLExperienceCache::hasLookupURL() -{ -	return !sLookupURL.empty(); -} -void LLExperienceCache::idle() +void LLExperienceCache::idleCoro()  { -	const F32 SECS_BETWEEN_REQUESTS = 0.1f; -	if (!sRequestTimer.checkExpirationAndReset(SECS_BETWEEN_REQUESTS)) -	{ -		return; -	} +    const F32 SECS_BETWEEN_REQUESTS = 0.5f; +    const F32 ERASE_EXPIRED_TIMEOUT = 60.f; // seconds -	// Must be large relative to above -	const F32 ERASE_EXPIRED_TIMEOUT = 60.f; // seconds -	if (sEraseExpiredTimer.checkExpirationAndReset(ERASE_EXPIRED_TIMEOUT)) -	{ -		eraseExpired(); -	} +    LL_INFOS("ExperienceCache") << "Launching Experience cache idle coro." << LL_ENDL; +    LLEventTimeout timeout; -	if(!sAskQueue.empty()) -	{ -		requestExperiences(); -	} +    do  +    { +        timeout.eventAfter(SECS_BETWEEN_REQUESTS, LLSD()); +        llcoro::waitForEventOn(timeout); + +        if (mEraseExpiredTimer.checkExpirationAndReset(ERASE_EXPIRED_TIMEOUT)) +        { +            eraseExpired(); +        } + +        if (!mRequestQueue.empty()) +        { +            requestExperiences(); +        } + +    } while (!mShutdown); + +    // The coroutine system will likely be shut down by the time we get to this point +    // (or at least no further cycling will occur on it since the user has decided to quit.)  }  void LLExperienceCache::erase(const LLUUID& key)  { -	cache_t::iterator it = sCache.find(key); +	cache_t::iterator it = mCache.find(key); -	if(it != sCache.end()) +	if(it != mCache.end())  	{ -		sCache.erase(it); +		mCache.erase(it);  	}  }  void LLExperienceCache::eraseExpired()  {  	F64 now = LLFrameTimer::getTotalSeconds(); -	cache_t::iterator it = sCache.begin(); -	while (it != sCache.end()) +	cache_t::iterator it = mCache.begin(); +	while (it != mCache.end())  	{  		cache_t::iterator cur = it;  		LLSD& exp = cur->second;  		++it; +        //LL_INFOS("ExperienceCache") << "Testing experience \"" << exp[NAME] << "\" with exp time " << exp[EXPIRES].asReal() << "(now = " << now << ")" << LL_ENDL; +  		if(exp.has(EXPIRES) && exp[EXPIRES].asReal() < now)  		{              if(!exp.has(EXPERIENCE_ID))  			{                  LL_WARNS("ExperienceCache") << "Removing experience with no id " << LL_ENDL ; -                sCache.erase(cur); -				} +                mCache.erase(cur); +			}              else              {                  LLUUID id = exp[EXPERIENCE_ID].asUUID(); @@ -512,7 +459,7 @@ void LLExperienceCache::eraseExpired()  				else  				{                      LL_WARNS("ExperienceCache") << "Removing invalid experience " << id << LL_ENDL ; -					sCache.erase(cur); +					mCache.erase(cur);  				}  			}  		} @@ -521,11 +468,11 @@ void LLExperienceCache::eraseExpired()  bool LLExperienceCache::fetch(const LLUUID& key, bool refresh/* = true*/)  { -	if(!key.isNull() && !isRequestPending(key) && (refresh || sCache.find(key)==sCache.end())) +	if(!key.isNull() && !isRequestPending(key) && (refresh || mCache.find(key)==mCache.end()))  	{ -		LL_DEBUGS("ExperienceCache") << " queue request for " << EXPERIENCE_ID << " " << key << LL_ENDL ; -		sAskQueue[key]=EXPERIENCE_ID; +		LL_DEBUGS("ExperienceCache") << " queue request for " << EXPERIENCE_ID << " " << key << LL_ENDL; +        mRequestQueue.insert(key);  		return true;  	}  	return false; @@ -549,13 +496,12 @@ const LLSD& LLExperienceCache::get(const LLUUID& key)  	if(key.isNull())   		return empty; -	cache_t::const_iterator it = sCache.find(key); +	cache_t::const_iterator it = mCache.find(key); -	if (it != sCache.end()) +	if (it != mCache.end())  	{  		return it->second;  	} -  	fetch(key);  	return empty; @@ -565,8 +511,8 @@ void LLExperienceCache::get(const LLUUID& key, LLExperienceCache::Callback_t slo  	if(key.isNull())   		return; -	cache_t::const_iterator it = sCache.find(key); -	if (it != sCache.end()) +	cache_t::const_iterator it = mCache.find(key); +	if (it != mCache.end())  	{  		// ...name already exists in cache, fire callback now  		callback_signal_t signal; @@ -580,28 +526,10 @@ void LLExperienceCache::get(const LLUUID& key, LLExperienceCache::Callback_t slo  	signal_ptr signal = signal_ptr(new callback_signal_t()); -	std::pair<signal_map_t::iterator, bool> result = sSignalMap.insert(signal_map_t::value_type(key, signal)); +	std::pair<signal_map_t::iterator, bool> result = mSignalMap.insert(signal_map_t::value_type(key, signal));  	if (!result.second) -		signal = result.first.second; +		signal = (*result.first).second;  	signal->connect(slot); -	 -#if 0 -	// always store additional callback, even if request is pending -	signal_map_t::iterator sig_it = sSignalMap.find(key); -	if (sig_it == sSignalMap.end()) -	{ -		// ...new callback for this id -		signal_ptr signal = signal_ptr(new callback_signal_t()); -		signal->connect(slot); -		sSignalMap[key] = signal; -	} -	else -	{ -		// ...existing callback, bind additional slot -		callback_signal_t* signal = sig_it->second; -		signal->connect(slot); -	} -#endif  }  //========================================================================= @@ -610,14 +538,71 @@ void LLExperienceCacheImpl::mapKeys(const LLSD& legacyKeys)  	LLSD::array_const_iterator exp = legacyKeys.beginArray();  	for (/**/; exp != legacyKeys.endArray(); ++exp)  	{ -		if (exp->has(LLExperienceCache::EXPERIENCE_ID) && exp->has(LLExperienceCache::PRIVATE_KEY)) +        if (exp->has(LLExperienceCacheImpl::EXPERIENCE_ID) && exp->has(LLExperienceCacheImpl::PRIVATE_KEY))  		{ -			privateToPublicKeyMap[(*exp)[LLExperienceCache::PRIVATE_KEY].asUUID()] = (*exp)[LLExperienceCache::EXPERIENCE_ID].asUUID(); +            LLExperienceCacheImpl::privateToPublicKeyMap[(*exp)[LLExperienceCacheImpl::PRIVATE_KEY].asUUID()] =  +                (*exp)[LLExperienceCacheImpl::EXPERIENCE_ID].asUUID();  		}  	}  } -bool LLExperienceCacheImpl::max_age_from_cache_control(const std::string& cache_control, S32 *max_age) +// Return time to retry a request that generated an error, based on +// error type and headers.  Return value is seconds-since-epoch. +F64 LLExperienceCacheImpl::getErrorRetryDeltaTime(S32 status, LLSD headers) +{ + +    // Retry-After takes priority +    LLSD retry_after = headers["retry-after"]; +    if (retry_after.isDefined()) +    { +        // We only support the delta-seconds type +        S32 delta_seconds = retry_after.asInteger(); +        if (delta_seconds > 0) +        { +            // ...valid delta-seconds +            return F64(delta_seconds); +        } +    } + +    // If no Retry-After, look for Cache-Control max-age +    // Allow the header to override the default +    LLSD cache_control_header = headers["cache-control"]; +    if (cache_control_header.isDefined()) +    { +        S32 max_age = 0; +        std::string cache_control = cache_control_header.asString(); +        if (LLExperienceCacheImpl::maxAgeFromCacheControl(cache_control, &max_age)) +        { +            LL_WARNS("ExperienceCache") +                << "got EXPIRES from headers, max_age " << max_age +                << LL_ENDL; +            return (F64)max_age; +        } +    } + +    // No information in header, make a guess +    if (status == 503) +    { +        // ...service unavailable, retry soon +        const F64 SERVICE_UNAVAILABLE_DELAY = 600.0; // 10 min +        return SERVICE_UNAVAILABLE_DELAY; +    } +    else if (status == 499) +    { +        // ...we were probably too busy, retry quickly +        const F64 BUSY_DELAY = 10.0; // 10 seconds +        return BUSY_DELAY; + +    } +    else +    { +        // ...other unexpected error +        const F64 DEFAULT_DELAY = 3600.0; // 1 hour +        return DEFAULT_DELAY; +    } +} + +bool LLExperienceCacheImpl::maxAgeFromCacheControl(const std::string& cache_control, S32 *max_age)  {  	// Split the string on "," to get a list of directives  	typedef boost::tokenizer<boost::char_separator<char> > tokenizer; diff --git a/indra/llmessage/llexperiencecache.h b/indra/llmessage/llexperiencecache.h index 8da038a8c3..937225a80a 100644 --- a/indra/llmessage/llexperiencecache.h +++ b/indra/llmessage/llexperiencecache.h @@ -31,7 +31,9 @@  #include "linden_common.h"  #include "llsingleton.h" +#include "llframetimer.h"  #include "llsd.h" +#include "llcorehttputil.h"  #include <boost/signals2.hpp>  #include <boost/function.hpp> @@ -44,8 +46,11 @@ class LLExperienceCache: public LLSingleton < LLExperienceCache >      friend class LLSingleton < LLExperienceCache > ;  public: +    typedef boost::function<std::string(const std::string &)> CapabilityQuery_t;      typedef boost::function<void(const LLSD &)> Callback_t; +    void cleanup(); +      void erase(const LLUUID& key);      bool fetch(const LLUUID& key, bool refresh = false);      void insert(const LLSD& experience_data); @@ -54,7 +59,39 @@ public:      // If name information is in cache, callback will be called immediately.      void get(const LLUUID& key, Callback_t slot); +    bool isRequestPending(const LLUUID& public_key); + +    void setCapabilityQuery(CapabilityQuery_t queryfn); + +    static const std::string NAME;			// "name" +    static const std::string EXPERIENCE_ID;	// "public_id" +    static const std::string AGENT_ID;      // "agent_id" +    static const std::string GROUP_ID;      // "group_id" +    static const std::string PROPERTIES;	// "properties" +    static const std::string EXPIRES;		// "expiration"   +    static const std::string DESCRIPTION;	// "description" +    static const std::string QUOTA;         // "quota" +    static const std::string MATURITY;      // "maturity" +    static const std::string METADATA;      // "extended_metadata" +    static const std::string SLURL;         // "slurl" + +    static const std::string MISSING;       // "DoesNotExist" + +    // should be in sync with experience-api/experiences/models.py +    static const int PROPERTY_INVALID;		// 1 << 0 +    static const int PROPERTY_PRIVILEGED;	// 1 << 3 +    static const int PROPERTY_GRID;			// 1 << 4 +    static const int PROPERTY_PRIVATE;		// 1 << 5 +    static const int PROPERTY_DISABLED;		// 1 << 6   +    static const int PROPERTY_SUSPENDED;	// 1 << 7 +  private: +    LLExperienceCache(); +    virtual ~LLExperienceCache(); + +    virtual void initSingleton(); + +      // Callback types for get()       typedef boost::signals2::signal < void(const LLSD &) > callback_signal_t;  	typedef boost::shared_ptr<callback_signal_t> signal_ptr; @@ -64,63 +101,43 @@ private:  	typedef std::map<LLUUID, signal_ptr> signal_map_t;  	typedef std::map<LLUUID, LLSD> cache_t; -	typedef std::set<LLUUID> ask_queue_t; -	 -	 +	typedef std::set<LLUUID> RequestQueue_t; +    typedef std::map<LLUUID, F64> PendingQueue_t; +  	//--------------------------------------------  	static const std::string PRIVATE_KEY;	// "private_id" -	static const std::string MISSING;       // "DoesNotExist" -	 -	static const std::string AGENT_ID;      // "agent_id" -	static const std::string GROUP_ID;      // "group_id" -	static const std::string EXPERIENCE_ID;	// "public_id" -	static const std::string NAME;			// "name" -	static const std::string PROPERTIES;	// "properties" -	static const std::string EXPIRES;		// "expiration"   -	static const std::string DESCRIPTION;	// "description" -	static const std::string QUOTA;         // "quota" -	static const std::string MATURITY;      // "maturity" -	static const std::string METADATA;      // "extended_metadata" -	static const std::string SLURL;         // "slurl" -	 -	// should be in sync with experience-api/experiences/models.py -	static const int PROPERTY_INVALID;		// 1 << 0 -	static const int PROPERTY_PRIVILEGED;	// 1 << 3 -	static const int PROPERTY_GRID;			// 1 << 4 -	static const int PROPERTY_PRIVATE;		// 1 << 5 -	static const int PROPERTY_DISABLED;		// 1 << 6   -	static const int PROPERTY_SUSPENDED;	// 1 << 7  	// default values  	static const F64 DEFAULT_EXPIRATION; 	// 600.0  	static const S32 DEFAULT_QUOTA; 		// 128 this is megabytes  //-------------------------------------------- -    LLExperienceCache(); -    virtual ~LLExperienceCache(); - -    void exportFile(std::ostream& ostr) const; -    void importFile(std::istream& istr); - -//--------------------------------------------      void processExperience(const LLUUID& public_key, const LLSD& experience);  //-------------------------------------------- -	cache_t			sCache; -	signal_map_t	sSignalMap;	 -	ask_queue_t		sAskQueue; -	 +	cache_t			mCache; +	signal_map_t	mSignalMap;	 +	RequestQueue_t	mRequestQueue; +    PendingQueue_t  mPendingQueue; + +    LLFrameTimer    mRequestTimer; +    LLFrameTimer    mEraseExpiredTimer;    // Periodically clean out expired entries from the cache +    CapabilityQuery_t mCapability; +    std::string     mCacheFileName; +    bool            mShutdown; + +    void idleCoro();  	void eraseExpired(); -	 -	void setLookupURL(const std::string& lookup_url); -	bool hasLookupURL(); +    void requestExperiencesCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t &, std::string, RequestQueue_t); +    void requestExperiences();  	void setMaximumLookups(int maximumLookups); -	void idle(); -	void bootstrap(const LLSD& legacyKeys, int initialExpiration); -	 +    void bootstrap(const LLSD& legacyKeys, int initialExpiration); +    void exportFile(std::ostream& ostr) const; +    void importFile(std::istream& istr); +    //   	const cache_t& getCached();  	// maps an experience private key to the experience id | 
