summaryrefslogtreecommitdiff
path: root/indra/newview/llinventorymodel.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/newview/llinventorymodel.cpp')
-rw-r--r--indra/newview/llinventorymodel.cpp364
1 files changed, 261 insertions, 103 deletions
diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp
index 3a7bc20c30..b818a66b30 100644
--- a/indra/newview/llinventorymodel.cpp
+++ b/indra/newview/llinventorymodel.cpp
@@ -28,6 +28,7 @@
#include <typeinfo>
#include <random>
+#include <thread>
#include "llinventorymodel.h"
@@ -82,6 +83,7 @@ const S32 LLInventoryModel::sCurrentInvCacheVersion = 5;
bool LLInventoryModel::sFirstTimeInViewer2 = true;
S32 LLInventoryModel::sPendingSystemFolders = 0;
+static std::vector<std::thread> sPendingCacheThreads;
///----------------------------------------------------------------------------
/// Local function declarations, constants, enums, and typedefs
@@ -100,45 +102,6 @@ struct InventoryIDPtrLess
}
};
-class LLCanCache : public LLInventoryCollectFunctor
-{
-public:
- LLCanCache(LLInventoryModel* model) : mModel(model) {}
- virtual ~LLCanCache() {}
- virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item);
-protected:
- LLInventoryModel* mModel;
- std::set<LLUUID> mCachedCatIDs;
-};
-
-bool LLCanCache::operator()(LLInventoryCategory* cat, LLInventoryItem* item)
-{
- bool rv = false;
- if(item)
- {
- if(mCachedCatIDs.find(item->getParentUUID()) != mCachedCatIDs.end())
- {
- rv = true;
- }
- }
- else if(cat)
- {
- // HACK: downcast
- LLViewerInventoryCategory* c = (LLViewerInventoryCategory*)cat;
- if(c->getVersion() != LLViewerInventoryCategory::VERSION_UNKNOWN)
- {
- S32 descendents_server = c->getDescendentCount();
- S32 descendents_actual = c->getViewerDescendentCount();
- if(descendents_server == descendents_actual)
- {
- mCachedCatIDs.insert(c->getUUID());
- rv = true;
- }
- }
- }
- return rv;
-}
-
struct InventoryCallbackInfo
{
InventoryCallbackInfo(U32 callback, const LLUUID& inv_id) :
@@ -433,6 +396,8 @@ LLInventoryModel::LLInventoryModel()
: // These are now ordered, keep them that way.
mBacklinkMMap(),
mIsAgentInvUsable(false),
+ mLibrarySkeletonLoadTime(0.f),
+ mAgentSkeletonLoadTime(0.f),
mRootFolderID(),
mLibraryRootFolderID(),
mLibraryOwnerID(),
@@ -465,30 +430,33 @@ LLInventoryModel::~LLInventoryModel()
void LLInventoryModel::cleanupInventory()
{
+ LL_PROFILE_ZONE_SCOPED;
empty();
- // Deleting one observer might erase others from the list, so always pop off the front
- while (!mObservers.empty())
+ // Deleting one observer might trigger removeObserver, so use a local copy
+ if (!mObservers.empty())
{
- observer_list_t::iterator iter = mObservers.begin();
- LLInventoryObserver* observer = *iter;
- mObservers.erase(iter);
- delete observer;
+ observer_list_t observers_to_delete;
+ observers_to_delete.swap(mObservers);
+
+ for (LLInventoryObserver* observer : observers_to_delete)
+ {
+ delete observer;
+ }
}
if (mBulkFecthCallbackSlot.connected())
{
mBulkFecthCallbackSlot.disconnect();
}
- mObservers.clear();
// Run down HTTP transport
mHttpHeaders.reset();
mHttpOptions.reset();
delete mHttpRequestFG;
- mHttpRequestFG = NULL;
+ mHttpRequestFG = nullptr;
delete mHttpRequestBG;
- mHttpRequestBG = NULL;
+ mHttpRequestBG = nullptr;
}
// This is a convenience function to check if one object has a parent
@@ -975,6 +943,15 @@ const LLUUID LLInventoryModel::findLibraryCategoryUUIDForType(LLFolderType::ETyp
return findCategoryUUIDForTypeInRoot(preferred_type, gInventory.getLibraryRootFolderID());
}
+const LLUUID LLInventoryModel::getMarketplaceListingsUUID()
+{
+ if (mMarketplaceListingsUUID.isNull())
+ {
+ mMarketplaceListingsUUID = findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS);
+ }
+ return mMarketplaceListingsUUID;
+}
+
// Convenience function to create a new category. You could call
// updateCategory() with a newly generated UUID category, but this
// version will take care of details like what the name should be
@@ -1692,7 +1669,7 @@ void LLInventoryModel::updateCategory(const LLViewerInventoryCategory* cat, U32
mask |= LLInventoryObserver::LABEL;
}
// Under marketplace, category labels are quite complex and need extra upate
- const LLUUID marketplace_id = findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS);
+ const LLUUID marketplace_id = getMarketplaceListingsUUID();
if (marketplace_id.notNull() && isObjectDescendentOf(cat->getUUID(), marketplace_id))
{
mask |= LLInventoryObserver::LABEL;
@@ -2358,39 +2335,183 @@ void LLInventoryModel::cache(
const LLUUID& parent_folder_id,
const LLUUID& agent_id)
{
+ LL_PROFILE_ZONE_SCOPED;
LL_DEBUGS(LOG_INV) << "Caching " << parent_folder_id << " for " << agent_id
<< LL_ENDL;
+
LLViewerInventoryCategory* root_cat = getCategory(parent_folder_id);
- if(!root_cat) return;
+ if (!root_cat)
+ {
+ LL_WARNS(LOG_INV) << "Root category not found for " << parent_folder_id << LL_ENDL;
+ return;
+ }
+
cat_array_t categories;
categories.push_back(root_cat);
item_array_t items;
- LLCanCache can_cache(this);
- can_cache(root_cat, NULL);
- collectDescendentsIf(
- parent_folder_id,
- categories,
- items,
- INCLUDE_TRASH,
- can_cache);
+ // Lambda to check if a category should be cached
+ // Only cache if it has known version and matching descendent counts
+ auto should_cache_category = [](LLViewerInventoryCategory* cat) -> bool {
+ if (!cat || cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN)
+ {
+ return false;
+ }
+ S32 descendents_server = cat->getDescendentCount();
+ S32 descendents_actual = cat->getViewerDescendentCount();
+ return (descendents_server == descendents_actual);
+ };
+
+ // Track which folders we've verified as cacheable descendants
+ std::unordered_set<LLUUID> processed_folders;
+ processed_folders.insert(parent_folder_id);
+
+ // First pass: identify all cacheable descendant folders
+ // Use pair of (folder_id, should_save_children)
+ std::deque<std::pair<LLUUID, bool>> folders_to_check;
+ folders_to_check.push_back(std::make_pair(parent_folder_id, should_cache_category(root_cat)));
+
+ while (!folders_to_check.empty())
+ {
+ auto [current_id, save_children] = folders_to_check.front();
+ folders_to_check.pop_front();
+
+ if (save_children) // else incorrect count or version
+ {
+ auto item_it = mParentChildItemTree.find(current_id);
+ if (item_it != mParentChildItemTree.end() && item_it->second)
+ {
+ for (LLViewerInventoryItem* item : *(item_it->second))
+ {
+ if (item)
+ {
+ items.push_back(item);
+ }
+ }
+ }
+ }
+
+ // Get child categories directly from the parent-child tree
+ auto cat_it = mParentChildCategoryTree.find(current_id);
+ if (cat_it != mParentChildCategoryTree.end() && cat_it->second)
+ {
+ for (LLViewerInventoryCategory* child_cat : *(cat_it->second))
+ {
+ if (!child_cat)
+ {
+ continue;
+ }
+
+ // Verify ownership matches (library vs agent inventory)
+ if (child_cat->getOwnerID() != root_cat->getOwnerID())
+ {
+ LL_WARNS(LOG_INV) << "Owner mismatch in category tree: expected "
+ << root_cat->getOwnerID() << " got "
+ << child_cat->getOwnerID() << " for category "
+ << child_cat->getName() << LL_ENDL;
+ continue;
+ }
+
+ const LLUUID& child_id = child_cat->getUUID();
+
+ // Only process each folder once
+ if (processed_folders.insert(child_id).second)
+ {
+ if (should_cache_category(child_cat))
+ {
+ categories.push_back(child_cat);
+ folders_to_check.push_back(std::make_pair(child_id, true));
+ }
+ else
+ {
+ folders_to_check.push_back(std::make_pair(child_id, false));
+ }
+ }
+ }
+ }
+ }
+
+ if (categories.empty() && items.empty())
+ {
+ LL_WARNS(LOG_INV) << "Nothing to cache for " << parent_folder_id << LL_ENDL;
+ return;
+ }
+
// Use temporary file to avoid potential conflicts with other
// instances (even a 'read only' instance unzips into a file)
std::string temp_file = gDirUtilp->getTempFilename();
- saveToFile(temp_file, categories, items);
+ if (!saveToFile(temp_file, categories, items))
+ {
+ LL_WARNS(LOG_INV) << "Failed to save inventory cache for " << parent_folder_id << LL_ENDL;
+ LLFile::remove(temp_file);
+ return;
+ }
std::string gzip_filename = getInvCacheAddres(agent_id);
gzip_filename.append(".gz");
- if(gzip_file(temp_file, gzip_filename))
+
+ if (sPendingCacheThreads.empty())
{
- LL_DEBUGS(LOG_INV) << "Successfully compressed " << temp_file << " to " << gzip_filename << LL_ENDL;
- LLFile::remove(temp_file);
+ LL_INFOS(LOG_INV) << "Inventory cache compression started" << LL_ENDL;
}
- else
+
+ // Launch background packing thread
+ // Main thread is the only one modifying sPendingCacheThreads
+ auto compress_cache = [temp_file, gzip_filename]()
{
- LL_WARNS(LOG_INV) << "Unable to compress " << temp_file << " into " << gzip_filename << LL_ENDL;
+ LL_PROFILE_ZONE_NAMED("inv cache compression");
+ LLTimer gzip_timer;
+
+ if (gzip_file(temp_file, gzip_filename))
+ {
+ F32 gzip_time = gzip_timer.getElapsedTimeF32();
+ LL_DEBUGS(LOG_INV) << "Successfully compressed " << temp_file
+ << " to " << gzip_filename
+ << " in " << gzip_time << "s" << LL_ENDL;
+ LLFile::remove(temp_file);
+ }
+ else
+ {
+ LL_WARNS(LOG_INV) << "Unable to compress " << temp_file
+ << " into " << gzip_filename << LL_ENDL;
+ }
+ };
+
+ try
+ {
+ sPendingCacheThreads.emplace_back(compress_cache);
+ }
+ catch (...)
+ {
+ LL_WARNS(LOG_INV) << "Failed to start inventory cache compression thread; running compression synchronously" << LL_ENDL;
+ compress_cache();
}
}
+void LLInventoryModel::waitForPendingCacheWrites()
+{
+ if (sPendingCacheThreads.empty())
+ {
+ return;
+ }
+ LL_PROFILE_ZONE_SCOPED;
+
+ // By this point all threads should have already been added,
+ // viewer is shutting down, main thread is the only one to
+ // modify sPendingCacheThreads
+ LL_DEBUGS(LOG_INV) << "Waiting for " << sPendingCacheThreads.size()
+ << " inventory cache compression thread(s) to complete..." << LL_ENDL;
+
+ for (auto& thread : sPendingCacheThreads)
+ {
+ if (thread.joinable())
+ {
+ thread.join();
+ }
+ }
+ sPendingCacheThreads.clear();
+
+ LL_INFOS(LOG_INV) << "Inventory cache compression completed" << LL_ENDL;
+}
void LLInventoryModel::addCategory(LLViewerInventoryCategory* category)
{
@@ -2698,10 +2819,11 @@ bool LLInventoryModel::loadSkeleton(
for(LLSD::array_const_iterator it = options.beginArray(),
end = options.endArray(); it != end; ++it)
{
- LLSD name = (*it)["name"];
- LLSD folder_id = (*it)["folder_id"];
- LLSD parent_id = (*it)["parent_id"];
- LLSD version = (*it)["version"];
+ const LLSD &folder = *it;
+ const LLSD &name = folder["name"];
+ const LLSD &folder_id = folder["folder_id"];
+ const LLSD &parent_id = folder["parent_id"];
+ const LLSD &version = folder["version"];
if(name.isDefined()
&& folder_id.isDefined()
&& parent_id.isDefined()
@@ -2715,7 +2837,7 @@ bool LLInventoryModel::loadSkeleton(
cat->setParent(parent_id.asUUID());
LLFolderType::EType preferred_type = LLFolderType::FT_NONE;
- LLSD type_default = (*it)["type_default"];
+ const LLSD &type_default = folder["type_default"];
if(type_default.isDefined())
{
preferred_type = (LLFolderType::EType)type_default.asInteger();
@@ -2745,7 +2867,7 @@ bool LLInventoryModel::loadSkeleton(
const S32 NO_VERSION = LLViewerInventoryCategory::VERSION_UNKNOWN;
std::string gzip_filename(inventory_filename);
gzip_filename.append(".gz");
- LLFILE* fp = LLFile::fopen(gzip_filename, "rb");
+ LLFILE* fp = LLFile::fopen(gzip_filename, LLFILE_MODE("rb"));
bool remove_inventory_file = false;
if (LLAppViewer::instance()->isSecondInstance())
{
@@ -2980,6 +3102,16 @@ bool LLInventoryModel::loadSkeleton(
<< " after " << timer.getElapsedTimeF32() << " seconds."
<< LL_ENDL;
+ const F32 elapsed = timer.getElapsedTimeF32();
+ if (owner_id == mLibraryOwnerID)
+ {
+ mLibrarySkeletonLoadTime = elapsed;
+ }
+ else
+ {
+ mAgentSkeletonLoadTime = elapsed;
+ }
+
return rv;
}
@@ -3416,6 +3548,7 @@ bool LLInventoryModel::loadFromFile(const std::string& filename,
LLSD inventory;
if (!is_cache_obsolete)
{
+ LL_PROFILE_ZONE_NAMED("inventory load from file - llsd parse");
LLPointer<LLSDParser> parser = new LLSDBinaryParser();
if (parser->parse(file, inventory, LLSDSerialize::SIZE_UNLIMITED) == LLSDParser::PARSE_FAILURE)
@@ -3427,56 +3560,65 @@ bool LLInventoryModel::loadFromFile(const std::string& filename,
if (!is_cache_obsolete)
{
- const LLSD& llsd_cats = inventory["categories"];
- if (llsd_cats.isArray())
{
- LLSD::array_const_iterator iter = llsd_cats.beginArray();
- LLSD::array_const_iterator end = llsd_cats.endArray();
- for (; iter != end; ++iter)
+ LL_PROFILE_ZONE_NAMED("inventory load from file - categories");
+ const LLSD& llsd_cats = inventory["categories"];
+ if (llsd_cats.isArray())
{
- LLPointer<LLViewerInventoryCategory> inv_cat = new LLViewerInventoryCategory(LLUUID::null);
- if (inv_cat->importLLSDMap(*iter))
+ size_t cats_count = llsd_cats.size();
+ categories.reserve(cats_count);
+ LLSD::array_const_iterator iter = llsd_cats.beginArray();
+ LLSD::array_const_iterator end = llsd_cats.endArray();
+ for (; iter != end; ++iter)
{
- categories.push_back(inv_cat);
+ LLPointer<LLViewerInventoryCategory> inv_cat = new LLViewerInventoryCategory(LLUUID::null);
+ if (inv_cat->importLLSDMap(*iter))
+ {
+ categories.push_back(inv_cat);
+ }
}
}
}
- const LLSD& llsd_items = inventory["items"];
- if (llsd_items.isArray())
{
- LLSD::array_const_iterator iter = llsd_items.beginArray();
- LLSD::array_const_iterator end = llsd_items.endArray();
- for (; iter != end; ++iter)
+ LL_PROFILE_ZONE_NAMED("inventory load from file - items");
+ const LLSD& llsd_items = inventory["items"];
+ if (llsd_items.isArray())
{
- LLPointer<LLViewerInventoryItem> inv_item = new LLViewerInventoryItem;
- if (inv_item->fromLLSD(*iter))
+ size_t items_count = llsd_items.size();
+ items.reserve(items_count);
+ LLSD::array_const_iterator iter = llsd_items.beginArray();
+ LLSD::array_const_iterator end = llsd_items.endArray();
+ for (; iter != end; ++iter)
{
- if (inv_item->getUUID().isNull())
+ LLPointer<LLViewerInventoryItem> inv_item = new LLViewerInventoryItem;
+ if (inv_item->fromLLSD(*iter))
{
- LL_DEBUGS(LOG_INV) << "Ignoring inventory with null item id: "
- << inv_item->getName() << LL_ENDL;
- }
- else
- {
- if (inv_item->getType() == LLAssetType::AT_UNKNOWN)
+ if (inv_item->getUUID().isNull())
{
- cats_to_update.insert(inv_item->getParentUUID());
+ LL_DEBUGS(LOG_INV) << "Ignoring inventory with null item id: " << inv_item->getName() << LL_ENDL;
}
else
{
- items.push_back(inv_item);
+ if (inv_item->getType() == LLAssetType::AT_UNKNOWN)
+ {
+ cats_to_update.insert(inv_item->getParentUUID());
+ }
+ else
+ {
+ items.push_back(inv_item);
+ }
}
}
- }
- // TODO(brad) - figure out how to reenable this without breaking everything else
- // static constexpr U64 BATCH_SIZE = 512U;
- // if ((++lines_count % BATCH_SIZE) == 0)
- // {
- // // SL-19968 - make sure message system code gets a chance to run every so often
- // pump_idle_startup_network();
- // }
+ // TODO(brad) - figure out how to reenable this without breaking everything else
+ // static constexpr U64 BATCH_SIZE = 512U;
+ // if ((++lines_count % BATCH_SIZE) == 0)
+ // {
+ // // SL-19968 - make sure message system code gets a chance to run every so often
+ // pump_idle_startup_network();
+ // }
+ }
}
}
}
@@ -3522,6 +3664,11 @@ bool LLInventoryModel::saveToFile(const std::string& filename,
S32 cat_count = 0;
for (auto& cat : categories)
{
+ if (cat.isNull())
+ {
+ LL_WARNS(LOG_INV) << "Skipping null category during inventory save" << LL_ENDL;
+ continue;
+ }
if (cat->getVersion() != LLViewerInventoryCategory::VERSION_UNKNOWN)
{
LLSD sd;
@@ -3536,6 +3683,11 @@ bool LLInventoryModel::saveToFile(const std::string& filename,
auto it_count = items.size();
for (auto& item : items)
{
+ if (item.isNull())
+ {
+ LL_WARNS(LOG_INV) << "Skipping null item during inventory save" << LL_ENDL;
+ continue;
+ }
LLSD sd;
item->asLLSD(sd);
item_array.append(sd);
@@ -3552,6 +3704,12 @@ bool LLInventoryModel::saveToFile(const std::string& filename,
LL_INFOS(LOG_INV) << "Inventory saved: " << (S32)cat_count << " categories, " << (S32)it_count << " items." << LL_ENDL;
}
+ catch(std::bad_alloc&)
+ {
+ // We are quiting, so just log an error and move on.
+ LL_WARNS(LOG_INV) << "Failed to save inventory to cache due to memory allocation failure." << LL_ENDL;
+ return false;
+ }
catch (...)
{
LOG_UNHANDLED_EXCEPTION("");
@@ -4908,7 +5066,7 @@ bool decompress_file(const char* src_filename, const char* dst_filename)
// open the files
src = gzopen(src_filename, "rb");
if(!src) goto err_decompress;
- dst = LLFile::fopen(dst_filename, "wb");
+ dst = LLFile::fopen(dst_filename, LLFILE_MODE("wb"));
if(!dst) goto err_decompress;
// decompress.