diff options
Diffstat (limited to 'indra/llmessage/llassetstorage.cpp')
-rw-r--r-- | indra/llmessage/llassetstorage.cpp | 330 |
1 files changed, 228 insertions, 102 deletions
diff --git a/indra/llmessage/llassetstorage.cpp b/indra/llmessage/llassetstorage.cpp index 2c8e7ce8a6..b26d412e9f 100644 --- a/indra/llmessage/llassetstorage.cpp +++ b/indra/llmessage/llassetstorage.cpp @@ -2,30 +2,25 @@ * @file llassetstorage.cpp * @brief Implementation of the base asset storage system. * - * $LicenseInfo:firstyear=2001&license=viewergpl$ - * - * Copyright (c) 2001-2007, Linden Research, Inc. - * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code - * The source code in this file ("Source Code") is provided by Linden Lab - * to you under the terms of the GNU General Public License, version 2.0 - * ("GPL"), unless you have obtained a separate licensing agreement - * ("Other License"), formally executed by you and Linden Lab. Terms of - * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlife.com/developers/opensource/gplv2 + * Copyright (C) 2010, 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. * - * There are special exceptions to the terms and conditions of the GPL as - * it is applied to this Source Code. View the full text of the exception - * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at http://secondlife.com/developers/opensource/flossexception + * 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. * - * By copying, modifying or distributing this software, you acknowledge - * that you have read and understood your obligations described above, - * and agree to abide by those obligations. + * 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 * - * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO - * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, - * COMPLETENESS OR PERFORMANCE. + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ @@ -42,6 +37,7 @@ #include "llstring.h" #include "lldir.h" #include "llsd.h" +#include "llframetimer.h" // this library includes #include "message.h" @@ -58,7 +54,13 @@ LLAssetStorage *gAssetStorage = NULL; LLMetrics *LLAssetStorage::metric_recipient = NULL; -const LLUUID CATEGORIZE_LOST_AND_FOUND_ID("00000000-0000-0000-0000-000000000010"); +const LLUUID CATEGORIZE_LOST_AND_FOUND_ID(std::string("00000000-0000-0000-0000-000000000010")); + +const U64 TOXIC_ASSET_LIFETIME = (120 * 1000000); // microseconds + +LLTempAssetStorage::~LLTempAssetStorage() +{ +} ///---------------------------------------------------------------------------- /// LLAssetInfo @@ -133,20 +135,20 @@ void LLAssetInfo::setFromNameValue( const LLNameValue& nv ) str.assign( nv.mName ); pos1 = str.find('|'); buf.assign( str, 0, pos1++ ); - mType = LLAssetType::lookup( buf.c_str() ); + mType = LLAssetType::lookup( buf ); buf.assign( str, pos1, std::string::npos ); - mUuid.set( buf.c_str() ); + mUuid.set( buf ); // convert the value to useful information str.assign( nv.getAsset() ); pos1 = str.find('|'); buf.assign( str, 0, pos1++ ); - mCreatorID.set( buf.c_str() ); + mCreatorID.set( buf ); pos2 = str.find( '|', pos1 ); buf.assign( str, pos1, (pos2++) - pos1 ); - setName( buf.c_str() ); + setName( buf ); buf.assign( str, pos2, std::string::npos ); - setDescription( buf.c_str() ); + setDescription( buf ); llinfos << "uuid: " << mUuid << llendl; llinfos << "creator: " << mCreatorID << llendl; } @@ -275,28 +277,30 @@ LLEstateAssetRequest::~LLEstateAssetRequest() // TODO: rework tempfile handling? -LLAssetStorage::LLAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, LLVFS *vfs, const LLHost &upstream_host) +LLAssetStorage::LLAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, LLVFS *vfs, LLVFS *static_vfs, const LLHost &upstream_host) { - _init(msg, xfer, vfs, upstream_host); + _init(msg, xfer, vfs, static_vfs, upstream_host); } LLAssetStorage::LLAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, - LLVFS *vfs) + LLVFS *vfs, LLVFS *static_vfs) { - _init(msg, xfer, vfs, LLHost::invalid); + _init(msg, xfer, vfs, static_vfs, LLHost::invalid); } void LLAssetStorage::_init(LLMessageSystem *msg, LLXferManager *xfer, LLVFS *vfs, + LLVFS *static_vfs, const LLHost &upstream_host) { mShutDown = FALSE; mMessageSys = msg; mXferManager = xfer; mVFS = vfs; + mStaticVFS = static_vfs; setUpstream(upstream_host); msg->setHandlerFuncFast(_PREHASH_AssetUploadComplete, processUploadComplete, (void **)this); @@ -314,6 +318,9 @@ LLAssetStorage::~LLAssetStorage() // unregister our callbacks with the message system gMessageSystem->setHandlerFuncFast(_PREHASH_AssetUploadComplete, NULL, NULL); } + + // Clear the toxic asset map + mToxicAssetMap.clear(); } void LLAssetStorage::setUpstream(const LLHost &upstream_host) @@ -385,7 +392,33 @@ void LLAssetStorage::_cleanupRequests(BOOL all, S32 error) BOOL LLAssetStorage::hasLocalAsset(const LLUUID &uuid, const LLAssetType::EType type) { - return mVFS->getExists(uuid, type); + return mStaticVFS->getExists(uuid, type) || mVFS->getExists(uuid, type); +} + +bool LLAssetStorage::findInStaticVFSAndInvokeCallback(const LLUUID& uuid, LLAssetType::EType type, + LLGetAssetCallback callback, void *user_data) +{ + BOOL exists = mStaticVFS->getExists(uuid, type); + if (exists) + { + LLVFile file(mStaticVFS, uuid, type); + U32 size = file.getSize(); + if (size > 0) + { + // we've already got the file + if (callback) + { + callback(mStaticVFS, uuid, type, user_data, LL_ERR_NOERR, LL_EXSTAT_VFS_CACHED); + } + return true; + } + else + { + llwarns << "Asset vfile " << uuid << ":" << type + << " found in static cache with bad size " << file.getSize() << ", ignoring" << llendl; + } + } + return false; } /////////////////////////////////////////////////////////////////////////// @@ -393,12 +426,15 @@ BOOL LLAssetStorage::hasLocalAsset(const LLUUID &uuid, const LLAssetType::EType /////////////////////////////////////////////////////////////////////////// // IW - uuid is passed by value to avoid side effects, please don't re-add & -void LLAssetStorage::getAssetData(const LLUUID uuid, LLAssetType::EType type, void (*callback)(LLVFS *vfs, const LLUUID&, LLAssetType::EType, void *, S32, LLExtStat), void *user_data, BOOL is_priority) +void LLAssetStorage::getAssetData(const LLUUID uuid, LLAssetType::EType type, LLGetAssetCallback callback, void *user_data, BOOL is_priority) { lldebugs << "LLAssetStorage::getAssetData() - " << uuid << "," << LLAssetType::lookup(type) << llendl; + llinfos << "ASSET_TRACE requesting " << uuid << " type " << LLAssetType::lookup(type) << llendl; + if (mShutDown) { + llinfos << "ASSET_TRACE cancelled " << uuid << " type " << LLAssetType::lookup(type) << " shutting down" << llendl; return; // don't get the asset or do any callbacks, we are shutting down } @@ -412,11 +448,30 @@ void LLAssetStorage::getAssetData(const LLUUID uuid, LLAssetType::EType type, vo return; } + // Try static VFS first. + if (findInStaticVFSAndInvokeCallback(uuid,type,callback,user_data)) + { + llinfos << "ASSET_TRACE asset " << uuid << " found in static VFS" << llendl; + return; + } + BOOL exists = mVFS->getExists(uuid, type); LLVFile file(mVFS, uuid, type); U32 size = exists ? file.getSize() : 0; - if (size < 1) + if (size > 0) + { + // we've already got the file + // theoretically, partial files w/o a pending request shouldn't happen + // unless there's a weird error + if (callback) + { + callback(mVFS, uuid, type, user_data, LL_ERR_NOERR, LL_EXSTAT_VFS_CACHED); + } + + llinfos << "ASSET_TRACE asset " << uuid << " found in VFS" << llendl; + } + else { if (exists) { @@ -455,16 +510,7 @@ void LLAssetStorage::getAssetData(const LLUUID uuid, LLAssetType::EType type, vo // This can be overridden by subclasses _queueDataRequest(uuid, type, callback, user_data, duplicate, is_priority); } - else - { - // we've already got the file - // theoretically, partial files w/o a pending request shouldn't happen - // unless there's a weird error - if (callback) - { - callback(mVFS, uuid, type, user_data, LL_ERR_NOERR, LL_EXSTAT_VFS_CACHED); - } - } + } void LLAssetStorage::_queueDataRequest(const LLUUID& uuid, LLAssetType::EType atype, @@ -517,6 +563,8 @@ void LLAssetStorage::downloadCompleteCallback( LLAssetType::EType file_type, void* user_data, LLExtStat ext_status) { + llinfos << "ASSET_TRACE asset " << file_id << " downloadCompleteCallback" << llendl; + lldebugs << "LLAssetStorage::downloadCompleteCallback() for " << file_id << "," << LLAssetType::lookup(file_type) << llendl; LLAssetRequest* req = (LLAssetRequest*)user_data; @@ -532,8 +580,19 @@ void LLAssetStorage::downloadCompleteCallback( return; } - req->setUUID(file_id); - req->setType(file_type); + // Inefficient since we're doing a find through a list that may have thousands of elements. + // This is due for refactoring; we will probably change mPendingDownloads into a set. + request_list_t::iterator download_iter = std::find(gAssetStorage->mPendingDownloads.begin(), + gAssetStorage->mPendingDownloads.end(), + req); + // If the LLAssetRequest doesn't exist in the downloads queue, then it either has already been deleted + // by _cleanupRequests, or it's a transfer. + if (download_iter != gAssetStorage->mPendingDownloads.end()) + { + req->setUUID(file_id); + req->setType(file_type); + } + if (LL_ERR_NOERR == result) { // we might have gotten a zero-size file @@ -556,7 +615,7 @@ void LLAssetStorage::downloadCompleteCallback( { request_list_t::iterator curiter = iter++; LLAssetRequest* tmp = *curiter; - if ((tmp->getUUID() == req->getUUID()) && (tmp->getType()== req->getType())) + if ((tmp->getUUID() == file_id) && (tmp->getType()== file_type)) { requests.push_front(tmp); iter = gAssetStorage->mPendingDownloads.erase(curiter); @@ -594,11 +653,27 @@ void LLAssetStorage::getEstateAsset(const LLHost &object_sim, const LLUUID &agen return; } + // Try static VFS first. + if (findInStaticVFSAndInvokeCallback(asset_id,atype,callback,user_data)) + { + return; + } + BOOL exists = mVFS->getExists(asset_id, atype); LLVFile file(mVFS, asset_id, atype); U32 size = exists ? file.getSize() : 0; - if (size < 1) + if (size > 0) + { + // we've already got the file + // theoretically, partial files w/o a pending request shouldn't happen + // unless there's a weird error + if (callback) + { + callback(mVFS, asset_id, atype, user_data, LL_ERR_NOERR, LL_EXSTAT_VFS_CACHED); + } + } + else { if (exists) { @@ -649,16 +724,6 @@ void LLAssetStorage::getEstateAsset(const LLHost &object_sim, const LLUUID &agen } } } - else - { - // we've already got the file - // theoretically, partial files w/o a pending request shouldn't happen - // unless there's a weird error - if (callback) - { - callback(mVFS, asset_id, atype, user_data, LL_ERR_NOERR, LL_EXSTAT_VFS_CACHED); - } - } } void LLAssetStorage::downloadEstateAssetCompleteCallback( @@ -725,6 +790,12 @@ void LLAssetStorage::getInvItemAsset(const LLHost &object_sim, const LLUUID &age if(asset_id.notNull()) { + // Try static VFS first. + if (findInStaticVFSAndInvokeCallback( asset_id, atype, callback, user_data)) + { + return; + } + exists = mVFS->getExists(asset_id, atype); LLVFile file(mVFS, asset_id, atype); size = exists ? file.getSize() : 0; @@ -736,7 +807,17 @@ void LLAssetStorage::getInvItemAsset(const LLHost &object_sim, const LLUUID &age } - if (size < 1) + if (size > 0) + { + // we've already got the file + // theoretically, partial files w/o a pending request shouldn't happen + // unless there's a weird error + if (callback) + { + callback(mVFS, asset_id, atype, user_data, LL_ERR_NOERR, LL_EXSTAT_VFS_CACHED); + } + } + else { // See whether we should talk to the object's originating sim, // or the upstream provider. @@ -785,16 +866,6 @@ void LLAssetStorage::getInvItemAsset(const LLHost &object_sim, const LLUUID &age } } } - else - { - // we've already got the file - // theoretically, partial files w/o a pending request shouldn't happen - // unless there's a weird error - if (callback) - { - callback(mVFS, asset_id, atype, user_data, LL_ERR_NOERR, LL_EXSTAT_VFS_CACHED); - } - } } @@ -1006,12 +1077,12 @@ LLSD LLAssetStorage::getPendingDetails(LLAssetStorage::ERequestType rt, { const request_list_t* requests = getRequestList(rt); LLSD sd; - sd["requests"] = getPendingDetails(requests, asset_type, detail_prefix); + sd["requests"] = getPendingDetailsImpl(requests, asset_type, detail_prefix); return sd; } // virtual -LLSD LLAssetStorage::getPendingDetails(const LLAssetStorage::request_list_t* requests, +LLSD LLAssetStorage::getPendingDetailsImpl(const LLAssetStorage::request_list_t* requests, LLAssetType::EType asset_type, const std::string& detail_prefix) const { @@ -1094,11 +1165,11 @@ LLSD LLAssetStorage::getPendingRequest(LLAssetStorage::ERequestType rt, const LLUUID& asset_id) const { const request_list_t* requests = getRequestList(rt); - return getPendingRequest(requests, asset_type, asset_id); + return getPendingRequestImpl(requests, asset_type, asset_id); } // virtual -LLSD LLAssetStorage::getPendingRequest(const LLAssetStorage::request_list_t* requests, +LLSD LLAssetStorage::getPendingRequestImpl(const LLAssetStorage::request_list_t* requests, LLAssetType::EType asset_type, const LLUUID& asset_id) const { @@ -1117,7 +1188,7 @@ bool LLAssetStorage::deletePendingRequest(LLAssetStorage::ERequestType rt, const LLUUID& asset_id) { request_list_t* requests = getRequestList(rt); - if (deletePendingRequest(requests, asset_type, asset_id)) + if (deletePendingRequestImpl(requests, asset_type, asset_id)) { llinfos << "Asset " << getRequestName(rt) << " request for " << asset_id << "." << LLAssetType::lookup(asset_type) @@ -1128,7 +1199,7 @@ bool LLAssetStorage::deletePendingRequest(LLAssetStorage::ERequestType rt, } // virtual -bool LLAssetStorage::deletePendingRequest(LLAssetStorage::request_list_t* requests, +bool LLAssetStorage::deletePendingRequestImpl(LLAssetStorage::request_list_t* requests, LLAssetType::EType asset_type, const LLUUID& asset_id) { @@ -1191,6 +1262,9 @@ const char* LLAssetStorage::getErrorString(S32 status) case LL_ERR_CIRCUIT_GONE: return "Circuit gone"; + case LL_ERR_PRICE_MISMATCH: + return "Viewer and server do not agree on price"; + default: return "Unknown status"; } @@ -1231,16 +1305,20 @@ void LLAssetStorage::getAssetData(const LLUUID uuid, LLAssetType::EType type, vo void LLAssetStorage::legacyGetDataCallback(LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType type, void *user_data, S32 status, LLExtStat ext_status) { LLLegacyAssetRequest *legacy = (LLLegacyAssetRequest *)user_data; - char filename[LL_MAX_PATH] = ""; /* Flawfinder: ignore */ + std::string filename; - if (! status) + // Check if the asset is marked toxic, and don't load bad stuff + BOOL toxic = gAssetStorage->isAssetToxic( uuid ); + + if ( !status + && !toxic ) { LLVFile file(vfs, uuid, type); - char uuid_str[UUID_STR_LENGTH]; /* Flawfinder: ignore */ + std::string uuid_str; uuid.toString(uuid_str); - snprintf(filename,sizeof(filename),"%s.%s",gDirUtilp->getExpandedFilename(LL_PATH_CACHE,uuid_str).c_str(),LLAssetType::lookup(type)); /* Flawfinder: ignore */ + filename = llformat("%s.%s",gDirUtilp->getExpandedFilename(LL_PATH_CACHE,uuid_str).c_str(),LLAssetType::lookup(type)); LLFILE* fp = LLFile::fopen(filename, "wb"); /* Flawfinder: ignore */ if (fp) @@ -1264,7 +1342,7 @@ void LLAssetStorage::legacyGetDataCallback(LLVFS *vfs, const LLUUID &uuid, LLAss } } - legacy->mDownCallback(filename, uuid, legacy->mUserData, status, ext_status); + legacy->mDownCallback(filename.c_str(), uuid, legacy->mUserData, status, ext_status); delete legacy; } @@ -1283,7 +1361,7 @@ void LLAssetStorage::storeAssetData( { llwarns << "storeAssetData: wrong version called" << llendl; // LLAssetStorage metric: Virtual base call - reportMetric( LLUUID::null, asset_type, NULL, LLUUID::null, 0, MR_BAD_FUNCTION, __FILE__, __LINE__, "Illegal call to base: LLAssetStorage::storeAssetData 1" ); + reportMetric( LLUUID::null, asset_type, LLStringUtil::null, LLUUID::null, 0, MR_BAD_FUNCTION, __FILE__, __LINE__, "Illegal call to base: LLAssetStorage::storeAssetData 1" ); } // virtual @@ -1302,13 +1380,13 @@ void LLAssetStorage::storeAssetData( { llwarns << "storeAssetData: wrong version called" << llendl; // LLAssetStorage metric: Virtual base call - reportMetric( asset_id, asset_type, NULL, requesting_agent_id, 0, MR_BAD_FUNCTION, __FILE__, __LINE__, "Illegal call to base: LLAssetStorage::storeAssetData 2" ); + reportMetric( asset_id, asset_type, LLStringUtil::null, requesting_agent_id, 0, MR_BAD_FUNCTION, __FILE__, __LINE__, "Illegal call to base: LLAssetStorage::storeAssetData 2" ); } // virtual // this does nothing, viewer and sim both override this. void LLAssetStorage::storeAssetData( - const char* filename, + const std::string& filename, const LLUUID& asset_id, LLAssetType::EType asset_type, LLStoreAssetCallback callback, @@ -1320,13 +1398,13 @@ void LLAssetStorage::storeAssetData( { llwarns << "storeAssetData: wrong version called" << llendl; // LLAssetStorage metric: Virtual base call - reportMetric( asset_id, asset_type, NULL, LLUUID::null, 0, MR_BAD_FUNCTION, __FILE__, __LINE__, "Illegal call to base: LLAssetStorage::storeAssetData 3" ); + reportMetric( asset_id, asset_type, LLStringUtil::null, LLUUID::null, 0, MR_BAD_FUNCTION, __FILE__, __LINE__, "Illegal call to base: LLAssetStorage::storeAssetData 3" ); } // virtual // this does nothing, viewer and sim both override this. void LLAssetStorage::storeAssetData( - const char* filename, + const std::string& filename, const LLTransactionID &transactoin_id, LLAssetType::EType asset_type, LLStoreAssetCallback callback, @@ -1338,7 +1416,7 @@ void LLAssetStorage::storeAssetData( { llwarns << "storeAssetData: wrong version called" << llendl; // LLAssetStorage metric: Virtual base call - reportMetric( LLUUID::null, asset_type, NULL, LLUUID::null, 0, MR_BAD_FUNCTION, __FILE__, __LINE__, "Illegal call to base: LLAssetStorage::storeAssetData 4" ); + reportMetric( LLUUID::null, asset_type, LLStringUtil::null, LLUUID::null, 0, MR_BAD_FUNCTION, __FILE__, __LINE__, "Illegal call to base: LLAssetStorage::storeAssetData 4" ); } // static @@ -1385,9 +1463,9 @@ void LLAssetStorage::clearTempAssetData() { } // static -void LLAssetStorage::reportMetric( const LLUUID& asset_id, const LLAssetType::EType asset_type, const char *filename, +void LLAssetStorage::reportMetric( const LLUUID& asset_id, const LLAssetType::EType asset_type, const std::string& in_filename, const LLUUID& agent_id, S32 asset_size, EMetricResult result, - const char *file, const S32 line, const char *message ) + const char *file, const S32 line, const std::string& in_message ) { if( !metric_recipient ) { @@ -1395,18 +1473,13 @@ void LLAssetStorage::reportMetric( const LLUUID& asset_id, const LLAssetType::ET return; } - filename = filename ? filename : ""; - file = file ? file : ""; - - // Create revised message - message = "message :: file:line" - std::string new_message; //( message ); - new_message = message; // << " " << file << " " << line; - new_message += " :: "; - new_message += filename; - char line_string[16]; - sprintf( line_string, ":%d", line ); - new_message += line_string; - message = new_message.c_str(); + std::string filename(in_filename); + if (filename.empty()) + filename = ll_safe_string(file); + + // Create revised message - new_message = "in_message :: file:line" + std::stringstream new_message; + new_message << in_message << " :: " << filename << ":" << line; // Change always_report to true if debugging... do not check it in this way static bool always_report = false; @@ -1419,15 +1492,68 @@ void LLAssetStorage::reportMetric( const LLUUID& asset_id, const LLAssetType::ET LLSD stats; stats["asset_id"] = asset_id; stats["asset_type"] = asset_type; - stats["filename"] = filename? filename : ""; + stats["filename"] = filename; stats["agent_id"] = agent_id; stats["asset_size"] = (S32)asset_size; stats["result"] = (S32)result; - metric_recipient->recordEventDetails( metric_name, message, success, stats); + metric_recipient->recordEventDetails( metric_name, new_message.str(), success, stats); } else { - metric_recipient->recordEvent(metric_name, message, success); + metric_recipient->recordEvent(metric_name, new_message.str(), success); } } + + +// Check if an asset is in the toxic map. If it is, the entry is updated +BOOL LLAssetStorage::isAssetToxic( const LLUUID& uuid ) +{ + BOOL is_toxic = FALSE; + + if ( !uuid.isNull() ) + { + toxic_asset_map_t::iterator iter = mToxicAssetMap.find( uuid ); + if ( iter != mToxicAssetMap.end() ) + { // Found toxic asset + (*iter).second = LLFrameTimer::getTotalTime() + TOXIC_ASSET_LIFETIME; + is_toxic = TRUE; + } + } + return is_toxic; +} + + + + +// Clean the toxic asset list, remove old entries +void LLAssetStorage::flushOldToxicAssets( BOOL force_it ) +{ + // Scan and look for old entries + U64 now = LLFrameTimer::getTotalTime(); + toxic_asset_map_t::iterator iter = mToxicAssetMap.begin(); + while ( iter != mToxicAssetMap.end() ) + { + if ( force_it + || (*iter).second < now ) + { // Too old - remove it + mToxicAssetMap.erase( iter++ ); + } + else + { + iter++; + } + } +} + + +// Add an item to the toxic asset map +void LLAssetStorage::markAssetToxic( const LLUUID& uuid ) +{ + if ( !uuid.isNull() ) + { + // Set the value to the current time. Creates a new entry if needed + mToxicAssetMap[ uuid ] = LLFrameTimer::getTotalTime() + TOXIC_ASSET_LIFETIME; + } +} + |