Added code for simulator integration.
Added signals for cache update callbacks. Added expiration timers for cached experiences.meow-7.2.2
parent
ad3f7ea13f
commit
2566e0d08f
|
|
@ -31,6 +31,7 @@
|
|||
#include "llsdserialize.h"
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include "boost\tokenizer.hpp"
|
||||
|
||||
#include "llexperiencecache.h"
|
||||
|
||||
|
|
@ -38,7 +39,6 @@
|
|||
|
||||
namespace LLExperienceCache
|
||||
{
|
||||
bool sRunning = false;
|
||||
std::string sLookupURL;
|
||||
|
||||
typedef std::set<LLUUID> ask_queue_t;
|
||||
|
|
@ -47,29 +47,45 @@ namespace LLExperienceCache
|
|||
typedef std::map<LLUUID, F64> pending_queue_t;
|
||||
pending_queue_t sPendingQueue;
|
||||
|
||||
|
||||
cache_t sCache;
|
||||
int sMaximumLookups = 10;
|
||||
|
||||
LLFrameTimer sRequestTimer;
|
||||
|
||||
// Periodically clean out expired entries from the cache
|
||||
LLFrameTimer sEraseExpiredTimer;
|
||||
|
||||
// May have multiple callbacks for a single ID, which are
|
||||
// represented as multiple slots bound to the signal.
|
||||
// Avoid copying signals via pointers.
|
||||
typedef std::map<LLUUID, callback_signal_t*> signal_map_t;
|
||||
signal_map_t sSignalMap;
|
||||
|
||||
|
||||
void processExperience( const LLUUID& agent_id, const LLExperienceData& experience, bool add_to_cache )
|
||||
bool max_age_from_cache_control(const std::string& cache_control, S32 *max_age);
|
||||
void eraseExpired();
|
||||
|
||||
void processExperience( const LLUUID& agent_id, const LLExperienceData& experience )
|
||||
{
|
||||
if(add_to_cache)
|
||||
sCache[agent_id]=experience;
|
||||
|
||||
sPendingQueue.erase(agent_id);
|
||||
|
||||
//signal
|
||||
signal_map_t::iterator sig_it = sSignalMap.find(agent_id);
|
||||
if (sig_it != sSignalMap.end())
|
||||
{
|
||||
sCache[agent_id]=experience;
|
||||
callback_signal_t* signal = sig_it->second;
|
||||
(*signal)(agent_id, experience);
|
||||
|
||||
sPendingQueue.erase(agent_id);
|
||||
sSignalMap.erase(agent_id);
|
||||
|
||||
|
||||
//signal
|
||||
delete signal;
|
||||
}
|
||||
}
|
||||
|
||||
void initClass( bool running )
|
||||
void initClass( )
|
||||
{
|
||||
sRunning = false;
|
||||
}
|
||||
|
||||
const cache_t& getCached()
|
||||
|
|
@ -77,6 +93,91 @@ namespace LLExperienceCache
|
|||
return sCache;
|
||||
}
|
||||
|
||||
void setMaximumLookups( int maximumLookups)
|
||||
{
|
||||
sMaximumLookups = maximumLookups;
|
||||
}
|
||||
|
||||
|
||||
bool expirationFromCacheControl(LLSD headers, F64 *expires)
|
||||
{
|
||||
// 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_DEBUGS("ExperienceCache")
|
||||
<< "got expiration from headers, max_age " << max_age
|
||||
<< LL_ENDL;
|
||||
F64 now = LLFrameTimer::getTotalSeconds();
|
||||
*expires = now + (F64)max_age;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static const std::string MAX_AGE("max-age");
|
||||
static const boost::char_separator<char> EQUALS_SEPARATOR("=");
|
||||
static const boost::char_separator<char> COMMA_SEPARATOR(",");
|
||||
|
||||
bool max_age_from_cache_control(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;
|
||||
tokenizer directives(cache_control, COMMA_SEPARATOR);
|
||||
|
||||
tokenizer::iterator token_it = directives.begin();
|
||||
for ( ; token_it != directives.end(); ++token_it)
|
||||
{
|
||||
// Tokens may have leading or trailing whitespace
|
||||
std::string token = *token_it;
|
||||
LLStringUtil::trim(token);
|
||||
|
||||
if (token.compare(0, MAX_AGE.size(), MAX_AGE) == 0)
|
||||
{
|
||||
// ...this token starts with max-age, so let's chop it up by "="
|
||||
tokenizer subtokens(token, EQUALS_SEPARATOR);
|
||||
tokenizer::iterator subtoken_it = subtokens.begin();
|
||||
|
||||
// Must have a token
|
||||
if (subtoken_it == subtokens.end()) return false;
|
||||
std::string subtoken = *subtoken_it;
|
||||
|
||||
// Must exactly equal "max-age"
|
||||
LLStringUtil::trim(subtoken);
|
||||
if (subtoken != MAX_AGE) return false;
|
||||
|
||||
// Must have another token
|
||||
++subtoken_it;
|
||||
if (subtoken_it == subtokens.end()) return false;
|
||||
subtoken = *subtoken_it;
|
||||
|
||||
// Must be a valid integer
|
||||
// *NOTE: atoi() returns 0 for invalid values, so we have to
|
||||
// check the string first.
|
||||
// *TODO: Do servers ever send "0000" for zero? We don't handle it
|
||||
LLStringUtil::trim(subtoken);
|
||||
if (subtoken == "0")
|
||||
{
|
||||
*max_age = 0;
|
||||
return true;
|
||||
}
|
||||
S32 val = atoi( subtoken.c_str() );
|
||||
if (val > 0 && val < S32_MAX)
|
||||
{
|
||||
*max_age = val;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void importFile(std::istream& istr)
|
||||
|
|
@ -146,7 +247,7 @@ namespace LLExperienceCache
|
|||
LL_DEBUGS("ExperienceCache") << __FUNCTION__ << "Received result for " << agent_id
|
||||
<< "display '" << experience.mDisplayName << "'" << LL_ENDL ;
|
||||
|
||||
processExperience(agent_id, experience, true);
|
||||
processExperience(agent_id, experience);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -154,13 +255,74 @@ namespace LLExperienceCache
|
|||
S32 num_unresolved = unresolved_agents.size();
|
||||
if(num_unresolved > 0)
|
||||
{
|
||||
LL_DEBUGS("ExperienceCache") << __FUNCTION__ << "Ignoreing " << num_unresolved
|
||||
LL_DEBUGS("ExperienceCache") << __FUNCTION__ << "Ignoring " << num_unresolved
|
||||
<< " bad ids" << LL_ENDL ;
|
||||
}
|
||||
|
||||
LL_DEBUGS("ExperienceCache") << __FUNCTION__ << sCache.size() << " cached experiences" << LL_ENDL;
|
||||
}
|
||||
|
||||
void error(U32 status, const std::string& reason)
|
||||
{
|
||||
// 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(status);
|
||||
|
||||
LLExperienceData experience;
|
||||
experience.mDisplayName = LLExperienceCache::DUMMY_NAME;
|
||||
experience.mDescription = LLExperienceCache::DUMMY_NAME;
|
||||
experience.mExpires = retry_timestamp;
|
||||
|
||||
// Add dummy records for all agent IDs in this request
|
||||
std::vector<LLUUID>::const_iterator it = mAgentIds.begin();
|
||||
for ( ; it != mAgentIds.end(); ++it)
|
||||
{
|
||||
LLExperienceCache::processExperience((*it), experience);
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
F64 now = LLFrameTimer::getTotalSeconds();
|
||||
|
||||
// Retry-After takes priority
|
||||
LLSD retry_after = mHeaders["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 now + F64(delta_seconds);
|
||||
}
|
||||
}
|
||||
|
||||
// If no Retry-After, look for Cache-Control max-age
|
||||
F64 expires = 0.0;
|
||||
if (LLExperienceCache::expirationFromCacheControl(mHeaders, &expires))
|
||||
{
|
||||
return expires;
|
||||
}
|
||||
|
||||
// No information in header, make a guess
|
||||
if (status == 503)
|
||||
{
|
||||
// ...service unavailable, retry soon
|
||||
const F64 SERVICE_UNAVAILABLE_DELAY = 600.0; // 10 min
|
||||
return now + SERVICE_UNAVAILABLE_DELAY;
|
||||
}
|
||||
else
|
||||
{
|
||||
// ...other unexpected error
|
||||
const F64 DEFAULT_DELAY = 3600.0; // 1 hour
|
||||
return now + DEFAULT_DELAY;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<LLUUID> mAgentIds;
|
||||
LLSD mHeaders;
|
||||
|
|
@ -186,13 +348,15 @@ namespace LLExperienceCache
|
|||
|
||||
std::string arg="?ids=";
|
||||
|
||||
for(ask_queue_t::const_iterator it = sAskQueue.begin(); it != sAskQueue.end() ; ++it)
|
||||
int request_count = 0;
|
||||
for(ask_queue_t::const_iterator it = sAskQueue.begin() ; it != sAskQueue.end() && request_count < sMaximumLookups; ++it)
|
||||
{
|
||||
const LLUUID& agent_id = *it;
|
||||
|
||||
url += arg;
|
||||
url += agent_id.asString();
|
||||
agent_ids.push_back(agent_id);
|
||||
request_count++;
|
||||
|
||||
sPendingQueue[agent_id] = now;
|
||||
|
||||
|
|
@ -244,7 +408,20 @@ namespace LLExperienceCache
|
|||
|
||||
void idle()
|
||||
{
|
||||
sRunning = true;
|
||||
|
||||
const F32 SECS_BETWEEN_REQUESTS = 0.1f;
|
||||
if (!sRequestTimer.checkExpirationAndReset(SECS_BETWEEN_REQUESTS))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Must be large relative to above
|
||||
const F32 ERASE_EXPIRED_TIMEOUT = 60.f; // seconds
|
||||
if (sEraseExpiredTimer.checkExpirationAndReset(ERASE_EXPIRED_TIMEOUT))
|
||||
{
|
||||
eraseExpired();
|
||||
}
|
||||
|
||||
|
||||
if(!sAskQueue.empty())
|
||||
{
|
||||
|
|
@ -257,6 +434,25 @@ namespace LLExperienceCache
|
|||
sCache.erase(agent_id);
|
||||
}
|
||||
|
||||
void eraseExpired()
|
||||
{
|
||||
S32 expired_count = 0;
|
||||
F64 now = LLFrameTimer::getTotalSeconds();
|
||||
cache_t::iterator it = sCache.begin();
|
||||
while (it != sCache.end())
|
||||
{
|
||||
cache_t::iterator cur = it;
|
||||
++it;
|
||||
const LLExperienceData& experience = cur->second;
|
||||
if (experience.mExpires < now)
|
||||
{
|
||||
sCache.erase(cur);
|
||||
expired_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void fetch( const LLUUID& agent_id )
|
||||
{
|
||||
LL_DEBUGS("ExperienceCache") << __FUNCTION__ << "queue request for agent" << agent_id << LL_ENDL ;
|
||||
|
|
@ -270,16 +466,12 @@ namespace LLExperienceCache
|
|||
|
||||
bool get( const LLUUID& agent_id, LLExperienceData* experience_data )
|
||||
{
|
||||
if(sRunning)
|
||||
cache_t::const_iterator it = sCache.find(agent_id);
|
||||
if (it != sCache.end())
|
||||
{
|
||||
|
||||
cache_t::const_iterator it = sCache.find(agent_id);
|
||||
if (it != sCache.end())
|
||||
{
|
||||
llassert(experience_data);
|
||||
*experience_data = it->second;
|
||||
return true;
|
||||
}
|
||||
llassert(experience_data);
|
||||
*experience_data = it->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
if(!isRequestPending(agent_id))
|
||||
|
|
@ -291,12 +483,54 @@ namespace LLExperienceCache
|
|||
}
|
||||
|
||||
|
||||
|
||||
void get( const LLUUID& agent_id, callback_slot_t slot )
|
||||
{
|
||||
cache_t::const_iterator it = sCache.find(agent_id);
|
||||
if (it != sCache.end())
|
||||
{
|
||||
// ...name already exists in cache, fire callback now
|
||||
callback_signal_t signal;
|
||||
signal.connect(slot);
|
||||
signal(agent_id, it->second);
|
||||
return;
|
||||
}
|
||||
|
||||
// schedule a request
|
||||
if (!isRequestPending(agent_id))
|
||||
{
|
||||
sAskQueue.insert(agent_id);
|
||||
}
|
||||
|
||||
// always store additional callback, even if request is pending
|
||||
signal_map_t::iterator sig_it = sSignalMap.find(agent_id);
|
||||
if (sig_it == sSignalMap.end())
|
||||
{
|
||||
// ...new callback for this id
|
||||
callback_signal_t* signal = new callback_signal_t();
|
||||
signal->connect(slot);
|
||||
sSignalMap[agent_id] = signal;
|
||||
}
|
||||
else
|
||||
{
|
||||
// ...existing callback, bind additional slot
|
||||
callback_signal_t* signal = sig_it->second;
|
||||
signal->connect(slot);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static const std::string EXPERIENCE_NAME("username");
|
||||
static const std::string EXPERIENCE_DESCRIPTION("display_name");
|
||||
static const std::string EXPERIENCE_EXPIRATION("display_name_expires");
|
||||
|
||||
bool LLExperienceData::fromLLSD( const LLSD& sd )
|
||||
{
|
||||
mDisplayName = sd["display_name"].asString();
|
||||
mDescription = sd["username"].asString();
|
||||
mDisplayName = sd[EXPERIENCE_NAME].asString();
|
||||
mDescription = sd[EXPERIENCE_DESCRIPTION].asString();
|
||||
LLDate expiration = sd[EXPERIENCE_EXPIRATION];
|
||||
mExpires = expiration.secondsSinceEpoch();
|
||||
|
||||
if(mDisplayName.empty() || mDescription.empty()) return false;
|
||||
|
||||
|
|
@ -307,7 +541,9 @@ bool LLExperienceData::fromLLSD( const LLSD& sd )
|
|||
LLSD LLExperienceData::asLLSD() const
|
||||
{
|
||||
LLSD sd;
|
||||
sd["display_name"] = mDisplayName;
|
||||
sd["username"] = mDescription.substr(0, llmin(mDescription.size(),mDescription.find(" %")));
|
||||
sd[EXPERIENCE_NAME] = mDisplayName;
|
||||
sd[EXPERIENCE_DESCRIPTION] = mDescription.substr(0, llmin(mDescription.size(),mDescription.find(" %")));
|
||||
sd[EXPERIENCE_EXPIRATION] = LLDate(mExpires);
|
||||
|
||||
return sd;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
#define LL_LLEXPERIENCECACHE_H
|
||||
|
||||
#include <string>
|
||||
#include <boost/signals2.hpp>
|
||||
|
||||
class LLUUID;
|
||||
|
||||
|
|
@ -43,27 +44,40 @@ public:
|
|||
|
||||
std::string mDisplayName;
|
||||
std::string mDescription;
|
||||
F64 mExpires;
|
||||
};
|
||||
|
||||
|
||||
|
||||
namespace LLExperienceCache
|
||||
{
|
||||
// dummy name used when we have nothing else
|
||||
const std::string DUMMY_NAME = "\?\?\?";
|
||||
// Callback types for get() below
|
||||
typedef boost::signals2::signal<
|
||||
void (const LLUUID& agent_id, const LLExperienceData& experience)>
|
||||
callback_signal_t;
|
||||
typedef callback_signal_t::slot_type callback_slot_t;
|
||||
typedef std::map<LLUUID, LLExperienceData> cache_t;
|
||||
|
||||
|
||||
void setLookupURL(const std::string& lookup_url);
|
||||
bool hasLookupURL();
|
||||
|
||||
void setMaximumLookups(int maximumLookups);
|
||||
|
||||
void idle();
|
||||
void exportFile(std::ostream& ostr);
|
||||
void importFile(std::istream& istr);
|
||||
void initClass(bool running);
|
||||
void initClass();
|
||||
|
||||
void erase(const LLUUID& agent_id);
|
||||
void fetch(const LLUUID& agent_id);
|
||||
void insert(const LLUUID& agent_id, const LLExperienceData& experience_data);
|
||||
bool get(const LLUUID& agent_id, LLExperienceData* experience_data);
|
||||
|
||||
typedef std::map<LLUUID, LLExperienceData> cache_t;
|
||||
// If name information is in cache, callback will be called immediately.
|
||||
void get(const LLUUID& agent_id, callback_slot_t slot);
|
||||
|
||||
const cache_t& getCached();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2817,7 +2817,7 @@ void LLStartUp::initNameCache()
|
|||
void LLStartUp::initExperienceCache()
|
||||
{
|
||||
LLAppViewer::instance()->loadExperienceCache();
|
||||
LLExperienceCache::initClass(false);
|
||||
LLExperienceCache::initClass();
|
||||
}
|
||||
|
||||
void LLStartUp::cleanupNameCache()
|
||||
|
|
|
|||
Loading…
Reference in New Issue