Improve AISv3 code to retry less aggressively

Syncs LLAISAPI with upstream alchemy
Thanks to Kitty Barnett for Appearance-SyncAttach patch
Fixes typos, uses more modern C++ features,
Accounts for VERSION_UNKNOWN so the viewer won't get stuck
Adds in support for 410 GONE response
No longer retries on empty map, malformed response.
This commit is contained in:
Liru Færs
2020-01-21 02:12:34 -05:00
parent 27d4e05f2b
commit 851b3659ee
2 changed files with 129 additions and 57 deletions

View File

@@ -35,13 +35,12 @@
#include "llviewerregion.h" #include "llviewerregion.h"
#include "llinventoryobserver.h" #include "llinventoryobserver.h"
#include "llviewercontrol.h" #include "llviewercontrol.h"
#include <boost/bind.hpp>
///---------------------------------------------------------------------------- ///----------------------------------------------------------------------------
/// Classes for AISv3 support. /// Classes for AISv3 support.
///---------------------------------------------------------------------------- ///----------------------------------------------------------------------------
class AISCommand : public LLHTTPClient::ResponderWithResult class AISCommand final : public LLHTTPClient::ResponderWithCompleted
{ {
public: public:
typedef boost::function<void()> command_func_type; typedef boost::function<void()> command_func_type;
@@ -60,35 +59,28 @@ public:
(mCommandFunc = func)(); (mCommandFunc = func)();
} }
char const* getName(void) const char const* getName(void) const override
{ {
return mName; return mName;
} }
void markComplete() void markComplete()
{
mRetryPolicy->onSuccess();
}
protected:
/* virtual */
void httpSuccess()
{ {
// Command func holds a reference to self, need to release it // Command func holds a reference to self, need to release it
// after a success or final failure. // after a success or final failure.
mCommandFunc = no_op; mCommandFunc = no_op;
AISAPI::InvokeAISCommandCoro(this, getURL(), mTargetId, getContent(), mCompletionFunc, (AISAPI::COMMAND_TYPE)mType); mRetryPolicy->onSuccess();
} }
/*virtual*/ void malformedResponse() { mStatus = HTTP_INTERNAL_ERROR_OTHER; mReason = llformat("Malformed response contents (original code: %d)", mStatus); }
void httpFailure()
bool onFailure()
{ {
LL_WARNS("Inventory") << dumpResponse() << LL_ENDL; bool retry = mStatus != HTTP_INTERNAL_ERROR_OTHER && mStatus != 410; // We handle these and stop
S32 status = getStatus(); LL_WARNS("Inventory") << "Inventory error: " << dumpResponse() << LL_ENDL;
const AIHTTPReceivedHeaders& headers = getResponseHeaders(); if (retry) mRetryPolicy->onFailure(mStatus, getResponseHeaders());
mRetryPolicy->onFailure(status, headers);
F32 seconds_to_wait; F32 seconds_to_wait;
if (mRetryPolicy->shouldRetry(seconds_to_wait)) if (retry && mRetryPolicy->shouldRetry(seconds_to_wait))
{ {
doAfterInterval(mCommandFunc,seconds_to_wait); doAfterInterval(mCommandFunc,seconds_to_wait);
} }
@@ -99,6 +91,13 @@ protected:
// *TODO: Notify user? This seems bad. // *TODO: Notify user? This seems bad.
mCommandFunc = no_op; mCommandFunc = no_op;
} }
return retry;
}
protected:
void httpCompleted() override
{
AISAPI::InvokeAISCommandCoro(this, getURL(), mTargetId, getContent(), mCompletionFunc, (AISAPI::COMMAND_TYPE)mType);
} }
command_func_type mCommandFunc; command_func_type mCommandFunc;
@@ -313,31 +312,97 @@ void AISAPI::UpdateItem(const LLUUID &itemId, const LLSD &updates, completion_t
boost::intrusive_ptr< AISCommand > responder = new AISCommand(UPDATEITEM, "UpdateItem",itemId, callback); boost::intrusive_ptr< AISCommand > responder = new AISCommand(UPDATEITEM, "UpdateItem",itemId, callback);
responder->run(boost::bind(&LLHTTPClient::patch, url, updates, responder/*,*/ DEBUG_CURLIO_PARAM(debug_off), keep_alive, (AIStateMachine*)NULL, 0)); responder->run(boost::bind(&LLHTTPClient::patch, url, updates, responder/*,*/ DEBUG_CURLIO_PARAM(debug_off), keep_alive, (AIStateMachine*)NULL, 0));
} }
void AISAPI::InvokeAISCommandCoro(LLHTTPClient::ResponderWithResult* responder, void AISAPI::InvokeAISCommandCoro(AISCommand* responder,
std::string url, std::string url,
LLUUID targetId, LLSD result, completion_t callback, COMMAND_TYPE type) LLUUID targetId, LLSD result, completion_t callback, COMMAND_TYPE type)
{ {
LL_DEBUGS("Inventory") << "url: " << url << LL_ENDL;
auto status = responder->getStatus();
if (!responder->isGoodStatus(status) || !result.isMap())
{ {
if (!result.isMap()) if (!result.isMap())
{ {
responder->failureResult(400, "Malformed response contents", result); responder->malformedResponse();
return;
} }
((AISCommand*)responder)->markComplete(); else if (status == 410) //GONE
{
// Item does not exist or was already deleted from server.
// parent folder is out of sync
if (type == REMOVECATEGORY)
{
LLViewerInventoryCategory *cat = gInventory.getCategory(targetId);
if (cat)
{
LL_WARNS("Inventory") << "Purge failed for '" << cat->getName()
<< "' local version:" << cat->getVersion()
<< " since folder no longer exists at server. Descendent count: server == " << cat->getDescendentCount()
<< ", viewer == " << cat->getViewerDescendentCount()
<< LL_ENDL;
gInventory.fetchDescendentsOf(cat->getParentUUID());
// Note: don't delete folder here - contained items will be deparented (or deleted)
// and since we are clearly out of sync we can't be sure we won't get rid of something we need.
// For example folder could have been moved or renamed with items intact, let it fetch first.
}
}
else if (type == REMOVEITEM)
{
LLViewerInventoryItem *item = gInventory.getItem(targetId);
if (item)
{
LL_WARNS("Inventory") << "Purge failed for '" << item->getName()
<< "' since item no longer exists at server." << LL_ENDL;
gInventory.fetchDescendentsOf(item->getParentUUID());
// since item not on the server and exists at viewer, so it needs an update at the least,
// so delete it, in worst case item will be refetched with new params.
gInventory.onObjectDeletedFromServer(targetId);
}
}
}
// Keep these statuses accounted for in the responder too
if (responder->onFailure()) // If we're retrying, exit early.
return;
} }
else responder->markComplete();
gInventory.onAISUpdateReceived("AISCommand", result); gInventory.onAISUpdateReceived("AISCommand", result);
if (callback && callback != nullptr) if (callback && callback != nullptr)
{ {
LLUUID id(LLUUID::null); // [SL:KB] - Patch: Appearance-SyncAttach | Checked: Catznip-3.7
uuid_list_t ids;
if (result.has("category_id") && (type == COPYLIBRARYCATEGORY)) switch (type)
{ {
id = result["category_id"]; case COPYLIBRARYCATEGORY:
if (result.has("category_id"))
{
ids.insert(result["category_id"]);
}
break;
case COPYINVENTORY:
{
AISUpdate::parseUUIDArray(result, "_created_items", ids);
AISUpdate::parseUUIDArray(result, "_created_categories", ids);
}
break;
default:
break;
} }
callback(id); // If we were feeling daring we'd call LLInventoryCallback::fire for every item but it would take additional work to investigate whether all LLInventoryCallback derived classes
// were designed to handle multiple fire calls (with legacy link creation only one would ever fire per link creation) so we'll be cautious and only call for the first one for now
// (note that the LL code as written below will always call fire once with the NULL UUID for anything but CopyLibraryCategoryCommand so even the above is an improvement)
callback( (!ids.empty()) ? *ids.begin() : LLUUID::null);
// [/SL:KB]
// LLUUID id(LLUUID::null);
//
// if (result.has("category_id") && (type == COPYLIBRARYCATEGORY))
// {
// id = result["category_id"];
// }
//
// callback(id);
} }
} }
@@ -374,18 +439,17 @@ void AISUpdate::parseMeta(const LLSD& update)
// parse _categories_removed -> mObjectsDeletedIds // parse _categories_removed -> mObjectsDeletedIds
uuid_list_t cat_ids; uuid_list_t cat_ids;
parseUUIDArray(update,"_categories_removed",cat_ids); parseUUIDArray(update,"_categories_removed",cat_ids);
for (uuid_list_t::const_iterator it = cat_ids.begin(); for (auto cat_id : cat_ids)
it != cat_ids.end(); ++it)
{ {
LLViewerInventoryCategory *cat = gInventory.getCategory(*it); LLViewerInventoryCategory *cat = gInventory.getCategory(cat_id);
if(cat) if(cat)
{ {
mCatDescendentDeltas[cat->getParentUUID()]--; mCatDescendentDeltas[cat->getParentUUID()]--;
mObjectsDeletedIds.insert(*it); mObjectsDeletedIds.insert(cat_id);
} }
else else
{ {
LL_WARNS("Inventory") << "removed category not found " << *it << LL_ENDL; LL_WARNS("Inventory") << "removed category not found " << cat_id << LL_ENDL;
} }
} }
@@ -393,36 +457,34 @@ void AISUpdate::parseMeta(const LLSD& update)
uuid_list_t item_ids; uuid_list_t item_ids;
parseUUIDArray(update,"_category_items_removed",item_ids); parseUUIDArray(update,"_category_items_removed",item_ids);
parseUUIDArray(update,"_removed_items",item_ids); parseUUIDArray(update,"_removed_items",item_ids);
for (uuid_list_t::const_iterator it = item_ids.begin(); for (auto item_id : item_ids)
it != item_ids.end(); ++it)
{ {
LLViewerInventoryItem *item = gInventory.getItem(*it); LLViewerInventoryItem *item = gInventory.getItem(item_id);
if(item) if(item)
{ {
mCatDescendentDeltas[item->getParentUUID()]--; mCatDescendentDeltas[item->getParentUUID()]--;
mObjectsDeletedIds.insert(*it); mObjectsDeletedIds.insert(item_id);
} }
else else
{ {
LL_WARNS("Inventory") << "removed item not found " << *it << LL_ENDL; LL_WARNS("Inventory") << "removed item not found " << item_id << LL_ENDL;
} }
} }
// parse _broken_links_removed -> mObjectsDeletedIds // parse _broken_links_removed -> mObjectsDeletedIds
uuid_list_t broken_link_ids; uuid_list_t broken_link_ids;
parseUUIDArray(update,"_broken_links_removed",broken_link_ids); parseUUIDArray(update,"_broken_links_removed",broken_link_ids);
for (uuid_list_t::const_iterator it = broken_link_ids.begin(); for (auto broken_link_id : broken_link_ids)
it != broken_link_ids.end(); ++it)
{ {
LLViewerInventoryItem *item = gInventory.getItem(*it); LLViewerInventoryItem *item = gInventory.getItem(broken_link_id);
if(item) if(item)
{ {
mCatDescendentDeltas[item->getParentUUID()]--; mCatDescendentDeltas[item->getParentUUID()]--;
mObjectsDeletedIds.insert(*it); mObjectsDeletedIds.insert(broken_link_id);
} }
else else
{ {
LL_WARNS("Inventory") << "broken link not found " << *it << LL_ENDL; LL_WARNS("Inventory") << "broken link not found " << broken_link_id << LL_ENDL;
} }
} }
@@ -766,7 +828,7 @@ void AISUpdate::parseEmbeddedCategories(const LLSD& categories)
void AISUpdate::doUpdate() void AISUpdate::doUpdate()
{ {
// Do version/descendent accounting. // Do version/descendant accounting.
for (std::map<LLUUID,S32>::const_iterator catit = mCatDescendentDeltas.begin(); for (std::map<LLUUID,S32>::const_iterator catit = mCatDescendentDeltas.begin();
catit != mCatDescendentDeltas.end(); ++catit) catit != mCatDescendentDeltas.end(); ++catit)
{ {
@@ -787,7 +849,7 @@ void AISUpdate::doUpdate()
continue; continue;
} }
// If we have a known descendent count, set that now. // If we have a known descendant count, set that now.
LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id); LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id);
if (cat) if (cat)
{ {
@@ -824,7 +886,7 @@ void AISUpdate::doUpdate()
LLUUID category_id(update_it->first); LLUUID category_id(update_it->first);
LLPointer<LLViewerInventoryCategory> new_category = update_it->second; LLPointer<LLViewerInventoryCategory> new_category = update_it->second;
// Since this is a copy of the category *before* the accounting update, above, // Since this is a copy of the category *before* the accounting update, above,
// we need to transfer back the updated version/descendent count. // we need to transfer back the updated version/descendant count.
LLViewerInventoryCategory* curr_cat = gInventory.getCategory(new_category->getUUID()); LLViewerInventoryCategory* curr_cat = gInventory.getCategory(new_category->getUUID());
if (!curr_cat) if (!curr_cat)
{ {
@@ -868,21 +930,19 @@ void AISUpdate::doUpdate()
} }
// DELETE OBJECTS // DELETE OBJECTS
for (uuid_list_t::const_iterator del_it = mObjectsDeletedIds.begin(); for (auto deleted_id : mObjectsDeletedIds)
del_it != mObjectsDeletedIds.end(); ++del_it)
{ {
LL_DEBUGS("Inventory") << "deleted item " << *del_it << LL_ENDL; LL_DEBUGS("Inventory") << "deleted item " << deleted_id << LL_ENDL;
gInventory.onObjectDeletedFromServer(*del_it, false, false, false); gInventory.onObjectDeletedFromServer(deleted_id, false, false, false);
} }
// TODO - how can we use this version info? Need to be sure all // TODO - how can we use this version info? Need to be sure all
// changes are going through AIS first, or at least through // changes are going through AIS first, or at least through
// something with a reliable responder. // something with a reliable responder.
for (uuid_int_map_t::iterator ucv_it = mCatVersionsUpdated.begin(); for (auto& ucv_it : mCatVersionsUpdated)
ucv_it != mCatVersionsUpdated.end(); ++ucv_it)
{ {
const LLUUID id = ucv_it->first; const LLUUID id = ucv_it.first;
S32 version = ucv_it->second; S32 version = ucv_it.second;
LLViewerInventoryCategory *cat = gInventory.getCategory(id); LLViewerInventoryCategory *cat = gInventory.getCategory(id);
LL_DEBUGS("Inventory") << "cat version update " << cat->getName() << " to version " << cat->getVersion() << LL_ENDL; LL_DEBUGS("Inventory") << "cat version update " << cat->getName() << " to version " << cat->getVersion() << LL_ENDL;
if (cat->getVersion() != version) if (cat->getVersion() != version)
@@ -898,7 +958,16 @@ void AISUpdate::doUpdate()
// inventory COF is maintained on the viewer through calls to // inventory COF is maintained on the viewer through calls to
// LLInventoryModel::accountForUpdate when a changing operation // LLInventoryModel::accountForUpdate when a changing operation
// is performed. This occasionally gets out of sync however. // is performed. This occasionally gets out of sync however.
cat->setVersion(version); if (version != LLViewerInventoryCategory::VERSION_UNKNOWN)
{
cat->setVersion(version);
}
else
{
// We do not account for update if version is UNKNOWN, so we shouldn't rise version
// either or viewer will get stuck on descendants count -1, try to refetch folder instead
cat->fetch();
}
} }
} }

View File

@@ -72,7 +72,7 @@ private:
static std::string getInvCap(); static std::string getInvCap();
static std::string getLibCap(); static std::string getLibCap();
static void InvokeAISCommandCoro( LLHTTPClient::ResponderWithResult* responder, static void InvokeAISCommandCoro(AISCommand* responder,
std::string url, LLUUID targetId, LLSD body, std::string url, LLUUID targetId, LLSD body,
completion_t callback, COMMAND_TYPE type); completion_t callback, COMMAND_TYPE type);
}; };
@@ -84,7 +84,10 @@ public:
void parseUpdate(const LLSD& update); void parseUpdate(const LLSD& update);
void parseMeta(const LLSD& update); void parseMeta(const LLSD& update);
void parseContent(const LLSD& update); void parseContent(const LLSD& update);
void parseUUIDArray(const LLSD& content, const std::string& name, uuid_list_t& ids); // [SL:KB] - Patch: Appearance-SyncAttach | Checked: Catznip-3.7
static void parseUUIDArray(const LLSD& content, const std::string& name, uuid_list_t& ids);
// [/SL:KB]
// void parseUUIDArray(const LLSD& content, const std::string& name, uuid_list_t& ids);
void parseLink(const LLSD& link_map); void parseLink(const LLSD& link_map);
void parseItem(const LLSD& link_map); void parseItem(const LLSD& link_map);
void parseCategory(const LLSD& link_map); void parseCategory(const LLSD& link_map);