From b6841d75c2f259c84d5ab6b012bd2ae37d985451 Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Fri, 15 Apr 2022 19:02:07 -0500 Subject: SL-17219 WIP - Texture pipeline overhaul --- indra/llcommon/llapr.cpp | 2 + indra/llcommon/llqueuedthread.cpp | 289 +++++++++++++++++++------------------- indra/llcommon/llqueuedthread.h | 51 ++----- indra/llcommon/lltimer.cpp | 3 +- indra/llcommon/llworkerthread.cpp | 27 ++-- indra/llcommon/llworkerthread.h | 11 +- indra/llcommon/workqueue.h | 8 +- 7 files changed, 177 insertions(+), 214 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llapr.cpp b/indra/llcommon/llapr.cpp index db94765871..b537102510 100644 --- a/indra/llcommon/llapr.cpp +++ b/indra/llcommon/llapr.cpp @@ -526,6 +526,7 @@ S32 LLAPRFile::seek(apr_file_t* file_handle, apr_seek_where_t where, S32 offset) //static S32 LLAPRFile::readEx(const std::string& filename, void *buf, S32 offset, S32 nbytes, LLVolatileAPRPool* pool) { + LL_PROFILE_ZONE_SCOPED; //***************************************** LLAPRFilePoolScope scope(pool); apr_file_t* file_handle = open(filename, scope.getVolatileAPRPool(), APR_READ|APR_BINARY); @@ -570,6 +571,7 @@ S32 LLAPRFile::readEx(const std::string& filename, void *buf, S32 offset, S32 nb //static S32 LLAPRFile::writeEx(const std::string& filename, void *buf, S32 offset, S32 nbytes, LLVolatileAPRPool* pool) { + LL_PROFILE_ZONE_SCOPED; apr_int32_t flags = APR_CREATE|APR_WRITE|APR_BINARY; if (offset < 0) { diff --git a/indra/llcommon/llqueuedthread.cpp b/indra/llcommon/llqueuedthread.cpp index 8cef4293cd..871c42f7ee 100644 --- a/indra/llcommon/llqueuedthread.cpp +++ b/indra/llcommon/llqueuedthread.cpp @@ -26,20 +26,25 @@ #include "linden_common.h" #include "llqueuedthread.h" +#include + #include "llstl.h" #include "lltimer.h" // ms_sleep() -#include "lltracethreadrecorder.h" +#include "llmutex.h" //============================================================================ // MAIN THREAD LLQueuedThread::LLQueuedThread(const std::string& name, bool threaded, bool should_pause) : - LLThread(name), - mThreaded(threaded), - mIdleThread(TRUE), - mNextHandle(0), - mStarted(FALSE) + LLThread(name), + mThreaded(threaded), + mIdleThread(TRUE), + mNextHandle(0), + mStarted(FALSE), + mRequestQueue(name, 1024 * 1024) { + mMainQueue = LL::WorkQueue::getInstance("mainloop"); + if (mThreaded) { if(should_pause) @@ -104,6 +109,8 @@ void LLQueuedThread::shutdown() { LL_WARNS() << "~LLQueuedThread() called with active requests: " << active_count << LL_ENDL; } + + mRequestQueue.close(); } //---------------------------------------------------------------------------- @@ -112,6 +119,7 @@ void LLQueuedThread::shutdown() // virtual S32 LLQueuedThread::update(F32 max_time_ms) { + LL_PROFILE_ZONE_SCOPED; if (!mStarted) { if (!mThreaded) @@ -125,29 +133,30 @@ S32 LLQueuedThread::update(F32 max_time_ms) S32 LLQueuedThread::updateQueue(F32 max_time_ms) { - F64 max_time = (F64)max_time_ms * .001; - LLTimer timer; - S32 pending = 1; - + LL_PROFILE_ZONE_SCOPED; // Frame Update if (mThreaded) { - pending = getPending(); - if(pending > 0) + // schedule a call to threadedUpdate for every call to updateQueue + mRequestQueue.post([=]() + { + LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qt - update"); + mIdleThread = FALSE; + threadedUpdate(); + mIdleThread = TRUE; + } + ); + if(getPending() > 0) { - unpause(); - } + unpause(); + } } else { - while (pending > 0) - { - pending = processNextRequest(); - if (max_time && timer.getElapsedTimeF64() > max_time) - break; - } + mRequestQueue.runFor(std::chrono::microseconds((int) (max_time_ms*1000.f))); + threadedUpdate(); } - return pending; + return getPending(); } void LLQueuedThread::incQueue() @@ -166,11 +175,7 @@ void LLQueuedThread::incQueue() // May be called from any thread S32 LLQueuedThread::getPending() { - S32 res; - lockData(); - res = mRequestQueue.size(); - unlockData(); - return res; + return mRequestQueue.size(); } // MAIN thread @@ -195,35 +200,28 @@ void LLQueuedThread::waitOnPending() // MAIN thread void LLQueuedThread::printQueueStats() { - lockData(); - if (!mRequestQueue.empty()) + U32 size = mRequestQueue.size(); + if (size > 0) { - QueuedRequest *req = *mRequestQueue.begin(); - LL_INFOS() << llformat("Pending Requests:%d Current status:%d", mRequestQueue.size(), req->getStatus()) << LL_ENDL; + LL_INFOS() << llformat("Pending Requests:%d ", mRequestQueue.size()) << LL_ENDL; } else { LL_INFOS() << "Queued Thread Idle" << LL_ENDL; } - unlockData(); } // MAIN thread LLQueuedThread::handle_t LLQueuedThread::generateHandle() { - lockData(); - while ((mNextHandle == nullHandle()) || (mRequestHash.find(mNextHandle))) - { - mNextHandle++; - } - const LLQueuedThread::handle_t res = mNextHandle++; - unlockData(); + U32 res = ++mNextHandle; return res; } // MAIN thread bool LLQueuedThread::addRequest(QueuedRequest* req) { + LL_PROFILE_ZONE_SCOPED; if (mStatus == QUITTING) { return false; @@ -231,14 +229,14 @@ bool LLQueuedThread::addRequest(QueuedRequest* req) lockData(); req->setStatus(STATUS_QUEUED); - mRequestQueue.insert(req); - mRequestHash.insert(req); + mRequestHash.insert(req); #if _DEBUG // LL_INFOS() << llformat("LLQueuedThread::Added req [%08d]",handle) << LL_ENDL; #endif unlockData(); - incQueue(); + llassert(!mDataLock->isSelfLocked()); + mRequestQueue.post([this, req]() { processRequest(req); }); return true; } @@ -246,6 +244,7 @@ bool LLQueuedThread::addRequest(QueuedRequest* req) // MAIN thread bool LLQueuedThread::waitForResult(LLQueuedThread::handle_t handle, bool auto_complete) { + LL_PROFILE_ZONE_SCOPED; llassert (handle != nullHandle()); bool res = false; bool waspaused = isPaused(); @@ -312,6 +311,7 @@ LLQueuedThread::status_t LLQueuedThread::getRequestStatus(handle_t handle) void LLQueuedThread::abortRequest(handle_t handle, bool autocomplete) { + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; lockData(); QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle); if (req) @@ -333,30 +333,9 @@ void LLQueuedThread::setFlags(handle_t handle, U32 flags) unlockData(); } -void LLQueuedThread::setPriority(handle_t handle, U32 priority) -{ - lockData(); - QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle); - if (req) - { - if(req->getStatus() == STATUS_INPROGRESS) - { - // not in list - req->setPriority(priority); - } - else if(req->getStatus() == STATUS_QUEUED) - { - // remove from list then re-insert - llverify(mRequestQueue.erase(req) == 1); - req->setPriority(priority); - mRequestQueue.insert(req); - } - } - unlockData(); -} - bool LLQueuedThread::completeRequest(handle_t handle) { + LL_PROFILE_ZONE_SCOPED; bool res = false; lockData(); QueuedRequest* req = (QueuedRequest*)mRequestHash.find(handle); @@ -399,89 +378,115 @@ bool LLQueuedThread::check() //============================================================================ // Runs on its OWN thread -S32 LLQueuedThread::processNextRequest() +void LLQueuedThread::processRequest(LLQueuedThread::QueuedRequest* req) { - QueuedRequest *req; + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; + + mIdleThread = FALSE; + //threadedUpdate(); + // Get next request from pool lockData(); - while(1) - { - req = NULL; - if (mRequestQueue.empty()) - { - break; - } - req = *mRequestQueue.begin(); - mRequestQueue.erase(mRequestQueue.begin()); - if ((req->getFlags() & FLAG_ABORT) || (mStatus == QUITTING)) - { - req->setStatus(STATUS_ABORTED); - req->finishRequest(false); - if (req->getFlags() & FLAG_AUTO_COMPLETE) - { - mRequestHash.erase(req); - req->deleteRequest(); -// check(); - } - continue; - } - llassert_always(req->getStatus() == STATUS_QUEUED); - break; - } - U32 start_priority = 0 ; - if (req) - { - req->setStatus(STATUS_INPROGRESS); - start_priority = req->getPriority(); - } - unlockData(); - - // This is the only place we will call req->setStatus() after - // it has initially been seet to STATUS_QUEUED, so it is - // safe to access req. - if (req) + if ((req->getFlags() & FLAG_ABORT) || (mStatus == QUITTING)) { - // process request - bool complete = req->processRequest(); - - if (complete) + LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qtpr - abort"); + req->setStatus(STATUS_ABORTED); + req->finishRequest(false); + if (req->getFlags() & FLAG_AUTO_COMPLETE) { - lockData(); - req->setStatus(STATUS_COMPLETE); - req->finishRequest(true); - if (req->getFlags() & FLAG_AUTO_COMPLETE) - { - mRequestHash.erase(req); - req->deleteRequest(); + mRequestHash.erase(req); + req->deleteRequest(); // check(); - } - unlockData(); } - else - { - lockData(); - req->setStatus(STATUS_QUEUED); - mRequestQueue.insert(req); - unlockData(); - if (mThreaded && start_priority < PRIORITY_NORMAL) - { - ms_sleep(1); // sleep the thread a little - } - } - - LLTrace::get_thread_recorder()->pushToParent(); + unlockData(); } + else + { + llassert_always(req->getStatus() == STATUS_QUEUED); + + if (req) + { + req->setStatus(STATUS_INPROGRESS); + } + unlockData(); + + // This is the only place we will call req->setStatus() after + // it has initially been seet to STATUS_QUEUED, so it is + // safe to access req. + if (req) + { + // process request + bool complete = req->processRequest(); + + if (complete) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qtpr - complete"); + lockData(); + req->setStatus(STATUS_COMPLETE); + req->finishRequest(true); + if (req->getFlags() & FLAG_AUTO_COMPLETE) + { + mRequestHash.erase(req); + req->deleteRequest(); + // check(); + } + unlockData(); + } + else + { + LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qtpr - retry"); + //put back on queue and try again in 0.1ms + lockData(); + req->setStatus(STATUS_QUEUED); + + unlockData(); + + llassert(!mDataLock->isSelfLocked()); + +#if 0 + // try again on next frame + // NOTE: tried using "post" with a time in the future, but this + // would invariably cause this thread to wait for a long time (10+ ms) + // while work is pending + bool ret = LL::WorkQueue::postMaybe( + mMainQueue, + [=]() + { + LL_PROFILE_ZONE_NAMED("processRequest - retry"); + mRequestQueue.post([=]() + { + LL_PROFILE_ZONE_NAMED("processRequest - retry"); // <-- not redundant, track retry on both queues + processRequest(req); + }); + }); + llassert(ret); +#else + using namespace std::chrono_literals; + auto retry_time = LL::WorkQueue::TimePoint::clock::now() + 16ms; + mRequestQueue.post([=] + { + LL_PROFILE_ZONE_NAMED("processRequest - retry"); + while (LL::WorkQueue::TimePoint::clock::now() < retry_time) + { + std::this_thread::yield(); //note: don't use LLThread::yield here to avoid + } + processRequest(req); + }); +#endif + + } + } + } - S32 pending = getPending(); - return pending; + mIdleThread = TRUE; } // virtual bool LLQueuedThread::runCondition() { // mRunCondition must be locked here - if (mRequestQueue.empty() && mIdleThread) + if (mRequestQueue.size() == 0 && mIdleThread) return false; else return true; @@ -495,18 +500,13 @@ void LLQueuedThread::run() startThread(); mStarted = TRUE; - while (1) + + /*while (1) { + LL_PROFILE_ZONE_SCOPED; // this will block on the condition until runCondition() returns true, the thread is unpaused, or the thread leaves the RUNNING state. checkPause(); - if (isQuitting()) - { - LLTrace::get_thread_recorder()->pushToParent(); - endThread(); - break; - } - mIdleThread = FALSE; threadedUpdate(); @@ -515,12 +515,18 @@ void LLQueuedThread::run() if (pending_work == 0) { + //LL_PROFILE_ZONE_NAMED("LLQueuedThread - sleep"); mIdleThread = TRUE; - ms_sleep(1); + //ms_sleep(1); } //LLThread::yield(); // thread should yield after each request - } + }*/ + mRequestQueue.runUntilClose(); + + endThread(); LL_INFOS() << "LLQueuedThread " << mName << " EXITING." << LL_ENDL; + + } // virtual @@ -540,10 +546,9 @@ void LLQueuedThread::threadedUpdate() //============================================================================ -LLQueuedThread::QueuedRequest::QueuedRequest(LLQueuedThread::handle_t handle, U32 priority, U32 flags) : +LLQueuedThread::QueuedRequest::QueuedRequest(LLQueuedThread::handle_t handle, U32 flags) : LLSimpleHashEntry(handle), mStatus(STATUS_UNKNOWN), - mPriority(priority), mFlags(flags) { } diff --git a/indra/llcommon/llqueuedthread.h b/indra/llcommon/llqueuedthread.h index 5d3f873646..6b82ccc434 100644 --- a/indra/llcommon/llqueuedthread.h +++ b/indra/llcommon/llqueuedthread.h @@ -36,6 +36,7 @@ #include "llthread.h" #include "llsimplehash.h" +#include "workqueue.h" //============================================================================ // Note: ~LLQueuedThread is O(N) N=# of queued threads, assumed to be small @@ -45,15 +46,6 @@ class LL_COMMON_API LLQueuedThread : public LLThread { //------------------------------------------------------------------------ public: - enum priority_t { - PRIORITY_IMMEDIATE = 0x7FFFFFFF, - PRIORITY_URGENT = 0x40000000, - PRIORITY_HIGH = 0x30000000, - PRIORITY_NORMAL = 0x20000000, - PRIORITY_LOW = 0x10000000, - PRIORITY_LOWBITS = 0x0FFFFFFF, - PRIORITY_HIGHBITS = 0x70000000 - }; enum status_t { STATUS_EXPIRED = -1, STATUS_UNKNOWN = 0, @@ -82,28 +74,17 @@ public: virtual ~QueuedRequest(); // use deleteRequest() public: - QueuedRequest(handle_t handle, U32 priority, U32 flags = 0); + QueuedRequest(handle_t handle, U32 flags = 0); status_t getStatus() { return mStatus; } - U32 getPriority() const - { - return mPriority; - } U32 getFlags() const { return mFlags; } - bool higherPriority(const QueuedRequest& second) const - { - if ( mPriority == second.mPriority) - return mHashKey < second.mHashKey; - else - return mPriority > second.mPriority; - } - + protected: status_t setStatus(status_t newstatus) { @@ -121,28 +102,11 @@ public: virtual void finishRequest(bool completed); // Always called from thread after request has completed or aborted virtual void deleteRequest(); // Only method to delete a request - void setPriority(U32 pri) - { - // Only do this on a request that is not in a queued list! - mPriority = pri; - }; - protected: LLAtomicBase mStatus; - U32 mPriority; U32 mFlags; }; -protected: - struct queued_request_less - { - bool operator()(const QueuedRequest* lhs, const QueuedRequest* rhs) const - { - return lhs->higherPriority(*rhs); // higher priority in front of queue (set) - } - }; - - //------------------------------------------------------------------------ public: @@ -167,7 +131,7 @@ private: protected: handle_t generateHandle(); bool addRequest(QueuedRequest* req); - S32 processNextRequest(void); + void processRequest(QueuedRequest* req); void incQueue(); public: @@ -186,7 +150,6 @@ public: status_t getRequestStatus(handle_t handle); void abortRequest(handle_t handle, bool autocomplete); void setFlags(handle_t handle, U32 flags); - void setPriority(handle_t handle, U32 priority); bool completeRequest(handle_t handle); // This is public for support classes like LLWorkerThread, // but generally the methods above should be used. @@ -200,8 +163,10 @@ protected: BOOL mStarted; // required when mThreaded is false to call startThread() from update() LLAtomicBool mIdleThread; // request queue is empty (or we are quitting) and the thread is idle - typedef std::set request_queue_t; - request_queue_t mRequestQueue; + //typedef std::set request_queue_t; + //request_queue_t mRequestQueue; + LL::WorkQueue mRequestQueue; + LL::WorkQueue::weak_t mMainQueue; enum { REQUEST_HASH_SIZE = 512 }; // must be power of 2 typedef LLSimpleHash request_hash_t; diff --git a/indra/llcommon/lltimer.cpp b/indra/llcommon/lltimer.cpp index aaa6df325c..b250bc3e1c 100644 --- a/indra/llcommon/lltimer.cpp +++ b/indra/llcommon/lltimer.cpp @@ -64,7 +64,8 @@ LLTimer* LLTimer::sTimer = NULL; #if LL_WINDOWS void ms_sleep(U32 ms) { - Sleep(ms); + LL_PROFILE_ZONE_SCOPED; + std::this_thread::sleep_for(std::chrono::microseconds(ms)); } U32 micro_sleep(U64 us, U32 max_yields) diff --git a/indra/llcommon/llworkerthread.cpp b/indra/llcommon/llworkerthread.cpp index 4b91b2caca..02ce4823b8 100644 --- a/indra/llcommon/llworkerthread.cpp +++ b/indra/llcommon/llworkerthread.cpp @@ -133,11 +133,11 @@ S32 LLWorkerThread::update(F32 max_time_ms) //---------------------------------------------------------------------------- -LLWorkerThread::handle_t LLWorkerThread::addWorkRequest(LLWorkerClass* workerclass, S32 param, U32 priority) +LLWorkerThread::handle_t LLWorkerThread::addWorkRequest(LLWorkerClass* workerclass, S32 param) { handle_t handle = generateHandle(); - WorkRequest* req = new WorkRequest(handle, priority, workerclass, param); + WorkRequest* req = new WorkRequest(handle, workerclass, param); bool res = addRequest(req); if (!res) @@ -160,8 +160,8 @@ void LLWorkerThread::deleteWorker(LLWorkerClass* workerclass) //============================================================================ // Runs on its OWN thread -LLWorkerThread::WorkRequest::WorkRequest(handle_t handle, U32 priority, LLWorkerClass* workerclass, S32 param) : - LLQueuedThread::QueuedRequest(handle, priority), +LLWorkerThread::WorkRequest::WorkRequest(handle_t handle, LLWorkerClass* workerclass, S32 param) : + LLQueuedThread::QueuedRequest(handle), mWorkerClass(workerclass), mParam(param) { @@ -180,6 +180,7 @@ void LLWorkerThread::WorkRequest::deleteRequest() // virtual bool LLWorkerThread::WorkRequest::processRequest() { + LL_PROFILE_ZONE_SCOPED; LLWorkerClass* workerclass = getWorkerClass(); workerclass->setWorking(true); bool complete = workerclass->doWork(getParam()); @@ -190,6 +191,7 @@ bool LLWorkerThread::WorkRequest::processRequest() // virtual void LLWorkerThread::WorkRequest::finishRequest(bool completed) { + LL_PROFILE_ZONE_SCOPED; LLWorkerClass* workerclass = getWorkerClass(); workerclass->finishWork(getParam(), completed); U32 flags = LLWorkerClass::WCF_WORK_FINISHED | (completed ? 0 : LLWorkerClass::WCF_WORK_ABORTED); @@ -203,7 +205,6 @@ LLWorkerClass::LLWorkerClass(LLWorkerThread* workerthread, const std::string& na : mWorkerThread(workerthread), mWorkerClassName(name), mRequestHandle(LLWorkerThread::nullHandle()), - mRequestPriority(LLWorkerThread::PRIORITY_NORMAL), mMutex(), mWorkFlags(0) { @@ -292,7 +293,7 @@ bool LLWorkerClass::yield() //---------------------------------------------------------------------------- // calls startWork, adds doWork() to queue -void LLWorkerClass::addWork(S32 param, U32 priority) +void LLWorkerClass::addWork(S32 param) { mMutex.lock(); llassert_always(!(mWorkFlags & (WCF_WORKING|WCF_HAVE_WORK))); @@ -306,7 +307,7 @@ void LLWorkerClass::addWork(S32 param, U32 priority) startWork(param); clearFlags(WCF_WORK_FINISHED|WCF_WORK_ABORTED); setFlags(WCF_HAVE_WORK); - mRequestHandle = mWorkerThread->addWorkRequest(this, param, priority); + mRequestHandle = mWorkerThread->addWorkRequest(this, param); mMutex.unlock(); } @@ -321,7 +322,6 @@ void LLWorkerClass::abortWork(bool autocomplete) if (mRequestHandle != LLWorkerThread::nullHandle()) { mWorkerThread->abortRequest(mRequestHandle, autocomplete); - mWorkerThread->setPriority(mRequestHandle, LLQueuedThread::PRIORITY_IMMEDIATE); setFlags(WCF_ABORT_REQUESTED); } mMutex.unlock(); @@ -395,16 +395,5 @@ void LLWorkerClass::scheduleDelete() } } -void LLWorkerClass::setPriority(U32 priority) -{ - mMutex.lock(); - if (mRequestHandle != LLWorkerThread::nullHandle() && mRequestPriority != priority) - { - mRequestPriority = priority; - mWorkerThread->setPriority(mRequestHandle, priority); - } - mMutex.unlock(); -} - //============================================================================ diff --git a/indra/llcommon/llworkerthread.h b/indra/llcommon/llworkerthread.h index 0387e75c65..06bbb7369e 100644 --- a/indra/llcommon/llworkerthread.h +++ b/indra/llcommon/llworkerthread.h @@ -56,7 +56,7 @@ public: virtual ~WorkRequest(); // use deleteRequest() public: - WorkRequest(handle_t handle, U32 priority, LLWorkerClass* workerclass, S32 param); + WorkRequest(handle_t handle, LLWorkerClass* workerclass, S32 param); S32 getParam() { @@ -90,7 +90,7 @@ public: /*virtual*/ S32 update(F32 max_time_ms); - handle_t addWorkRequest(LLWorkerClass* workerclass, S32 param, U32 priority = PRIORITY_NORMAL); + handle_t addWorkRequest(LLWorkerClass* workerclass, S32 param); S32 getNumDeletes() { return (S32)mDeleteList.size(); } // debug @@ -151,10 +151,6 @@ public: bool isWorking() { return getFlags(WCF_WORKING); } bool wasAborted() { return getFlags(WCF_ABORT_REQUESTED); } - // setPriority(): changes the priority of a request - void setPriority(U32 priority); - U32 getPriority() { return mRequestPriority; } - const std::string& getName() const { return mWorkerClassName; } protected: @@ -169,7 +165,7 @@ protected: void setWorkerThread(LLWorkerThread* workerthread); // addWork(): calls startWork, adds doWork() to queue - void addWork(S32 param, U32 priority = LLWorkerThread::PRIORITY_NORMAL); + void addWork(S32 param); // abortWork(): requests that work be aborted void abortWork(bool autocomplete); @@ -193,7 +189,6 @@ protected: LLWorkerThread* mWorkerThread; std::string mWorkerClassName; handle_t mRequestHandle; - U32 mRequestPriority; // last priority set private: LLMutex mMutex; diff --git a/indra/llcommon/workqueue.h b/indra/llcommon/workqueue.h index 96574a18b9..46f7363830 100644 --- a/indra/llcommon/workqueue.h +++ b/indra/llcommon/workqueue.h @@ -161,10 +161,16 @@ namespace LL void postEvery(const std::chrono::duration& interval, CALLABLE&& callable); + template + bool tryPost(const TimePoint& time, CALLABLE&& callable) + { + return mQueue.tryPush(TimedWork(time, std::move(callable))); + } + template bool tryPost(CALLABLE&& callable) { - return mQueue.tryPush(TimedWork(TimePoint::clock::now(), std::move(callable))); + return mQueue.tryPost(TimePoint::clock::now(), std::move(callable)); } /*------------------------- handshake API --------------------------*/ -- cgit v1.2.3 From b08742d0b3e0000430b8ae772c7c4d25cfe084fa Mon Sep 17 00:00:00 2001 From: Dave Houlton Date: Thu, 28 Apr 2022 11:53:43 -0600 Subject: Add a (broken) material upload handler fxn --- indra/llcommon/llassettype.cpp | 3 ++- indra/llcommon/llassettype.h | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llassettype.cpp b/indra/llcommon/llassettype.cpp index e6cc06e8d0..7bf3c6553e 100644 --- a/indra/llcommon/llassettype.cpp +++ b/indra/llcommon/llassettype.cpp @@ -96,7 +96,8 @@ LLAssetDictionary::LLAssetDictionary() addEntry(LLAssetType::AT_WIDGET, new AssetEntry("WIDGET", "widget", "widget", false, false, false)); addEntry(LLAssetType::AT_PERSON, new AssetEntry("PERSON", "person", "person", false, false, false)); addEntry(LLAssetType::AT_SETTINGS, new AssetEntry("SETTINGS", "settings", "settings blob", true, true, true)); - addEntry(LLAssetType::AT_UNKNOWN, new AssetEntry("UNKNOWN", "invalid", NULL, false, false, false)); + addEntry(LLAssetType::AT_MATERIAL, new AssetEntry("MATERIAL", "material", "material", true, false, true)); + addEntry(LLAssetType::AT_UNKNOWN, new AssetEntry("UNKNOWN", "invalid", NULL, false, false, false)); addEntry(LLAssetType::AT_NONE, new AssetEntry("NONE", "-1", NULL, FALSE, FALSE, FALSE)); }; diff --git a/indra/llcommon/llassettype.h b/indra/llcommon/llassettype.h index 652c548d59..1027957863 100644 --- a/indra/llcommon/llassettype.h +++ b/indra/llcommon/llassettype.h @@ -127,8 +127,10 @@ public: AT_RESERVED_6 = 55, AT_SETTINGS = 56, // Collection of settings + + AT_MATERIAL = 57, - AT_COUNT = 57, + AT_COUNT = 58, // +*********************************************************+ // | TO ADD AN ELEMENT TO THIS ENUM: | -- cgit v1.2.3 From c9ef206e39063c46c1fbab355c1a015e3e8b022e Mon Sep 17 00:00:00 2001 From: Brad Kittenbrink Date: Thu, 28 Apr 2022 13:08:37 -0700 Subject: Beginning viewer side work for SL-17198 new asset and inventory types for Materials --- indra/llcommon/llassettype.cpp | 1 + indra/llcommon/llassettype.h | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llassettype.cpp b/indra/llcommon/llassettype.cpp index e6cc06e8d0..0bb1f1a0fd 100644 --- a/indra/llcommon/llassettype.cpp +++ b/indra/llcommon/llassettype.cpp @@ -96,6 +96,7 @@ LLAssetDictionary::LLAssetDictionary() addEntry(LLAssetType::AT_WIDGET, new AssetEntry("WIDGET", "widget", "widget", false, false, false)); addEntry(LLAssetType::AT_PERSON, new AssetEntry("PERSON", "person", "person", false, false, false)); addEntry(LLAssetType::AT_SETTINGS, new AssetEntry("SETTINGS", "settings", "settings blob", true, true, true)); + addEntry(LLAssetType::AT_MATERIAL, new AssetEntry("MATERIAL", "material", "render material", true, true, true)); addEntry(LLAssetType::AT_UNKNOWN, new AssetEntry("UNKNOWN", "invalid", NULL, false, false, false)); addEntry(LLAssetType::AT_NONE, new AssetEntry("NONE", "-1", NULL, FALSE, FALSE, FALSE)); diff --git a/indra/llcommon/llassettype.h b/indra/llcommon/llassettype.h index 652c548d59..9598f0f707 100644 --- a/indra/llcommon/llassettype.h +++ b/indra/llcommon/llassettype.h @@ -127,8 +127,9 @@ public: AT_RESERVED_6 = 55, AT_SETTINGS = 56, // Collection of settings + AT_MATERIAL = 57, // Render Material - AT_COUNT = 57, + AT_COUNT = 58, // +*********************************************************+ // | TO ADD AN ELEMENT TO THIS ENUM: | -- cgit v1.2.3 From 72ddfbd76ef3152c86e9b0b4331919d15d6a3d2a Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 10 May 2022 12:18:27 -0400 Subject: SL-17219: WorkQueue::tryPost() must call ThreadSafeSchedule::tryPush(). We inadvertently changed tryPost() to call ThreadSafeSchedule::tryPost(), which doesn't exist. --- indra/llcommon/workqueue.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/workqueue.h b/indra/llcommon/workqueue.h index 46f7363830..784327f929 100644 --- a/indra/llcommon/workqueue.h +++ b/indra/llcommon/workqueue.h @@ -170,7 +170,7 @@ namespace LL template bool tryPost(CALLABLE&& callable) { - return mQueue.tryPost(TimePoint::clock::now(), std::move(callable)); + return mQueue.tryPush(TimePoint::clock::now(), std::move(callable)); } /*------------------------- handshake API --------------------------*/ -- cgit v1.2.3 From 6c6d9a10f830e264cf75603949b54a12256cab78 Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Fri, 20 May 2022 13:31:18 -0500 Subject: SL-17287 Update Tracy to 0.8.1. Clean up GPU instrumentation. --- indra/llcommon/llframetimer.cpp | 6 +++--- indra/llcommon/llprofiler.h | 35 +++++++++++++++++++++++++++-------- 2 files changed, 30 insertions(+), 11 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llframetimer.cpp b/indra/llcommon/llframetimer.cpp index c54029e8b4..58af32f1af 100644 --- a/indra/llcommon/llframetimer.cpp +++ b/indra/llcommon/llframetimer.cpp @@ -30,9 +30,9 @@ #include "llframetimer.h" // We don't bother building a stand alone lib; we just need to include the one source file for Tracy support -#if LL_PROFILER_CONFIGURATION == LL_PROFILER_CONFIG_TRACY || LL_PROFILER_CONFIGURATION == LL_PROFILER_CONFIG_TRACY_FAST_TIMER - #include "TracyClient.cpp" -#endif // LL_PROFILER_CONFIGURATION +//#if LL_PROFILER_CONFIGURATION == LL_PROFILER_CONFIG_TRACY || LL_PROFILER_CONFIGURATION == LL_PROFILER_CONFIG_TRACY_FAST_TIMER +// #include "TracyClient.cpp" +//#endif // LL_PROFILER_CONFIGURATION // Static members //LLTimer LLFrameTimer::sInternalTimer; diff --git a/indra/llcommon/llprofiler.h b/indra/llcommon/llprofiler.h index f9d7ae7ce4..bc46128d21 100644 --- a/indra/llcommon/llprofiler.h +++ b/indra/llcommon/llprofiler.h @@ -86,8 +86,8 @@ extern thread_local bool gProfilerEnabled; #define TRACY_ONLY_IPV4 1 #include "Tracy.hpp" - // Mutually exclusive with detailed memory tracing - #define LL_PROFILER_ENABLE_TRACY_OPENGL 0 + // Disable memory tracing when enabled, but enabled + #define LL_PROFILER_ENABLE_TRACY_OPENGL 1 #endif #if LL_PROFILER_CONFIGURATION == LL_PROFILER_CONFIG_TRACY @@ -104,8 +104,6 @@ extern thread_local bool gProfilerEnabled; #define LL_PROFILE_ZONE_ERR(name) LL_PROFILE_ZONE_NAMED_COLOR( name, 0XFF0000 ) // RGB yellow #define LL_PROFILE_ZONE_INFO(name) LL_PROFILE_ZONE_NAMED_COLOR( name, 0X00FFFF ) // RGB cyan #define LL_PROFILE_ZONE_WARN(name) LL_PROFILE_ZONE_NAMED_COLOR( name, 0x0FFFF00 ) // RGB red - #define LL_PROFILE_ALLOC(ptr, size) TracyAlloc(ptr, size) - #define LL_PROFILE_FREE(ptr) TracyFree(ptr) #endif #if LL_PROFILER_CONFIGURATION == LL_PROFILER_CONFIG_FAST_TIMER #define LL_PROFILER_FRAME_END @@ -121,8 +119,6 @@ extern thread_local bool gProfilerEnabled; #define LL_PROFILE_ZONE_ERR(name) (void)(name); // Not supported #define LL_PROFILE_ZONE_INFO(name) (void)(name); // Not supported #define LL_PROFILE_ZONE_WARN(name) (void)(name); // Not supported - #define LL_PROFILE_ALLOC(ptr, size) (void)(ptr); (void)(size); - #define LL_PROFILE_FREE(ptr) (void)(ptr); #endif #if LL_PROFILER_CONFIGURATION == LL_PROFILER_CONFIG_TRACY_FAST_TIMER #define LL_PROFILER_FRAME_END FrameMark @@ -138,14 +134,37 @@ extern thread_local bool gProfilerEnabled; #define LL_PROFILE_ZONE_ERR(name) LL_PROFILE_ZONE_NAMED_COLOR( name, 0XFF0000 ) // RGB yellow #define LL_PROFILE_ZONE_INFO(name) LL_PROFILE_ZONE_NAMED_COLOR( name, 0X00FFFF ) // RGB cyan #define LL_PROFILE_ZONE_WARN(name) LL_PROFILE_ZONE_NAMED_COLOR( name, 0x0FFFF00 ) // RGB red - #define LL_PROFILE_ALLOC(ptr, size) TracyAlloc(ptr, size) - #define LL_PROFILE_FREE(ptr) TracyFree(ptr) #endif #else #define LL_PROFILER_FRAME_END #define LL_PROFILER_SET_THREAD_NAME( name ) (void)(name) #endif // LL_PROFILER +#if LL_PROFILER_ENABLE_TRACY_OPENGL +#define LL_PROFILE_GPU_ZONE(name) TracyGpuZone(name) +#define LL_PROFILE_GPU_ZONEC(name,color) TracyGpuZoneC(name,color) +#define LL_PROFILER_GPU_COLLECT TracyGpuCollect +#define LL_PROFILER_GPU_CONTEXT TracyGpuContext + +// disable memory tracking (incompatible with GPU tracing +#define LL_PROFILE_ALLOC(ptr, size) (void)(ptr); (void)(size); +#define LL_PROFILE_FREE(ptr) (void)(ptr); +#else +#define LL_PROFILE_GPU_ZONE(name) (void)name; +#define LL_PROFILE_GPU_ZONEC(name,color) (void)name;(void)color; +#define LL_PROFILER_GPU_COLLECT +#define LL_PROFILER_GPU_CONTEXT + +#if LL_PROFILER_CONFIG > 1 +#define LL_PROFILE_ALLOC(ptr, size) TracyAlloc(ptr, size) +#define LL_PROFILE_FREE(ptr) TracyFree(ptr) +#else +#define LL_PROFILE_ALLOC(ptr, size) (void)(ptr); (void)(size); +#define LL_PROFILE_FREE(ptr) (void)(ptr); +#endif + +#endif + #include "llprofilercategories.h" #endif // LL_PROFILER_H -- cgit v1.2.3 From 9bcc01e3e33c9152a0fb71de2b33e3e41b5a5534 Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Tue, 24 May 2022 14:30:30 -0500 Subject: SL-17484 Build fix. --- indra/llcommon/lltimer.cpp | 3 +++ 1 file changed, 3 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lltimer.cpp b/indra/llcommon/lltimer.cpp index b250bc3e1c..466f98f9b2 100644 --- a/indra/llcommon/lltimer.cpp +++ b/indra/llcommon/lltimer.cpp @@ -30,6 +30,9 @@ #include "u64.h" +#include +#include + #if LL_WINDOWS # include "llwin32headerslean.h" #elif LL_LINUX || LL_DARWIN -- cgit v1.2.3 From 8c5697a2d6b3976979d22da5c16569ef1c5aa9fd Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Tue, 31 May 2022 08:33:18 -0500 Subject: SL-17490 Update Tracy --- indra/llcommon/llframetimer.cpp | 5 ----- indra/llcommon/llprofiler.h | 6 ++++-- 2 files changed, 4 insertions(+), 7 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llframetimer.cpp b/indra/llcommon/llframetimer.cpp index c54029e8b4..1e9920746b 100644 --- a/indra/llcommon/llframetimer.cpp +++ b/indra/llcommon/llframetimer.cpp @@ -29,11 +29,6 @@ #include "llframetimer.h" -// We don't bother building a stand alone lib; we just need to include the one source file for Tracy support -#if LL_PROFILER_CONFIGURATION == LL_PROFILER_CONFIG_TRACY || LL_PROFILER_CONFIGURATION == LL_PROFILER_CONFIG_TRACY_FAST_TIMER - #include "TracyClient.cpp" -#endif // LL_PROFILER_CONFIGURATION - // Static members //LLTimer LLFrameTimer::sInternalTimer; U64 LLFrameTimer::sStartTotalTime = totalTime(); diff --git a/indra/llcommon/llprofiler.h b/indra/llcommon/llprofiler.h index f9d7ae7ce4..7a593092da 100644 --- a/indra/llcommon/llprofiler.h +++ b/indra/llcommon/llprofiler.h @@ -138,8 +138,10 @@ extern thread_local bool gProfilerEnabled; #define LL_PROFILE_ZONE_ERR(name) LL_PROFILE_ZONE_NAMED_COLOR( name, 0XFF0000 ) // RGB yellow #define LL_PROFILE_ZONE_INFO(name) LL_PROFILE_ZONE_NAMED_COLOR( name, 0X00FFFF ) // RGB cyan #define LL_PROFILE_ZONE_WARN(name) LL_PROFILE_ZONE_NAMED_COLOR( name, 0x0FFFF00 ) // RGB red - #define LL_PROFILE_ALLOC(ptr, size) TracyAlloc(ptr, size) - #define LL_PROFILE_FREE(ptr) TracyFree(ptr) + //#define LL_PROFILE_ALLOC(ptr, size) TracyAlloc(ptr, size) // memory allocation tracking currently not working + //#define LL_PROFILE_FREE(ptr) TracyFree(ptr) + #define LL_PROFILE_ALLOC(ptr, size) (void)(ptr); (void)(size); + #define LL_PROFILE_FREE(ptr) (void)(ptr); #endif #else #define LL_PROFILER_FRAME_END -- cgit v1.2.3 From fc7b5549cb6092194d11b8d87600f21992305c1c Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Tue, 31 May 2022 16:54:05 -0500 Subject: SL-17484 Fix for unit tests. Deprecate non-threaded LLQueuedThread and make lllfsthread threaded. --- indra/llcommon/llqueuedthread.cpp | 23 ++++++++++++++--------- indra/llcommon/lltimer.cpp | 7 ++++++- 2 files changed, 20 insertions(+), 10 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llqueuedthread.cpp b/indra/llcommon/llqueuedthread.cpp index 871c42f7ee..60304fff75 100644 --- a/indra/llcommon/llqueuedthread.cpp +++ b/indra/llcommon/llqueuedthread.cpp @@ -37,12 +37,13 @@ // MAIN THREAD LLQueuedThread::LLQueuedThread(const std::string& name, bool threaded, bool should_pause) : LLThread(name), - mThreaded(threaded), mIdleThread(TRUE), mNextHandle(0), mStarted(FALSE), + mThreaded(threaded), mRequestQueue(name, 1024 * 1024) { + llassert(threaded); // not threaded implementation is deprecated mMainQueue = LL::WorkQueue::getInstance("mainloop"); if (mThreaded) @@ -138,14 +139,18 @@ S32 LLQueuedThread::updateQueue(F32 max_time_ms) if (mThreaded) { // schedule a call to threadedUpdate for every call to updateQueue - mRequestQueue.post([=]() - { - LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qt - update"); - mIdleThread = FALSE; - threadedUpdate(); - mIdleThread = TRUE; - } - ); + if (!isQuitting()) + { + mRequestQueue.post([=]() + { + LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qt - update"); + mIdleThread = FALSE; + threadedUpdate(); + mIdleThread = TRUE; + } + ); + } + if(getPending() > 0) { unpause(); diff --git a/indra/llcommon/lltimer.cpp b/indra/llcommon/lltimer.cpp index 466f98f9b2..39dfee3755 100644 --- a/indra/llcommon/lltimer.cpp +++ b/indra/llcommon/lltimer.cpp @@ -68,7 +68,12 @@ LLTimer* LLTimer::sTimer = NULL; void ms_sleep(U32 ms) { LL_PROFILE_ZONE_SCOPED; - std::this_thread::sleep_for(std::chrono::microseconds(ms)); + using TimePoint = std::chrono::steady_clock::time_point; + auto resume_time = TimePoint::clock::now() + std::chrono::milliseconds(ms); + while (TimePoint::clock::now() < resume_time) + { + std::this_thread::yield(); //note: don't use LLThread::yield here to avoid yielding for too long + } } U32 micro_sleep(U64 us, U32 max_yields) -- cgit v1.2.3 From 609476e607b18d303afa5f5b5eeabeca84f95d16 Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Wed, 1 Jun 2022 09:25:16 -0500 Subject: SL-17484 More unit test pruning. Fix for crash when deleting textures. --- indra/llcommon/llqueuedthread.cpp | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llqueuedthread.cpp b/indra/llcommon/llqueuedthread.cpp index 60304fff75..155e32ebae 100644 --- a/indra/llcommon/llqueuedthread.cpp +++ b/indra/llcommon/llqueuedthread.cpp @@ -75,6 +75,11 @@ void LLQueuedThread::shutdown() unpause(); // MAIN THREAD if (mThreaded) { + if (mRequestQueue.size() == 0) + { + mRequestQueue.close(); + } + S32 timeout = 100; for ( ; timeout>0; timeout--) { -- cgit v1.2.3 From ef87eb7fa80a72b94d67d5ab680f60a837dd1ddd Mon Sep 17 00:00:00 2001 From: Cosmic Linden Date: Tue, 31 May 2022 12:49:53 -0700 Subject: SL-17483: Make ThreadPool inherit LLInstanceTracker (cherry picked from commit 41d6a0e222241606c317281e2f0b211e16813dd5) --- indra/llcommon/threadpool.cpp | 1 + indra/llcommon/threadpool.h | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/threadpool.cpp b/indra/llcommon/threadpool.cpp index ba914035e2..d5adf11264 100644 --- a/indra/llcommon/threadpool.cpp +++ b/indra/llcommon/threadpool.cpp @@ -22,6 +22,7 @@ #include "stringize.h" LL::ThreadPool::ThreadPool(const std::string& name, size_t threads, size_t capacity): + super(name), mQueue(name, capacity), mName("ThreadPool:" + name), mThreadCount(threads) diff --git a/indra/llcommon/threadpool.h b/indra/llcommon/threadpool.h index b79c9b9090..f8eec3b457 100644 --- a/indra/llcommon/threadpool.h +++ b/indra/llcommon/threadpool.h @@ -22,8 +22,10 @@ namespace LL { - class ThreadPool + class ThreadPool: public LLInstanceTracker { + private: + using super = LLInstanceTracker; public: /** * Pass ThreadPool a string name. This can be used to look up the -- cgit v1.2.3 From ac99e979f43d49402a24b2f58a154c4ef1583efd Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 9 Jun 2022 10:18:29 -0400 Subject: SL-17483: Make it possible to override width of any ThreadPool. Introduce CommonControl, which in a running viewer (or any program containing an LLViewerControlListener instance) gives access to LLViewerControl functionality, e.g. getting, setting or enumerating control variables -- without introducing a link dependency on newview. Make ThreadPool's constructor consult CommonControl to check for an override for the width of the new ThreadPool in the Global (i.e. gSavedSettings) setting ThreadPoolSizes, and honor that if found. Introduce static ThreadPool methods getConfiguredWidth(), to query for such an override on any particular ThreadPool name; and getWidth(), to ask for the width of an instance if that instance already exists, else the width with which it *would* be instantiated. --- indra/llcommon/CMakeLists.txt | 2 + indra/llcommon/commoncontrol.cpp | 104 +++++++++++++++++++++++++++++++++++++++ indra/llcommon/commoncontrol.h | 75 ++++++++++++++++++++++++++++ indra/llcommon/threadpool.cpp | 51 ++++++++++++++++++- indra/llcommon/threadpool.h | 30 ++++++++++- 5 files changed, 260 insertions(+), 2 deletions(-) create mode 100644 indra/llcommon/commoncontrol.cpp create mode 100644 indra/llcommon/commoncontrol.h (limited to 'indra/llcommon') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index ca8b5e946f..4dbf1282c4 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -29,6 +29,7 @@ include_directories( # ${LLCOMMON_LIBRARIES}) set(llcommon_SOURCE_FILES + commoncontrol.cpp indra_constants.cpp llallocator.cpp llallocator_heap_profile.cpp @@ -129,6 +130,7 @@ set(llcommon_HEADER_FILES CMakeLists.txt chrono.h + commoncontrol.h ctype_workaround.h fix_macros.h indra_constants.h diff --git a/indra/llcommon/commoncontrol.cpp b/indra/llcommon/commoncontrol.cpp new file mode 100644 index 0000000000..2c2a2abeb0 --- /dev/null +++ b/indra/llcommon/commoncontrol.cpp @@ -0,0 +1,104 @@ +/** + * @file commoncontrol.cpp + * @author Nat Goodspeed + * @date 2022-06-08 + * @brief Implementation for commoncontrol. + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Copyright (c) 2022, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "commoncontrol.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "llevents.h" +#include "llsdutil.h" + +LLSD LL::CommonControl::access(const LLSD& params) +{ + // We can't actually introduce a link-time dependency on llxml, or on any + // global LLControlGroup (*koff* gSavedSettings *koff*) but we can issue a + // runtime query. If we're running as part of a viewer with + // LLViewerControlListener, we can use that to interact with any + // instantiated LLControGroup. + LLSD response; + { + LLEventStream reply("reply"); + LLTempBoundListener connection = reply.listen("listener", + [&response] (const LLSD& event) + { + response = event; + return false; + }); + LLSD rparams{ params }; + rparams["reply"] = reply.getName(); + LLEventPumps::instance().obtain("LLViewerControl").post(rparams); + } + // LLViewerControlListener responds immediately. If it's listening at all, + // it will already have set response. + if (! response.isDefined()) + { + LLTHROW(NoListener("No LLViewerControl listener instantiated")); + } + LLSD error{ response["error"] }; + if (error.isDefined()) + { + LLTHROW(ParamError(error)); + } + return response; +} + +/// set control group.key to defined default value +LLSD LL::CommonControl::set_default(const std::string& group, const std::string& key) +{ + return access(llsd::map("op", "set", + "group", group, "key", key))["value"]; +} + +/// set control group.key to specified value +LLSD LL::CommonControl::set(const std::string& group, const std::string& key, const LLSD& value) +{ + return access(llsd::map("op", "set", + "group", group, "key", key, "value", value))["value"]; +} + +/// toggle boolean control group.key +LLSD LL::CommonControl::toggle(const std::string& group, const std::string& key) +{ + return access(llsd::map("op", "toggle", + "group", group, "key", key))["value"]; +} + +/// get the definition for control group.key, (! isDefined()) if bad +/// ["name"], ["type"], ["value"], ["comment"] +LLSD LL::CommonControl::get_def(const std::string& group, const std::string& key) +{ + return access(llsd::map("op", "get", + "group", group, "key", key)); +} + +/// get the value of control group.key +LLSD LL::CommonControl::get(const std::string& group, const std::string& key) +{ + return access(llsd::map("op", "get", + "group", group, "key", key))["value"]; +} + +/// get defined groups +std::vector LL::CommonControl::get_groups() +{ + auto groups{ access(llsd::map("op", "groups"))["groups"] }; + return { groups.beginArray(), groups.endArray() }; +} + +/// get definitions for all variables in group +LLSD LL::CommonControl::get_vars(const std::string& group) +{ + return access(llsd::map("op", "vars", "group", group))["vars"]; +} diff --git a/indra/llcommon/commoncontrol.h b/indra/llcommon/commoncontrol.h new file mode 100644 index 0000000000..07d4a45ac5 --- /dev/null +++ b/indra/llcommon/commoncontrol.h @@ -0,0 +1,75 @@ +/** + * @file commoncontrol.h + * @author Nat Goodspeed + * @date 2022-06-08 + * @brief Access LLViewerControl LLEventAPI, if process has one. + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Copyright (c) 2022, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_COMMONCONTROL_H) +#define LL_COMMONCONTROL_H + +#include +#include "llexception.h" +#include "llsd.h" + +namespace LL +{ + class CommonControl + { + public: + struct Error: public LLException + { + Error(const std::string& what): LLException(what) {} + }; + + /// Exception thrown if there's no LLViewerControl LLEventAPI + struct NoListener: public Error + { + NoListener(const std::string& what): Error(what) {} + }; + + struct ParamError: public Error + { + ParamError(const std::string& what): Error(what) {} + }; + + /// set control group.key to defined default value + static + LLSD set_default(const std::string& group, const std::string& key); + + /// set control group.key to specified value + static + LLSD set(const std::string& group, const std::string& key, const LLSD& value); + + /// toggle boolean control group.key + static + LLSD toggle(const std::string& group, const std::string& key); + + /// get the definition for control group.key, (! isDefined()) if bad + /// ["name"], ["type"], ["value"], ["comment"] + static + LLSD get_def(const std::string& group, const std::string& key); + + /// get the value of control group.key + static + LLSD get(const std::string& group, const std::string& key); + + /// get defined groups + static + std::vector get_groups(); + + /// get definitions for all variables in group + static + LLSD get_vars(const std::string& group); + + private: + static + LLSD access(const LLSD& params); + }; +} // namespace LL + +#endif /* ! defined(LL_COMMONCONTROL_H) */ diff --git a/indra/llcommon/threadpool.cpp b/indra/llcommon/threadpool.cpp index d5adf11264..10d67abf37 100644 --- a/indra/llcommon/threadpool.cpp +++ b/indra/llcommon/threadpool.cpp @@ -17,15 +17,17 @@ // std headers // external library headers // other Linden headers +#include "commoncontrol.h" #include "llerror.h" #include "llevents.h" +#include "llsd.h" #include "stringize.h" LL::ThreadPool::ThreadPool(const std::string& name, size_t threads, size_t capacity): super(name), mQueue(name, capacity), mName("ThreadPool:" + name), - mThreadCount(threads) + mThreadCount(getConfiguredWidth(name, threads)) {} void LL::ThreadPool::start() @@ -87,3 +89,50 @@ void LL::ThreadPool::run() { mQueue.runUntilClose(); } + +//static +size_t LL::ThreadPool::getConfiguredWidth(const std::string& name, size_t dft=0) +{ + LLSD poolSizes{ LL::CommonControl::get("Global", "ThreadPoolSizes") }; + // "ThreadPoolSizes" is actually a map containing the sizes of interest -- + // or should be, if this process has an "LLViewerControl" LLEventAPI + // instance and its settings include "ThreadPoolSizes". If we failed to + // retrieve it, perhaps we're in a program that doesn't define that, or + // perhaps there's no such setting, or perhaps we're asking too early, + // before the LLEventAPI itself has been instantiated. In any of those + // cases, it seems worth warning. + if (! poolSizes.isDefined()) + { + // Note: we don't warn about absence of an override key for a + // particular ThreadPool name, that's fine. This warning is about + // complete absence of a ThreadPoolSizes setting, which we expect in a + // normal viewer session. + LL_WARNS("ThreadPool") << "No 'ThreadPoolSizes' setting for ThreadPool '" + << name << "'" << LL_ENDL; + } + else + { + //LL_DEBUGS + LL_INFOS("ThreadPool") << "ThreadPoolSizes = " << poolSizes << LL_ENDL; + } + // LLSD treats an undefined value as an empty map when asked to retrieve a + // key, so we don't need this to be conditional. + LLSD sizeSpec{ poolSizes[name] }; + // We retrieve sizeSpec as LLSD, rather than immediately as LLSD::Integer, + // so we can distinguish the case when it's undefined. + return sizeSpec.isInteger() ? sizeSpec.asInteger() : dft; +} + +//static +size_t LL::ThreadPool::getWidth(const std::string& name, size_t dft) +{ + auto instance{ getInstance(name) }; + if (instance) + { + return instance->getWidth(); + } + else + { + return getConfiguredWidth(name, dft); + } +} diff --git a/indra/llcommon/threadpool.h b/indra/llcommon/threadpool.h index f8eec3b457..b49d511257 100644 --- a/indra/llcommon/threadpool.h +++ b/indra/llcommon/threadpool.h @@ -30,8 +30,17 @@ namespace LL /** * Pass ThreadPool a string name. This can be used to look up the * relevant WorkQueue. + * + * The number of threads you pass sets the compile-time default. But + * if the user has overridden the LLSD map in the "ThreadPoolSizes" + * setting with a key matching this ThreadPool name, that setting + * overrides this parameter. + * + * Pass an explicit capacity to limit the size of the queue. + * Constraining the queue can cause a submitter to block. Do not + * constrain any ThreadPool accepting work from the main thread. */ - ThreadPool(const std::string& name, size_t threads=1, size_t capacity=1024); + ThreadPool(const std::string& name, size_t threads=1, size_t capacity=1024*1024); virtual ~ThreadPool(); /** @@ -59,6 +68,25 @@ namespace LL */ virtual void run(); + /** + * getConfiguredWidth() returns the setting, if any, for the specified + * ThreadPool name. Returns dft if the "ThreadPoolSizes" map does not + * contain the specified name. + */ + static + size_t getConfiguredWidth(const std::string& name, size_t dft=0); + + /** + * This getWidth() returns the width of the instantiated ThreadPool + * with the specified name, if any. If no instance exists, returns its + * getConfiguredWidth() if any. If there's no instance and no relevant + * override, return dft. Presumably dft should match the threads + * parameter passed to the ThreadPool constructor call that will + * eventually instantiate the ThreadPool with that name. + */ + static + size_t getWidth(const std::string& name, size_t dft); + private: void run(const std::string& name); -- cgit v1.2.3 From 1ff79a430d9d55cbb4b4ba55018c791a8a933916 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 9 Jun 2022 11:19:21 -0400 Subject: SL-17483: Fix ThreadPool::getConfiguredWidth() compile error. Log ThreadPoolSizes at DEBUG level, not INFO. --- indra/llcommon/threadpool.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/threadpool.cpp b/indra/llcommon/threadpool.cpp index 10d67abf37..e8daf549ef 100644 --- a/indra/llcommon/threadpool.cpp +++ b/indra/llcommon/threadpool.cpp @@ -91,7 +91,7 @@ void LL::ThreadPool::run() } //static -size_t LL::ThreadPool::getConfiguredWidth(const std::string& name, size_t dft=0) +size_t LL::ThreadPool::getConfiguredWidth(const std::string& name, size_t dft) { LLSD poolSizes{ LL::CommonControl::get("Global", "ThreadPoolSizes") }; // "ThreadPoolSizes" is actually a map containing the sizes of interest -- @@ -112,8 +112,7 @@ size_t LL::ThreadPool::getConfiguredWidth(const std::string& name, size_t dft=0) } else { - //LL_DEBUGS - LL_INFOS("ThreadPool") << "ThreadPoolSizes = " << poolSizes << LL_ENDL; + LL_DEBUGS("ThreadPool") << "ThreadPoolSizes = " << poolSizes << LL_ENDL; } // LLSD treats an undefined value as an empty map when asked to retrieve a // key, so we don't need this to be conditional. -- cgit v1.2.3 From 64209ddeeafd944f82da6f13a6e790f9b542b3ff Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 9 Jun 2022 17:04:26 -0400 Subject: SL-17483: Add integration test for CommonControl and for LLViewerControlListener, to which it talks. Fix glitches detected by the tests. --- indra/llcommon/commoncontrol.cpp | 2 ++ indra/llcommon/threadpool.cpp | 43 ++++++++++++++++++++++++---------------- 2 files changed, 28 insertions(+), 17 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/commoncontrol.cpp b/indra/llcommon/commoncontrol.cpp index 2c2a2abeb0..81e66baf8c 100644 --- a/indra/llcommon/commoncontrol.cpp +++ b/indra/llcommon/commoncontrol.cpp @@ -51,6 +51,8 @@ LLSD LL::CommonControl::access(const LLSD& params) { LLTHROW(ParamError(error)); } + response.erase("error"); + response.erase("reqid"); return response; } diff --git a/indra/llcommon/threadpool.cpp b/indra/llcommon/threadpool.cpp index e8daf549ef..f49dd40a8b 100644 --- a/indra/llcommon/threadpool.cpp +++ b/indra/llcommon/threadpool.cpp @@ -93,27 +93,36 @@ void LL::ThreadPool::run() //static size_t LL::ThreadPool::getConfiguredWidth(const std::string& name, size_t dft) { - LLSD poolSizes{ LL::CommonControl::get("Global", "ThreadPoolSizes") }; - // "ThreadPoolSizes" is actually a map containing the sizes of interest -- - // or should be, if this process has an "LLViewerControl" LLEventAPI - // instance and its settings include "ThreadPoolSizes". If we failed to - // retrieve it, perhaps we're in a program that doesn't define that, or - // perhaps there's no such setting, or perhaps we're asking too early, - // before the LLEventAPI itself has been instantiated. In any of those - // cases, it seems worth warning. - if (! poolSizes.isDefined()) + LLSD poolSizes; + try { - // Note: we don't warn about absence of an override key for a - // particular ThreadPool name, that's fine. This warning is about - // complete absence of a ThreadPoolSizes setting, which we expect in a - // normal viewer session. - LL_WARNS("ThreadPool") << "No 'ThreadPoolSizes' setting for ThreadPool '" - << name << "'" << LL_ENDL; + poolSizes = LL::CommonControl::get("Global", "ThreadPoolSizes"); + // "ThreadPoolSizes" is actually a map containing the sizes of + // interest -- or should be, if this process has an + // LLViewerControlListener instance and its settings include + // "ThreadPoolSizes". If we failed to retrieve it, perhaps we're in a + // program that doesn't define that, or perhaps there's no such + // setting, or perhaps we're asking too early, before the LLEventAPI + // itself has been instantiated. In any of those cases, it seems worth + // warning. + if (! poolSizes.isDefined()) + { + // Note: we don't warn about absence of an override key for a + // particular ThreadPool name, that's fine. This warning is about + // complete absence of a ThreadPoolSizes setting, which we expect + // in a normal viewer session. + LL_WARNS("ThreadPool") << "No 'ThreadPoolSizes' setting for ThreadPool '" + << name << "'" << LL_ENDL; + } } - else + catch (const LL::CommonControl::Error& exc) { - LL_DEBUGS("ThreadPool") << "ThreadPoolSizes = " << poolSizes << LL_ENDL; + // We don't want ThreadPool to *require* LLViewerControlListener. + // Just log it and carry on. + LL_WARNS("ThreadPool") << "Can't check 'ThreadPoolSizes': " << exc.what() << LL_ENDL; } + + LL_DEBUGS("ThreadPool") << "ThreadPoolSizes = " << poolSizes << LL_ENDL; // LLSD treats an undefined value as an empty map when asked to retrieve a // key, so we don't need this to be conditional. LLSD sizeSpec{ poolSizes[name] }; -- cgit v1.2.3 From bdd76a6a1d0ee0fa5a8da76f1e8c8583822eeabe Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Mon, 13 Jun 2022 13:59:07 -0700 Subject: SL-17485 - Provide corrected missing memory data on mac: sAllocatedPageSizeInKB, sAllocatedMemInKB, sAvailPhysicalMemInKB, sMaxPhysicalMemInKB --- indra/llcommon/llmemory.cpp | 49 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llmemory.cpp b/indra/llcommon/llmemory.cpp index 849867586a..267159e7cc 100644 --- a/indra/llcommon/llmemory.cpp +++ b/indra/llcommon/llmemory.cpp @@ -35,6 +35,7 @@ # include # include # include +#include #elif LL_LINUX # include #endif @@ -46,6 +47,9 @@ #include "lltrace.h" #include "llerror.h" //---------------------------------------------------------------------------- +#if defined(LL_DARWIN) +extern LLMemoryInfo gSysMemory; +#endif //static U32Kilobytes LLMemory::sAvailPhysicalMemInKB(U32_MAX); @@ -109,6 +113,51 @@ void LLMemory::updateMemoryInfo() { sAvailPhysicalMemInKB = U32Kilobytes(0); } + +#elif defined(LL_DARWIN) + task_vm_info info; + mach_msg_type_number_t infoCount = TASK_VM_INFO_COUNT; + // MACH_TASK_BASIC_INFO reports the same resident_size, but does not tell us the reusable bytes or phys_footprint. + if (task_info(mach_task_self(), TASK_VM_INFO, reinterpret_cast(&info), &infoCount) == KERN_SUCCESS) + { + // Our Windows definition of PagefileUsage is documented by Microsoft as "the total amount of + // memory that the memory manager has committed for a running process", which is rss. + sAllocatedPageSizeInKB = U32Bytes(info.resident_size); + + // Activity Monitor => Inspect Process => Real Memory Size appears to report resident_size + // Activity monitor => main window memory column appears to report phys_footprint, which spot checks as at least 30% less. + // I think that is because of compression, which isn't going to give us a consistent measurement. We want uncompressed totals. + // + // In between is resident_size - reusable. This is what Chrome source code uses, with source comments saying it is 'the "Real Memory" value + // reported for the app by the Memory Monitor in Instruments.' It is still about 8% bigger than phys_footprint. + // + // (On Windows, we use WorkingSetSize.) + sAllocatedMemInKB = U32Bytes(info.resident_size - info.reusable); + } + else + { + LL_WARNS() << "task_info failed" << LL_ENDL; + } + + // Total installed and available physical memory are properties of the host, not just our process. + vm_statistics64_data_t vmstat; + mach_msg_type_number_t count = HOST_VM_INFO64_COUNT; + mach_port_t host = mach_host_self(); + vm_size_t page_size; + host_page_size(host, &page_size); + kern_return_t result = host_statistics64(host, HOST_VM_INFO64, reinterpret_cast(&vmstat), &count); + if (result == KERN_SUCCESS) { + // This is what Chrome reports as 'the "Physical Memory Free" value reported by the Memory Monitor in Instruments.' + // Note though that inactive pages are not included here and not yet free, but could become so under memory pressure. + sAvailPhysicalMemInKB = U32Bytes(vmstat.free_count * page_size); + + sMaxPhysicalMemInKB = gSysMemory.getPhysicalMemoryKB(); + } + else + { + LL_WARNS() << "task_info failed" << LL_ENDL; + } + #else //not valid for other systems for now. sAllocatedMemInKB = U64Bytes(LLMemory::getCurrentRSS()); -- cgit v1.2.3 From a4ff9caf69cb8fbbf3e5d40a258ec99a070b0f94 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 15 Jun 2022 19:43:53 -0400 Subject: DRTVWR-564: WIP: add LLEventPumps::registerPumpFactory() and registerTypeFactory(). Untested. This will support registering just-in-time LLEventAPI instances, instantiated on demand. --- indra/llcommon/llevents.cpp | 44 ++++++++++++++++++++++++++++++++++++++------ indra/llcommon/llevents.h | 39 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 76 insertions(+), 7 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp index 0a213bddef..5725dad9cc 100644 --- a/indra/llcommon/llevents.cpp +++ b/indra/llcommon/llevents.cpp @@ -68,19 +68,51 @@ LLEventPumps::LLEventPumps(): mFactories { - { "LLEventStream", [](const std::string& name, bool tweak) + { "LLEventStream", [](const std::string& name, bool tweak, const std::string& /*type*/) { return new LLEventStream(name, tweak); } }, - { "LLEventMailDrop", [](const std::string& name, bool tweak) + { "LLEventMailDrop", [](const std::string& name, bool tweak, const std::string& /*type*/) { return new LLEventMailDrop(name, tweak); } } }, mTypes { - // LLEventStream is the default for obtain(), so even if somebody DOES - // call obtain("placeholder"), this sample entry won't break anything. - { "placeholder", "LLEventStream" } +// { "placeholder", "LLEventStream" } } {} +bool LLEventPumps::registerTypeFactory(const std::string& type, const TypeFactory& factory) +{ + auto found = mFactories.find(type); + // can't re-register a TypeFactory for a type name that's already registered + if (found != mFactories.end()) + return false; + // doesn't already exist, go ahead and register + mFactories[type] = factory; + return true; +} + +bool LLEventPumps::registerPumpFactory(const std::string& name, const PumpFactory& factory) +{ + // Do we already have a pump by this name? + if (mPumpMap.find(name) != mPumpMap.end()) + return false; + // Do we already have an override for this pump name? + if (mTypes.find(name) != mTypes.end()) + return false; + // Leverage the two-level lookup implemented by mTypes (pump name -> type + // name) and mFactories (type name -> factory). We could instead create a + // whole separate (pump name -> factory) map, and look in both; or we + // could change mTypes to (pump name -> factory) and, for typical type- + // based lookups, use a "factory" that looks up the real factory in + // mFactories. But this works, and we don't expect many calls to make() - + // either explicit or implicit via obtain(). + // Create a bogus type name extremely unlikely to collide with an actual type. + static std::string nul(1, '\0'); + std::string type_name{ nul + name }; + mTypes[name] = type_name; + mFactories[type_name] = factory; + return true; +} + LLEventPump& LLEventPumps::obtain(const std::string& name) { PumpMap::iterator found = mPumpMap.find(name); @@ -114,7 +146,7 @@ LLEventPump& LLEventPumps::make(const std::string& name, bool tweak, // Passing an unrecognized type name is a no-no LLTHROW(BadType(type)); } - auto newInstance = (found->second)(name, tweak); + auto newInstance = (found->second)(name, tweak, type); // LLEventPump's constructor implicitly registers each new instance in // mPumpMap. But remember that we instantiated it (in mOurPumps) so we'll // delete it later. diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h index ae6e5aabc9..38adc31121 100644 --- a/indra/llcommon/llevents.h +++ b/indra/llcommon/llevents.h @@ -268,6 +268,43 @@ public: LLEventPump& make(const std::string& name, bool tweak=false, const std::string& type=std::string()); + /// function passed to registerTypeFactory() + typedef std::function TypeFactory; + + /** + * Register a TypeFactory for use with make(). When make() is called with + * the specified @a type string, call @a factory(name, tweak, type) to + * instantiate it. + * + * Returns true if successfully registered, false if there already exists + * a TypeFactory for the specified @a type name. + */ + bool registerTypeFactory(const std::string& type, const TypeFactory& factory); + + /// function passed to registerPumpFactory() + typedef std::function PumpFactory; + + /** + * Register a PumpFactory for use with obtain(). When obtain() is called + * with the specified @a name string, if an LLEventPump with the specified + * @a name doesn't already exist, call @a factory(name) to instantiate it. + * + * Returns true if successfully registered, false if there already exists + * a factory override for the specified @a name. + * + * PumpFactory does not support @a tweak because it's only called when + * that particular @a name is passed to obtain(). Bear in mind that + * obtain(name) might still bypass the caller's PumpFactory for a + * couple different reasons: + * + * * registerPumpFactory() returns false because there's already a factory + * override for the specified @name + * * between a successful registerPumpFactory(name) call (returns + * true) and a call to obtain(name), someone explicitly + * instantiated an LLEventPump(name), so obtain(name) returned that. + */ + bool registerPumpFactory(const std::string& name, const PumpFactory& factory); + /** * Find the named LLEventPump instance. If it exists post the message to it. * If the pump does not exist, do nothing. @@ -325,7 +362,7 @@ testable: typedef std::set PumpSet; PumpSet mOurPumps; // for make(), map string type name to LLEventPump subclass factory function - typedef std::map> PumpFactories; + typedef std::map PumpFactories; // Data used by make(). // One might think mFactories and mTypes could reasonably be static. So // they could -- if not for the fact that make() or obtain() might be -- cgit v1.2.3 From b26e516d2b93a442d09f5c3b1b4d8d60139c42f5 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 6 Apr 2022 17:34:28 -0400 Subject: DRTVWR-558: Change LLEventDispatcher error action (also LLEventAPI). Originally the LLEventAPI mechanism was primarily used for VITA testing. In that case it was okay for the viewer to crash with LL_ERRS if the test script passed a bad request. With puppetry, hopefully new LEAP scripts will be written to engage LLEventAPIs in all sorts of interesting ways. Change error handling from LL_ERRS to LL_WARNS. Furthermore, if the incoming request contains a "reply" key, send back an error response to the requester. Update lleventdispatcher_test.cpp accordingly. (cherry picked from commit de0539fcbe815ceec2041ecc9981e3adf59f2806) --- indra/llcommon/lleventdispatcher.cpp | 127 +++++++++++++++++------- indra/llcommon/lleventdispatcher.h | 29 ++++-- indra/llcommon/tests/lleventdispatcher_test.cpp | 62 ++++++++---- 3 files changed, 152 insertions(+), 66 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index 5b6d4efbe9..742d6cf51f 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -40,15 +40,24 @@ // other Linden headers #include "llevents.h" #include "llerror.h" +#include "llexception.h" #include "llsdutil.h" #include "stringize.h" #include // std::auto_ptr +/***************************************************************************** +* DispatchError +*****************************************************************************/ +struct DispatchError: public LLException +{ + DispatchError(const std::string& what): LLException(what) {} +}; + /***************************************************************************** * LLSDArgsSource *****************************************************************************/ /** - * Store an LLSD array, producing its elements one at a time. Die with LL_ERRS + * Store an LLSD array, producing its elements one at a time. It is an error * if the consumer requests more elements than the array contains. */ class LL_COMMON_API LLSDArgsSource @@ -74,8 +83,7 @@ LLSDArgsSource::LLSDArgsSource(const std::string function, const LLSD& args): { if (! (_args.isUndefined() || _args.isArray())) { - LL_ERRS("LLSDArgsSource") << _function << " needs an args array instead of " - << _args << LL_ENDL; + LLTHROW(DispatchError(stringize(_function, " needs an args array instead of ", _args))); } } @@ -88,8 +96,8 @@ LLSD LLSDArgsSource::next() { if (_index >= _args.size()) { - LL_ERRS("LLSDArgsSource") << _function << " requires more arguments than the " - << _args.size() << " provided: " << _args << LL_ENDL; + LLTHROW(DispatchError(stringize(_function, " requires more arguments than the ", + _args.size(), " provided: ", _args))); } return _args[_index++]; } @@ -163,7 +171,8 @@ public: /// default values LLSDArgsMapper(const std::string& function, const LLSD& names, const LLSD& defaults); - /// Given arguments map, return LLSD::Array of parameter values, or LL_ERRS. + /// Given arguments map, return LLSD::Array of parameter values, or + /// trigger error. LLSD map(const LLSD& argsmap) const; private: @@ -195,7 +204,7 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function, { if (! (_names.isUndefined() || _names.isArray())) { - LL_ERRS("LLSDArgsMapper") << function << " names must be an array, not " << names << LL_ENDL; + LLTHROW(DispatchError(stringize(function, " names must be an array, not ", names))); } LLSD::Integer nparams(_names.size()); // From _names generate _indexes. @@ -218,8 +227,8 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function, // defaults is a (possibly empty) array. Right-align it with names. if (ndefaults > nparams) { - LL_ERRS("LLSDArgsMapper") << function << " names array " << names - << " shorter than defaults array " << defaults << LL_ENDL; + LLTHROW(DispatchError(stringize(function, " names array ", names, + " shorter than defaults array ", defaults))); } // Offset by which we slide defaults array right to right-align with @@ -256,14 +265,14 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function, } if (bogus.size()) { - LL_ERRS("LLSDArgsMapper") << function << " defaults specified for nonexistent params " - << formatlist(bogus) << LL_ENDL; + LLTHROW(DispatchError(stringize(function, " defaults specified for nonexistent params ", + formatlist(bogus)))); } } else { - LL_ERRS("LLSDArgsMapper") << function << " defaults must be a map or an array, not " - << defaults << LL_ENDL; + LLTHROW(DispatchError(stringize(function, " defaults must be a map or an array, not ", + defaults))); } } @@ -271,8 +280,8 @@ LLSD LLSDArgsMapper::map(const LLSD& argsmap) const { if (! (argsmap.isUndefined() || argsmap.isMap() || argsmap.isArray())) { - LL_ERRS("LLSDArgsMapper") << _function << " map() needs a map or array, not " - << argsmap << LL_ENDL; + LLTHROW(DispatchError(stringize(_function, " map() needs a map or array, not ", + argsmap))); } // Initialize the args array. Indexing a non-const LLSD array grows it // to appropriate size, but we don't want to resize this one on each @@ -369,8 +378,8 @@ LLSD LLSDArgsMapper::map(const LLSD& argsmap) const // by argsmap, that's a problem. if (unfilled.size()) { - LL_ERRS("LLSDArgsMapper") << _function << " missing required arguments " - << formatlist(unfilled) << " from " << argsmap << LL_ENDL; + LLTHROW(DispatchError(stringize(_function, " missing required arguments ", + formatlist(unfilled), " from ", argsmap))); } // done @@ -420,7 +429,7 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE std::string mismatch(llsd_matches(mRequired, event)); if (! mismatch.empty()) { - LL_ERRS("LLEventDispatcher") << desc << ": bad request: " << mismatch << LL_ENDL; + LLTHROW(DispatchError(stringize(desc, ": bad request: ", mismatch))); } // Event syntax looks good, go for it! mFunc(event); @@ -596,48 +605,90 @@ bool LLEventDispatcher::remove(const std::string& name) return true; } -/// Call a registered callable with an explicitly-specified name. If no -/// such callable exists, die with LL_ERRS. +/// Call a registered callable with an explicitly-specified name. It is an +/// error if no such callable exists. void LLEventDispatcher::operator()(const std::string& name, const LLSD& event) const { - if (! try_call(name, event)) + std::string error{ try_call_log(std::string(), name, event) }; + if (! error.empty()) { - LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): '" << name - << "' not found" << LL_ENDL; + callFail(event, error); } } -/// Extract the @a key value from the incoming @a event, and call the -/// callable whose name is specified by that map @a key. If no such -/// callable exists, die with LL_ERRS. +/// Extract the @a key value from the incoming @a event, and call the callable +/// whose name is specified by that map @a key. It is an error if no such +/// callable exists. void LLEventDispatcher::operator()(const LLSD& event) const { - // This could/should be implemented in terms of the two-arg overload. - // However -- we can produce a more informative error message. - std::string name(event[mKey]); - if (! try_call(name, event)) + std::string error{ try_call_log(mKey, event[mKey], event) }; + if (! error.empty()) + { + callFail(event, error); + } +} + +void LLEventDispatcher::callFail(const LLSD& event, const std::string& msg) const +{ + static LLSD::String key{ "reply" }; + if (event.has(key)) { - LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << "): bad " << mKey - << " value '" << name << "'" << LL_ENDL; + // Oh good, the incoming event specifies a reply pump -- pass back a + // response that includes an "error" key with the message. + sendReply(llsd::map("error", msg), event, key); } } bool LLEventDispatcher::try_call(const LLSD& event) const { - return try_call(event[mKey], event); + return try_call_log(mKey, event[mKey], event).empty(); } bool LLEventDispatcher::try_call(const std::string& name, const LLSD& event) const +{ + return try_call_log(std::string(), name, event).empty(); +} + +std::string LLEventDispatcher::try_call_log(const std::string& key, const std::string& name, + const LLSD& event) const +{ + std::string error{ try_call(key, name, event) }; + if (! error.empty()) + { + LL_WARNS("LLEventDispatcher") << error << LL_ENDL; + } + return error; +} + +// This internal method returns empty string if the call succeeded, else +// non-empty error message. +std::string LLEventDispatcher::try_call(const std::string& key, const std::string& name, + const LLSD& event) const { DispatchMap::const_iterator found = mDispatch.find(name); if (found == mDispatch.end()) { - return false; + if (key.empty()) + { + return stringize("LLEventDispatcher(", mDesc, "): '", name, "' not found"); + } + else + { + return stringize("LLEventDispatcher(", mDesc, "): bad ", key, " value '", name, "'"); + } + } + + try + { + // Found the name, so it's plausible to even attempt the call. + found->second->call(stringize("LLEventDispatcher(", mDesc, ") calling '", name, "'"), + event); + } + catch (const DispatchError& err) + { + return err.what(); } - // Found the name, so it's plausible to even attempt the call. - found->second->call(STRINGIZE("LLEventDispatcher(" << mDesc << ") calling '" << name << "'"), - event); - return true; // tell caller we were able to call + return {}; // tell caller we were able to call } LLSD LLEventDispatcher::getMetadata(const std::string& name) const diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 9e1244ef5b..b78c77f8b3 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -254,28 +254,28 @@ public: /// Unregister a callable bool remove(const std::string& name); - /// Call a registered callable with an explicitly-specified name. If no - /// such callable exists, die with LL_ERRS. If the @a event fails to match - /// the @a required prototype specified at add() time, die with LL_ERRS. + /// Call a registered callable with an explicitly-specified name. It is an + /// error if no such callable exists. It is an error if the @a event fails + /// to match the @a required prototype specified at add() time. void operator()(const std::string& name, const LLSD& event) const; /// Call a registered callable with an explicitly-specified name and /// return true. If no such callable exists, return - /// false. If the @a event fails to match the @a required - /// prototype specified at add() time, die with LL_ERRS. + /// false. It is an error if the @a event fails to match the @a + /// required prototype specified at add() time. bool try_call(const std::string& name, const LLSD& event) const; /// Extract the @a key value from the incoming @a event, and call the - /// callable whose name is specified by that map @a key. If no such - /// callable exists, die with LL_ERRS. If the @a event fails to match the - /// @a required prototype specified at add() time, die with LL_ERRS. + /// callable whose name is specified by that map @a key. It is an error if + /// no such callable exists. It is an error if the @a event fails to match + /// the @a required prototype specified at add() time. void operator()(const LLSD& event) const; /// Extract the @a key value from the incoming @a event, call the callable /// whose name is specified by that map @a key and return true. - /// If no such callable exists, return false. If the @a event - /// fails to match the @a required prototype specified at add() time, die - /// with LL_ERRS. + /// If no such callable exists, return false. It is an error if + /// the @a event fails to match the @a required prototype specified at + /// add() time. bool try_call(const LLSD& event) const; /// @name Iterate over defined names @@ -340,6 +340,13 @@ private: } } void addFail(const std::string& name, const std::string& classname) const; + std::string try_call_log(const std::string& key, const std::string& name, + const LLSD& event) const; + std::string try_call(const std::string& key, const std::string& name, + const LLSD& event) const; + // Implement "it is an error" semantics for attempted call operations: if + // the incoming event includes a "reply" key, log and send an error reply. + void callFail(const LLSD& event, const std::string& msg) const; std::string mDesc, mKey; DispatchMap mDispatch; diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 9da1ecfd67..82a0ddf61b 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -20,6 +20,7 @@ #include "../test/lltut.h" #include "llsd.h" #include "llsdutil.h" +#include "llevents.h" #include "stringize.h" #include "tests/wrapllerrs.h" #include "../test/catch_and_store_what_in.h" @@ -644,12 +645,45 @@ namespace tut outer.find(inner) != std::string::npos); } - void call_exc(const std::string& func, const LLSD& args, const std::string& exc_frag) + std::string call_exc(const std::string& func, const LLSD& args, const std::string& exc_frag) { - std::string threw = catch_what([this, &func, &args](){ - work(func, args); - }); - ensure_has(threw, exc_frag); + // This method was written when LLEventDispatcher responded to + // name or argument errors with LL_ERRS, hence the name: we used + // to have to intercept LL_ERRS by making it throw. Now we set up + // to catch an error response instead. But -- for that we need to + // be able to sneak a "reply" key into args, which must be a Map. + if (! (args.isUndefined() or args.isMap())) + fail(stringize("can't test call_exc() with ", args)); + LLEventStream replypump("reply"); + LLSD reply; + LLTempBoundListener bound{ + replypump.listen( + "listener", + [&reply](const LLSD& event) + { + reply = event; + return false; + }) }; + LLSD modargs{ args }; + modargs["reply"] = replypump.getName(); + if (func.empty()) + { + work(modargs); + } + else + { + work(func, modargs); + } + ensure("no error response", reply.has("error")); + ensure_has(reply["error"], exc_frag); + return reply["error"]; + } + + void call_logerr(const std::string& func, const LLSD& args, const std::string& frag) + { + CaptureLog capture; + work(func, args); + capture.messageWith(frag); } LLSD getMetadata(const std::string& name) @@ -1031,13 +1065,7 @@ namespace tut { set_test_name("call with bad name"); call_exc("freek", LLSD(), "not found"); - // We don't have a comparable helper function for the one-arg - // operator() method, and it's not worth building one just for this - // case. Write it out. - std::string threw = catch_what([this](){ - work(LLSDMap("op", "freek")); - }); - ensure_has(threw, "bad"); + std::string threw = call_exc("", LLSDMap("op", "freek"), "bad"); ensure_has(threw, "op"); ensure_has(threw, "freek"); } @@ -1087,7 +1115,7 @@ namespace tut ensure_equals("answer mismatch", tr.llsd, answer); // Should NOT be able to pass 'answer' to Callables registered // with 'required'. - call_exc(tr.name_req, answer, "bad request"); + call_logerr(tr.name_req, answer, "bad request"); // But SHOULD be able to pass 'matching' to Callables registered // with 'required'. work(tr.name_req, matching); @@ -1107,11 +1135,11 @@ namespace tut // args. We should only need to engage it for one map-style // registration and one array-style registration. std::string array_exc("needs an args array"); - call_exc("free0_array", 17, array_exc); - call_exc("free0_array", LLSDMap("pi", 3.14), array_exc); + call_logerr("free0_array", 17, array_exc); + call_logerr("free0_array", LLSDMap("pi", 3.14), array_exc); std::string map_exc("needs a map"); - call_exc("free0_map", 17, map_exc); + call_logerr("free0_map", 17, map_exc); // Passing an array to a map-style function works now! No longer an // error case! // call_exc("free0_map", LLSDArray("a")("b"), map_exc); @@ -1158,7 +1186,7 @@ namespace tut { foreach(const llsd::MapEntry& e, inMap(funcsab)) { - call_exc(e.second, tooshort, "requires more arguments"); + call_logerr(e.second, tooshort, "requires more arguments"); } } } -- cgit v1.2.3 From f134eace911eca8a231a7c1d556d7d891e8b21ca Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 6 Apr 2022 21:49:03 -0400 Subject: DRTVWR-558: LLEventAPI allows all LLEventDispatcher add() overloads. Previously, LLEventAPI intentionally hid all but one of the many add() overloads supported by its LLEventDispatcher base class. The reason was that certain of the add() methods take an optional fourth parameter that's an LLSD::Map describing the expected parameter structure, while others take a fourth templated parameter that's an instance getter callable. This led to ambiguity, especially when passed an LLSDMap instance that's convertible to LLSD but isn't literally LLSD. At the time, it was simpler to constrain the add() methods inherited from LLEventDispatcher. But by adding new std::enable_if constraints to certain LLEventDispatcher add() methods, we've resolved the ambiguities, so LLEventAPI subclasses can now use any add() overload (as claimed on the relevant Confluence page). LLEventDispatcher comments have always loftily claimed that an instance getter callable may return either a pointer or a reference, doesn't matter. But it does when trying to pass the getter's result to boost::fusion::push_back(): a reference must be wrapped with std::ref() while a pointer cannot be. std::ref(pointer) produces errors. Introduce LLEventDispatcher::invoker:: bindable() overloads to Do The Right Thing whether passed a pointer or a reference. (cherry picked from commit 743f487c2e123171c9fc6d5b84d768f1d856d569) --- indra/llcommon/lleventapi.h | 13 ------ indra/llcommon/lleventdispatcher.h | 89 ++++++++++++++++++++++++++------------ 2 files changed, 61 insertions(+), 41 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventapi.h b/indra/llcommon/lleventapi.h index 5991fe8fd5..ed62fa064a 100644 --- a/indra/llcommon/lleventapi.h +++ b/indra/llcommon/lleventapi.h @@ -64,19 +64,6 @@ public: /// Get the documentation string std::string getDesc() const { return mDesc; } - /** - * Publish only selected add() methods from LLEventDispatcher. - * Every LLEventAPI add() @em must have a description string. - */ - template - void add(const std::string& name, - const std::string& desc, - CALLABLE callable, - const LLSD& required=LLSD()) - { - LLEventDispatcher::add(name, desc, callable, required); - } - /** * Instantiate a Response object in any LLEventAPI subclass method that * wants to guarantee a reply (if requested) will be sent on exit from the diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index b78c77f8b3..f1e4fe2df7 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -61,7 +61,6 @@ static const auto& nil(nil_); #include #include #include -#include #include #include #include @@ -167,10 +166,11 @@ public: * converted to the corresponding parameter type using LLSDParam. */ template - typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin - >::type add(const std::string& name, - const std::string& desc, - Function f); + typename std::enable_if< + boost::function_types::is_nonmember_callable_builtin::value + >::type add(const std::string& name, + const std::string& desc, + Function f); /** * Register a nonstatic class method with arbitrary parameters. @@ -190,11 +190,14 @@ public: * converted to the corresponding parameter type using LLSDParam. */ template - typename boost::enable_if< boost::function_types::is_member_function_pointer - >::type add(const std::string& name, - const std::string& desc, - Method f, - const InstanceGetter& getter); + typename std::enable_if< + boost::function_types::is_member_function_pointer::value && + ! std::is_same::value && + ! std::is_same::value + >::type add(const std::string& name, + const std::string& desc, + Method f, + const InstanceGetter& getter); /** * Register a free function with arbitrary parameters. (This also works @@ -212,12 +215,13 @@ public: * to the corresponding parameter type using LLSDParam. */ template - typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin - >::type add(const std::string& name, - const std::string& desc, - Function f, - const LLSD& params, - const LLSD& defaults=LLSD()); + typename std::enable_if< + boost::function_types::is_nonmember_callable_builtin::value + >::type add(const std::string& name, + const std::string& desc, + Function f, + const LLSD& params, + const LLSD& defaults=LLSD()); /** * Register a nonstatic class method with arbitrary parameters. @@ -241,13 +245,16 @@ public: * to the corresponding parameter type using LLSDParam. */ template - typename boost::enable_if< boost::function_types::is_member_function_pointer - >::type add(const std::string& name, - const std::string& desc, - Method f, - const InstanceGetter& getter, - const LLSD& params, - const LLSD& defaults=LLSD()); + typename std::enable_if< + boost::function_types::is_member_function_pointer::value && + ! std::is_same::value && + ! std::is_same::value + >::type add(const std::string& name, + const std::string& desc, + Method f, + const InstanceGetter& getter, + const LLSD& params, + const LLSD& defaults=LLSD()); //@} @@ -434,7 +441,25 @@ struct LLEventDispatcher::invoker // Instead of grabbing the first item from argsrc and making an // LLSDParam of it, call getter() and pass that as the instance param. invoker::apply - ( func, argsrc, boost::fusion::push_back(boost::fusion::nil(), boost::ref(getter()))); + ( func, argsrc, boost::fusion::push_back(boost::fusion::nil(), bindable(getter()))); + } + + template + static inline + auto bindable(T&& value, + typename std::enable_if::value, bool>::type=true) + { + // if passed a pointer, just return that pointer + return std::forward(value); + } + + template + static inline + auto bindable(T&& value, + typename std::enable_if::value, bool>::type=true) + { + // if passed a reference, wrap it for binding + return std::ref(std::forward(value)); } }; @@ -454,7 +479,7 @@ struct LLEventDispatcher::invoker }; template -typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin >::type +typename std::enable_if< boost::function_types::is_nonmember_callable_builtin::value >::type LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f) { // Construct an invoker_function, a callable accepting const args_source&. @@ -465,7 +490,11 @@ LLEventDispatcher::add(const std::string& name, const std::string& desc, Functio } template -typename boost::enable_if< boost::function_types::is_member_function_pointer >::type +typename std::enable_if< + boost::function_types::is_member_function_pointer::value && + ! std::is_same::value && + ! std::is_same::value +>::type LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, const InstanceGetter& getter) { @@ -476,7 +505,7 @@ LLEventDispatcher::add(const std::string& name, const std::string& desc, Method } template -typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin >::type +typename std::enable_if< boost::function_types::is_nonmember_callable_builtin::value >::type LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f, const LLSD& params, const LLSD& defaults) { @@ -485,7 +514,11 @@ LLEventDispatcher::add(const std::string& name, const std::string& desc, Functio } template -typename boost::enable_if< boost::function_types::is_member_function_pointer >::type +typename std::enable_if< + boost::function_types::is_member_function_pointer::value && + ! std::is_same::value && + ! std::is_same::value +>::type LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, const InstanceGetter& getter, const LLSD& params, const LLSD& defaults) -- cgit v1.2.3 From 4e7b4bab79be8cf2de9af242e5cd23347fba8bb2 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 6 Apr 2022 22:19:10 -0400 Subject: DRTVWR-558: Generalize LLEventDispatcher::add() constraints. Instead of checking whether an add() parameter is exactly LLSD or LLSDMap, check whether it's convertible to LLSD -- which handles those cases and more. (cherry picked from commit fa168c11f64771dadc5df86d14ca2f07eba3b8ba) --- indra/llcommon/lleventdispatcher.h | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index f1e4fe2df7..1b3e834aeb 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -192,8 +192,7 @@ public: template typename std::enable_if< boost::function_types::is_member_function_pointer::value && - ! std::is_same::value && - ! std::is_same::value + ! std::is_convertible::value >::type add(const std::string& name, const std::string& desc, Method f, @@ -247,8 +246,7 @@ public: template typename std::enable_if< boost::function_types::is_member_function_pointer::value && - ! std::is_same::value && - ! std::is_same::value + ! std::is_convertible::value >::type add(const std::string& name, const std::string& desc, Method f, @@ -492,8 +490,7 @@ LLEventDispatcher::add(const std::string& name, const std::string& desc, Functio template typename std::enable_if< boost::function_types::is_member_function_pointer::value && - ! std::is_same::value && - ! std::is_same::value + ! std::is_convertible::value >::type LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, const InstanceGetter& getter) @@ -516,8 +513,7 @@ LLEventDispatcher::add(const std::string& name, const std::string& desc, Functio template typename std::enable_if< boost::function_types::is_member_function_pointer::value && - ! std::is_same::value && - ! std::is_same::value + ! std::is_convertible::value >::type LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, const InstanceGetter& getter, -- cgit v1.2.3 From 33fe18c335375ecdaf57ddd0138fc7a6cb227843 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Fri, 17 Jun 2022 16:29:39 -0700 Subject: SL-17485-b - Attempt to make teamcity builds happy by not referencing a newview global from an llcommon file. --- indra/llcommon/llmemory.cpp | 6 +----- indra/llcommon/llsys.cpp | 24 ++++++++++++++++-------- indra/llcommon/llsys.h | 5 ++++- 3 files changed, 21 insertions(+), 14 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llmemory.cpp b/indra/llcommon/llmemory.cpp index 267159e7cc..749b66b472 100644 --- a/indra/llcommon/llmemory.cpp +++ b/indra/llcommon/llmemory.cpp @@ -47,9 +47,6 @@ #include "lltrace.h" #include "llerror.h" //---------------------------------------------------------------------------- -#if defined(LL_DARWIN) -extern LLMemoryInfo gSysMemory; -#endif //static U32Kilobytes LLMemory::sAvailPhysicalMemInKB(U32_MAX); @@ -150,8 +147,7 @@ void LLMemory::updateMemoryInfo() // This is what Chrome reports as 'the "Physical Memory Free" value reported by the Memory Monitor in Instruments.' // Note though that inactive pages are not included here and not yet free, but could become so under memory pressure. sAvailPhysicalMemInKB = U32Bytes(vmstat.free_count * page_size); - - sMaxPhysicalMemInKB = gSysMemory.getPhysicalMemoryKB(); + sMaxPhysicalMemInKB = LLMemoryInfo::getHardwareMemSize(); } else { diff --git a/indra/llcommon/llsys.cpp b/indra/llcommon/llsys.cpp index f717b2cf34..aeb6ae1b0a 100644 --- a/indra/llcommon/llsys.cpp +++ b/indra/llcommon/llsys.cpp @@ -684,20 +684,28 @@ static U32Kilobytes LLMemoryAdjustKBResult(U32Kilobytes inKB) } #endif +#if LL_DARWIN +// static +U32Kilobytes LLMemoryInfo::getHardwareMemSize() +{ + // This might work on Linux as well. Someone check... + uint64_t phys = 0; + int mib[2] = { CTL_HW, HW_MEMSIZE }; + + size_t len = sizeof(phys); + sysctl(mib, 2, &phys, &len, NULL, 0); + + return U64Bytes(phys); +} +#endif + U32Kilobytes LLMemoryInfo::getPhysicalMemoryKB() const { #if LL_WINDOWS return LLMemoryAdjustKBResult(U32Kilobytes(mStatsMap["Total Physical KB"].asInteger())); #elif LL_DARWIN - // This might work on Linux as well. Someone check... - uint64_t phys = 0; - int mib[2] = { CTL_HW, HW_MEMSIZE }; - - size_t len = sizeof(phys); - sysctl(mib, 2, &phys, &len, NULL, 0); - - return U64Bytes(phys); + return getHardwareMemSize(); #elif LL_LINUX U64 phys = 0; diff --git a/indra/llcommon/llsys.h b/indra/llcommon/llsys.h index 5ab97939b9..b2f9b6a4ab 100644 --- a/indra/llcommon/llsys.h +++ b/indra/llcommon/llsys.h @@ -113,7 +113,10 @@ public: LLMemoryInfo(); ///< Default constructor void stream(std::ostream& s) const; ///< output text info to s - U32Kilobytes getPhysicalMemoryKB() const; + U32Kilobytes getPhysicalMemoryKB() const; +#if LL_DARWIN + static U32Kilobytes getHardwareMemSize(); // Because some Mac linkers won't let us reference extern gSysMemory from a different lib. +#endif //get the available memory infomation in KiloBytes. static void getAvailableMemoryKB(U32Kilobytes& avail_physical_mem_kb, U32Kilobytes& avail_virtual_mem_kb); -- cgit v1.2.3 From dc2e2cd76f387ea6e80787fb94adcbc269cd1f25 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 18 Jun 2022 11:53:57 -0400 Subject: DRTVWR-564: Add LL::apply(): call function, passing args from tuple. This anticipates C++17's std::apply(), and in fact once we detect C++17, we'll just use that. But in C++14 we must still provide our own implementation. --- indra/llcommon/apply.h | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 indra/llcommon/apply.h (limited to 'indra/llcommon') diff --git a/indra/llcommon/apply.h b/indra/llcommon/apply.h new file mode 100644 index 0000000000..ef4a8fd68b --- /dev/null +++ b/indra/llcommon/apply.h @@ -0,0 +1,51 @@ +/** + * @file apply.h + * @author Nat Goodspeed + * @date 2022-06-18 + * @brief C++14 version of std::apply() + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Copyright (c) 2022, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_APPLY_H) +#define LL_APPLY_H + +#include + +namespace LL +{ + +#if __cplusplus >= 201703L + +// C++17 implementation +using std::apply; + +#else // C++14 + +// Derived from https://stackoverflow.com/a/20441189 +// and https://en.cppreference.com/w/cpp/utility/apply +template +auto apply_impl(CALLABLE&& func, TUPLE&& args, std::index_sequence) +{ + // call func(unpacked args) + return std::forward(func)(std::move(std::get(args))...); +} + +template +auto apply(CALLABLE&& func, std::tuple&& args) +{ + // std::index_sequence_for is the magic sauce here, generating an argument + // pack of indexes for each entry in args. apply_impl() can then pass + // those to std::get() to unpack args into individual arguments. + return apply_impl(std::forward(func), + std::forward>(args), + std::index_sequence_for{}); +} + +#endif // C++14 + +} // namespace LL + +#endif /* ! defined(LL_APPLY_H) */ -- cgit v1.2.3 From af4fbc1f8a99a3c5370cb6db45435e67f9ce92d2 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 18 Jun 2022 11:57:10 -0400 Subject: DRTVWR-564: WIP: Add LazyEventAPI and tests. Tests don't yet pass. LazyEventAPI is a registrar that implicitly instantiates some particular LLEventAPI subclass on demand: that is, when LLEventPumps::obtain() tries to find an LLEventPump by the registered name. This leverages the new LLEventPumps::registerPumpFactory() machinery. Fix registerPumpFactory() to adapt the passed PumpFactory to accept TypeFactory parameters (two of which it ignores). Supplement it with unregisterPumpFactory() to support LazyEventAPI instances with lifespans shorter than the process -- which may be mostly test programs, but still a hole worth closing. Similarly, add unregisterTypeFactory(). A LazyEventAPI subclass takes over responsibility for specifying the LLEventAPI's name, desc, field, plus whatever add() calls will be needed to register the LLEventAPI's operations. This is so we can (later) enhance LLLeapListener to consult LazyEventAPI instances for not-yet-instantiated LLEventAPI metadata, as well as enumerating existing LLEventAPI instances. The trickiest part of this is capturing calls to the various LLEventDispatcher::add() overloads in such a way that, when the LLEventAPI subclass is eventually instantiated, we can replay them in the new instance. LLEventAPI acquires a new protected constructor specifically for use by a subclass registered by a companion LazyEventAPI. It accepts a const reference to LazyEventAPIParams, intended to be opaque to the LLEventAPI subclass; the subclass must declare a constructor that accepts and forwards the parameter block to the new LLEventAPI constructor. The implementation delegates to the existing LLEventAPI constructor, plus it runs deferred add() calls. LLDispatchListener now derives from LLEventStream instead of containing it as a data member. The reason is that if LLEventPumps::obtain() implicitly instantiates it, LLEventPumps's destructor will try to destroy it by deleting the LLEventPump*. If the LLEventPump returned by the factory function is a data member of an outer class, that won't work so well. But if LLDispatchListener (and by implication, LLEventAPI and any subclass) is derived from LLEventPump, then the virtual destructor will Do The Right Thing. Change LLDispatchListener to *not* allow tweaking the LLEventPump name. Since the overwhelming use case for LLDispatchListener is LLEventAPI, accepting but silently renaming an LLEventAPI subclass would ensure nobody could reach it. Change LLEventDispatcher's use of std::enable_if to control the set of add() overloads available for the intended use cases. Apparently this formulation is just as functional at the method declaration point, while avoiding the need to restate the whole enable_if expression at the method definition point. Add lazyeventapi_test.cpp to exercise. --- indra/llcommon/CMakeLists.txt | 4 + indra/llcommon/lazyeventapi.cpp | 53 ++++++++ indra/llcommon/lazyeventapi.h | 204 +++++++++++++++++++++++++++++ indra/llcommon/lleventapi.cpp | 8 ++ indra/llcommon/lleventapi.h | 23 +++- indra/llcommon/lleventdispatcher.cpp | 13 +- indra/llcommon/lleventdispatcher.h | 105 +++++++-------- indra/llcommon/llevents.cpp | 29 +++- indra/llcommon/llevents.h | 6 +- indra/llcommon/tests/lazyeventapi_test.cpp | 89 +++++++++++++ 10 files changed, 466 insertions(+), 68 deletions(-) create mode 100644 indra/llcommon/lazyeventapi.cpp create mode 100644 indra/llcommon/lazyeventapi.h create mode 100644 indra/llcommon/tests/lazyeventapi_test.cpp (limited to 'indra/llcommon') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index ca8b5e946f..36b2e09dc5 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -30,6 +30,7 @@ include_directories( set(llcommon_SOURCE_FILES indra_constants.cpp + lazyeventapi.cpp llallocator.cpp llallocator_heap_profile.cpp llapp.cpp @@ -128,10 +129,12 @@ set(llcommon_SOURCE_FILES set(llcommon_HEADER_FILES CMakeLists.txt + apply.h chrono.h ctype_workaround.h fix_macros.h indra_constants.h + lazyeventapi.h linden_common.h llalignedarray.h llallocator.h @@ -338,6 +341,7 @@ if (LL_TESTS) ${BOOST_SYSTEM_LIBRARY}) LL_ADD_INTEGRATION_TEST(commonmisc "" "${test_libs}") LL_ADD_INTEGRATION_TEST(bitpack "" "${test_libs}") + LL_ADD_INTEGRATION_TEST(lazyeventapi "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llbase64 "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llcond "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lldate "" "${test_libs}") diff --git a/indra/llcommon/lazyeventapi.cpp b/indra/llcommon/lazyeventapi.cpp new file mode 100644 index 0000000000..aefc2db6da --- /dev/null +++ b/indra/llcommon/lazyeventapi.cpp @@ -0,0 +1,53 @@ +/** + * @file lazyeventapi.cpp + * @author Nat Goodspeed + * @date 2022-06-17 + * @brief Implementation for lazyeventapi. + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Copyright (c) 2022, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lazyeventapi.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "llevents.h" + +LL::LazyEventAPIBase::LazyEventAPIBase( + const std::string& name, const std::string& desc, const std::string& field) +{ + // populate embedded LazyEventAPIParams instance + mParams.name = name; + mParams.desc = desc; + mParams.field = field; + // mParams.init and mOperations are populated by subsequent add() calls. + + // Our raison d'etre: register as an LLEventPumps::PumpFactory + // so obtain() will notice any request for this name and call us. + // Of course, our subclass constructor must finish running (making add() + // calls) before mParams will be fully populated, but we expect that to + // happen well before the first LLEventPumps::obtain(name) call. + mRegistered = LLEventPumps::instance().registerPumpFactory( + name, + [this](const std::string& name){ return construct(name); }); +} + +LL::LazyEventAPIBase::~LazyEventAPIBase() +{ + // If our constructor's registerPumpFactory() call was unsuccessful, that + // probably means somebody else claimed the name first. If that's the + // case, do NOT unregister their name out from under them! + // If this is a static instance being destroyed at process shutdown, + // LLEventPumps will probably have been cleaned up already. + if (mRegistered && ! LLEventPumps::wasDeleted()) + { + // unregister the callback to this doomed instance + LLEventPumps::instance().unregisterPumpFactory(mParams.name); + } +} diff --git a/indra/llcommon/lazyeventapi.h b/indra/llcommon/lazyeventapi.h new file mode 100644 index 0000000000..2e947899dc --- /dev/null +++ b/indra/llcommon/lazyeventapi.h @@ -0,0 +1,204 @@ +/** + * @file lazyeventapi.h + * @author Nat Goodspeed + * @date 2022-06-16 + * @brief Declaring a static module-scope LazyEventAPI registers a specific + * LLEventAPI for future on-demand instantiation. + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Copyright (c) 2022, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LAZYEVENTAPI_H) +#define LL_LAZYEVENTAPI_H + +#include "apply.h" +#include "lleventapi.h" +#include "llinstancetracker.h" +#include +#include +#include +#include // std::pair +#include + +namespace LL +{ + /** + * Bundle params we want to pass to LLEventAPI's protected constructor. We + * package them this way so a subclass constructor can simply forward an + * opaque reference to the LLEventAPI constructor. + */ + // This is a class instead of a plain struct mostly so when we forward- + // declare it we don't have to remember the distinction. + class LazyEventAPIParams + { + public: + // package the parameters used by the normal LLEventAPI constructor + std::string name, desc, field; + // bundle LLEventAPI::add() calls collected by LazyEventAPI::add(), so + // the special LLEventAPI constructor we engage can "play back" those + // add() calls + boost::signals2::signal init; + }; + + // The tricky part is: can we capture a sequence of add() calls in the + // LazyEventAPI subclass constructor and then, in effect, replay those + // add() calls on instantiation of the registered LLEventAPI subclass? so + // we don't have to duplicate the add() calls in both constructors? + + // Derive a subclass from LazyEventAPI. Its constructor must pass + // LazyEventAPI's constructor the name, desc, field params. Moreover the + // constructor body must call add(name, desc, *args) for any of the + // LLEventDispatcher add() methods, referencing the LLEventAPI subclass + // methods. + + // LazyEventAPI will store the name, desc, field params for the overall + // LLEventAPI. It will support a single generic add() call accepting name, + // desc, parameter pack. + + // It will hold a std::vector> for each operation. + // It will make all these strings available to LLLeapListener. + + // Maybe what we want is to store a vector of callables (a + // boost::signals2!) and populate it with lambdas, each of which accepts + // LLEventAPI* and calls the relevant add() method by forwarding exactly + // the name, desc and parameter pack. Then, on constructing the target + // LLEventAPI, we just fire the signal, passing the new instance pointer. + + /** + * LazyEventAPIBase implements most of the functionality of LazyEventAPI + * (q.v.), but we need the LazyEventAPI template subclass so we can accept + * the specific LLEventAPI subclass type. + */ + // No LLInstanceTracker key: we don't need to find a specific instance, + // LLLeapListener just needs to be able to enumerate all instances. + class LazyEventAPIBase: public LLInstanceTracker + { + public: + LazyEventAPIBase(const std::string& name, const std::string& desc, + const std::string& field); + virtual ~LazyEventAPIBase(); + + // Do not copy or move: once constructed, LazyEventAPIBase must stay + // put: we bind its instance pointer into a callback. + LazyEventAPIBase(const LazyEventAPIBase&) = delete; + LazyEventAPIBase(LazyEventAPIBase&&) = delete; + LazyEventAPIBase& operator=(const LazyEventAPIBase&) = delete; + LazyEventAPIBase& operator=(LazyEventAPIBase&&) = delete; + + // actually instantiate the companion LLEventAPI subclass + virtual LLEventPump* construct(const std::string& name) = 0; + + // capture add() calls we want to play back on LLEventAPI construction + template + void add(const std::string& name, const std::string& desc, ARGS&&... rest) + { + // capture the metadata separately + mOperations.push_back(std::make_pair(name, desc)); + // Use connect_extended() so the lambda is passed its own + // connection. + // We can't bind an unexpanded parameter pack into a lambda -- + // shame really. Instead, capture it as a std::tuple and then, in + // the lambda, use apply() to convert back to function args. + mParams.init.connect_extended( + [name, desc, rest = std::make_tuple(std::forward(rest)...)] + (const boost::signals2::connection& conn, LLEventAPI* instance) + { + // we only need this connection once + conn.disconnect(); + // Our add() method distinguishes name and desc because we + // capture them separately. But now, because apply() + // expects a tuple specifying ALL the arguments, expand to + // a tuple including add_trampoline() arguments: instance, + // name, desc, rest. + // apply() can't accept a template per se; it needs a + // particular specialization. + apply(&LazyEventAPIBase::add_trampoline, + std::tuple_cat(std::make_tuple(instance, name, desc), + rest)); + }); + } + + // metadata that might be queried by LLLeapListener + std::vector> mOperations; + // Params with which to instantiate the companion LLEventAPI subclass + LazyEventAPIParams mParams; + + private: + // Passing an overloaded function to any function that accepts an + // arbitrary callable is a PITB because you have to specify the + // correct overload. What we want is for the compiler to select the + // correct overload, based on the carefully-wrought enable_ifs in + // LLEventDispatcher. This (one and only) add_trampoline() method + // exists solely to pass to LL::apply(). Once add_trampoline() is + // called with the expanded arguments, we hope the compiler will Do + // The Right Thing in selecting the correct LLEventAPI::add() + // overload. + template + static + void add_trampoline(LLEventAPI* instance, ARGS&&... args) + { + instance->add(std::forward(args)...); + } + + bool mRegistered; + }; + + /** + * LazyEventAPI provides a way to register a particular LLEventAPI to be + * instantiated on demand, that is, when its name is passed to + * LLEventPumps::obtain(). + * + * Derive your listener from LLEventAPI as usual, with its various + * operation methods, but code your constructor to accept + * (const LL::LazyEventAPIParams& params) + * and forward that reference to (the protected) + * LLEventAPI(const LL::LazyEventAPIParams&) constructor. + * + * Then derive your listener registrar from + * LazyEventAPI. The constructor should + * look very like a traditional LLEventAPI constructor: + * + * * pass (name, desc [, field]) to LazyEventAPI's constructor + * * in the body, make a series of add() calls referencing your LLEventAPI + * subclass methods. + * + * You may use any LLEventAPI::add() methods, that is, any + * LLEventDispatcher::add() methods. But the target methods you pass to + * add() must belong to your LLEventAPI subclass, not the LazyEventAPI + * subclass. + * + * Declare a static instance of your LazyEventAPI listener registrar + * class. When it's constructed at static initialization time, it will + * register your LLEventAPI subclass with LLEventPumps. It will also + * collect metadata for the LLEventAPI and its operations to provide to + * LLLeapListener's introspection queries. + * + * When someone later calls LLEventPumps::obtain() to post an event to + * your LLEventAPI subclass, obtain() will instantiate it using + * LazyEventAPI's name, desc, field and add() calls. + */ + template + class LazyEventAPI: public LazyEventAPIBase + { + public: + // for subclass constructor to reference handler methods + using listener = EVENTAPI; + + LazyEventAPI(const std::string& name, const std::string& desc, + const std::string& field="op"): + // Forward ctor params to LazyEventAPIBase + LazyEventAPIBase(name, desc, field) + {} + + LLEventPump* construct(const std::string& /*name*/) override + { + // base class has carefully assembled LazyEventAPIParams embedded + // in this instance, just pass to LLEventAPI subclass constructor + return new EVENTAPI(mParams); + } + }; +} // namespace LL + +#endif /* ! defined(LL_LAZYEVENTAPI_H) */ diff --git a/indra/llcommon/lleventapi.cpp b/indra/llcommon/lleventapi.cpp index ff5459c1eb..3d46ef1034 100644 --- a/indra/llcommon/lleventapi.cpp +++ b/indra/llcommon/lleventapi.cpp @@ -35,6 +35,7 @@ // external library headers // other Linden headers #include "llerror.h" +#include "lazyeventapi.h" LLEventAPI::LLEventAPI(const std::string& name, const std::string& desc, const std::string& field): lbase(name, field), @@ -43,6 +44,13 @@ LLEventAPI::LLEventAPI(const std::string& name, const std::string& desc, const s { } +LLEventAPI::LLEventAPI(const LL::LazyEventAPIParams& params): + LLEventAPI(params.name, params.desc, params.field) +{ + // call initialization functions with our brand-new instance pointer + params.init(this); +} + LLEventAPI::~LLEventAPI() { } diff --git a/indra/llcommon/lleventapi.h b/indra/llcommon/lleventapi.h index ed62fa064a..a019458553 100644 --- a/indra/llcommon/lleventapi.h +++ b/indra/llcommon/lleventapi.h @@ -35,6 +35,13 @@ #include "llinstancetracker.h" #include +namespace LL +{ + template + class LazyEventAPI; + class LazyEventAPIParams; +} + /** * LLEventAPI not only provides operation dispatch functionality, inherited * from LLDispatchListener -- it also gives us event API introspection. @@ -45,6 +52,8 @@ class LL_COMMON_API LLEventAPI: public LLDispatchListener, { typedef LLDispatchListener lbase; typedef LLInstanceTracker ibase; + template + friend class LL::LazyEventAPI; public: @@ -137,16 +146,20 @@ public: * @endcode */ LLSD& operator[](const LLSD::String& key) { return mResp[key]; } - - /** - * set the response to the given data - */ - void setResponse(LLSD const & response){ mResp = response; } + + /** + * set the response to the given data + */ + void setResponse(LLSD const & response){ mResp = response; } LLSD mResp, mReq; LLSD::String mKey; }; +protected: + // constructor used only by subclasses registered by LazyEventAPI + LLEventAPI(const LL::LazyEventAPIParams&); + private: std::string mDesc; }; diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index 742d6cf51f..bc53ec3da0 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -706,8 +706,17 @@ LLSD LLEventDispatcher::getMetadata(const std::string& name) const LLDispatchListener::LLDispatchListener(const std::string& pumpname, const std::string& key): LLEventDispatcher(pumpname, key), - mPump(pumpname, true), // allow tweaking for uniqueness - mBoundListener(mPump.listen("self", boost::bind(&LLDispatchListener::process, this, _1))) + // Do NOT tweak the passed pumpname. In practice, when someone + // instantiates a subclass of our LLEventAPI subclass, they intend to + // claim that LLEventPump name in the global LLEventPumps namespace. It + // would be mysterious and distressing if we allowed name tweaking, and + // someone else claimed pumpname first for a completely unrelated + // LLEventPump. Posted events would never reach our subclass listener + // because we would have silently changed its name; meanwhile listeners + // (if any) on that other LLEventPump would be confused by the events + // intended for our subclass. + LLEventStream(pumpname, false), + mBoundListener(listen("self", [this](const LLSD& event){ return process(event); })) { } diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 1b3e834aeb..ce9d3775cc 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -165,12 +165,12 @@ public: * When calling this name, pass an LLSD::Array. Each entry in turn will be * converted to the corresponding parameter type using LLSDParam. */ - template - typename std::enable_if< - boost::function_types::is_nonmember_callable_builtin::value - >::type add(const std::string& name, - const std::string& desc, - Function f); + // enable_if usage per https://stackoverflow.com/a/39913395/5533635 + template::value + >::type> + void add(const std::string& name, const std::string& desc, Function f); /** * Register a nonstatic class method with arbitrary parameters. @@ -189,14 +189,13 @@ public: * When calling this name, pass an LLSD::Array. Each entry in turn will be * converted to the corresponding parameter type using LLSDParam. */ - template - typename std::enable_if< - boost::function_types::is_member_function_pointer::value && - ! std::is_convertible::value - >::type add(const std::string& name, - const std::string& desc, - Method f, - const InstanceGetter& getter); + template::value && + ! std::is_convertible::value + >::type> + void add(const std::string& name, const std::string& desc, Method f, + const InstanceGetter& getter); /** * Register a free function with arbitrary parameters. (This also works @@ -213,14 +212,12 @@ public: * an LLSD::Array using LLSDArgsMapper and then convert each entry in turn * to the corresponding parameter type using LLSDParam. */ - template - typename std::enable_if< - boost::function_types::is_nonmember_callable_builtin::value - >::type add(const std::string& name, - const std::string& desc, - Function f, - const LLSD& params, - const LLSD& defaults=LLSD()); + template::value + >::type> + void add(const std::string& name, const std::string& desc, Function f, + const LLSD& params, const LLSD& defaults=LLSD()); /** * Register a nonstatic class method with arbitrary parameters. @@ -243,16 +240,14 @@ public: * an LLSD::Array using LLSDArgsMapper and then convert each entry in turn * to the corresponding parameter type using LLSDParam. */ - template - typename std::enable_if< - boost::function_types::is_member_function_pointer::value && - ! std::is_convertible::value - >::type add(const std::string& name, - const std::string& desc, - Method f, - const InstanceGetter& getter, - const LLSD& params, - const LLSD& defaults=LLSD()); + template::value && + ! std::is_convertible::value + >::type> + void add(const std::string& name, const std::string& desc, Method f, + const InstanceGetter& getter, const LLSD& params, + const LLSD& defaults=LLSD()); //@} @@ -476,9 +471,8 @@ struct LLEventDispatcher::invoker } }; -template -typename std::enable_if< boost::function_types::is_nonmember_callable_builtin::value >::type -LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f) +template +void LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f) { // Construct an invoker_function, a callable accepting const args_source&. // Add to DispatchMap an ArrayParamsDispatchEntry that will handle the @@ -487,13 +481,9 @@ LLEventDispatcher::add(const std::string& name, const std::string& desc, Functio boost::function_types::function_arity::value); } -template -typename std::enable_if< - boost::function_types::is_member_function_pointer::value && - ! std::is_convertible::value ->::type -LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, - const InstanceGetter& getter) +template +void LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, + const InstanceGetter& getter) { // Subtract 1 from the compile-time arity because the getter takes care of // the first parameter. We only need (arity - 1) additional arguments. @@ -501,23 +491,18 @@ LLEventDispatcher::add(const std::string& name, const std::string& desc, Method boost::function_types::function_arity::value - 1); } -template -typename std::enable_if< boost::function_types::is_nonmember_callable_builtin::value >::type -LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f, - const LLSD& params, const LLSD& defaults) +template +void LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f, + const LLSD& params, const LLSD& defaults) { // See comments for previous is_nonmember_callable_builtin add(). addMapParamsDispatchEntry(name, desc, make_invoker(f), params, defaults); } -template -typename std::enable_if< - boost::function_types::is_member_function_pointer::value && - ! std::is_convertible::value ->::type -LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, - const InstanceGetter& getter, - const LLSD& params, const LLSD& defaults) +template +void LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, + const InstanceGetter& getter, + const LLSD& params, const LLSD& defaults) { addMapParamsDispatchEntry(name, desc, make_invoker(f, getter), params, defaults); } @@ -560,17 +545,21 @@ LLEventDispatcher::make_invoker(Method f, const InstanceGetter& getter) * LLEventPump name and dispatch key, and add() its methods. Incoming events * will automatically be dispatched. */ -class LL_COMMON_API LLDispatchListener: public LLEventDispatcher +// Instead of containing an LLEventStream, LLDispatchListener derives from it. +// This allows an LLEventPumps::PumpFactory to return a pointer to an +// LLDispatchListener (subclass) instance, and still have ~LLEventPumps() +// properly clean it up. +class LL_COMMON_API LLDispatchListener: + public LLEventDispatcher, + public LLEventStream { public: LLDispatchListener(const std::string& pumpname, const std::string& key); - - std::string getPumpName() const { return mPump.getName(); } + virtual ~LLDispatchListener() {} private: bool process(const LLSD& event); - LLEventStream mPump; LLTempBoundListener mBoundListener; }; diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp index 5725dad9cc..1a305ec3dc 100644 --- a/indra/llcommon/llevents.cpp +++ b/indra/llcommon/llevents.cpp @@ -90,6 +90,13 @@ bool LLEventPumps::registerTypeFactory(const std::string& type, const TypeFactor return true; } +void LLEventPumps::unregisterTypeFactory(const std::string& type) +{ + auto found = mFactories.find(type); + if (found != mFactories.end()) + mFactories.erase(found); +} + bool LLEventPumps::registerPumpFactory(const std::string& name, const PumpFactory& factory) { // Do we already have a pump by this name? @@ -109,10 +116,30 @@ bool LLEventPumps::registerPumpFactory(const std::string& name, const PumpFactor static std::string nul(1, '\0'); std::string type_name{ nul + name }; mTypes[name] = type_name; - mFactories[type_name] = factory; + // TypeFactory is called with (name, tweak, type), whereas PumpFactory + // accepts only name. We could adapt with std::bind(), but this lambda + // does the trick. + mFactories[type_name] = + [factory] + (const std::string& name, bool /*tweak*/, const std::string& /*type*/) + { return factory(name); }; return true; } +void LLEventPumps::unregisterPumpFactory(const std::string& name) +{ + auto tfound = mTypes.find(name); + if (tfound != mTypes.end()) + { + auto ffound = mFactories.find(tfound->second); + if (ffound != mFactories.end()) + { + mFactories.erase(ffound); + } + mTypes.erase(tfound); + } +} + LLEventPump& LLEventPumps::obtain(const std::string& name) { PumpMap::iterator found = mPumpMap.find(name); diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h index 38adc31121..c1dbf4392f 100644 --- a/indra/llcommon/llevents.h +++ b/indra/llcommon/llevents.h @@ -280,6 +280,7 @@ public: * a TypeFactory for the specified @a type name. */ bool registerTypeFactory(const std::string& type, const TypeFactory& factory); + void unregisterTypeFactory(const std::string& type); /// function passed to registerPumpFactory() typedef std::function PumpFactory; @@ -304,6 +305,7 @@ public: * instantiated an LLEventPump(name), so obtain(name) returned that. */ bool registerPumpFactory(const std::string& name, const PumpFactory& factory); + void unregisterPumpFactory(const std::string& name); /** * Find the named LLEventPump instance. If it exists post the message to it. @@ -362,13 +364,13 @@ testable: typedef std::set PumpSet; PumpSet mOurPumps; // for make(), map string type name to LLEventPump subclass factory function - typedef std::map PumpFactories; + typedef std::map TypeFactories; // Data used by make(). // One might think mFactories and mTypes could reasonably be static. So // they could -- if not for the fact that make() or obtain() might be // called before this module's static variables have been initialized. // This is why we use singletons in the first place. - PumpFactories mFactories; + TypeFactories mFactories; // for obtain(), map desired string instance name to string type when // obtain() must create the instance diff --git a/indra/llcommon/tests/lazyeventapi_test.cpp b/indra/llcommon/tests/lazyeventapi_test.cpp new file mode 100644 index 0000000000..6639c5e540 --- /dev/null +++ b/indra/llcommon/tests/lazyeventapi_test.cpp @@ -0,0 +1,89 @@ +/** + * @file lazyeventapi_test.cpp + * @author Nat Goodspeed + * @date 2022-06-18 + * @brief Test for lazyeventapi. + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Copyright (c) 2022, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lazyeventapi.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "../test/lltut.h" +#include "llevents.h" + +// LLEventAPI listener subclass +class MyListener: public LLEventAPI +{ +public: + MyListener(const LL::LazyEventAPIParams& params): + LLEventAPI(params) + {} + + void get(const LLSD& event) + { + std::cout << "MyListener::get() got " << event << std::endl; + } +}; + +// LazyEventAPI registrar subclass +class MyRegistrar: public LL::LazyEventAPI +{ + using super = LL::LazyEventAPI; + using super::listener; +public: + MyRegistrar(): + super("Test", "This is a test LLEventAPI") + { + add("get", "This is a get operation", &listener::get); + } +}; +// Normally we'd declare a static instance of MyRegistrar -- but because we +// may want to test with and without, defer declaration to individual test +// methods. + +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + struct lazyeventapi_data + { + ~lazyeventapi_data() + { + // after every test, reset LLEventPumps + LLEventPumps::deleteSingleton(); + } + }; + typedef test_group lazyeventapi_group; + typedef lazyeventapi_group::object object; + lazyeventapi_group lazyeventapigrp("lazyeventapi"); + + template<> template<> + void object::test<1>() + { + set_test_name("LazyEventAPI"); + // this is where the magic (should) happen + // 'register' still a keyword until C++17 + MyRegistrar regster; + LLEventPumps::instance().obtain("Test").post("hey"); + } + + template<> template<> + void object::test<2>() + { + set_test_name("No LazyEventAPI"); + // Because the MyRegistrar declaration in test<1>() is local, because + // it has been destroyed, we fully expect NOT to reach a MyListener + // instance with this post. + LLEventPumps::instance().obtain("Test").post("moot"); + } +} // namespace tut -- cgit v1.2.3 From 490de3ab6e3a2b9dd8668c2093e265f36324f82e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 21 Jun 2022 14:46:59 -0400 Subject: DRTVWR-564: We don't need LLEventAPI to befriend LazyEventAPI. --- indra/llcommon/lleventapi.h | 4 ---- 1 file changed, 4 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventapi.h b/indra/llcommon/lleventapi.h index a019458553..25f6becd8b 100644 --- a/indra/llcommon/lleventapi.h +++ b/indra/llcommon/lleventapi.h @@ -37,8 +37,6 @@ namespace LL { - template - class LazyEventAPI; class LazyEventAPIParams; } @@ -52,8 +50,6 @@ class LL_COMMON_API LLEventAPI: public LLDispatchListener, { typedef LLDispatchListener lbase; typedef LLInstanceTracker ibase; - template - friend class LL::LazyEventAPI; public: -- cgit v1.2.3 From fdc0257acbde5a2d5bb201efcc8bb723df09daf8 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 21 Jun 2022 15:23:29 -0400 Subject: DRTVWR-564: Fix LLEventDispatcher::addMethod() for LazyEventAPI. A classic LLEventAPI subclass calls LLEventDispatcher::add() methods in its own constructor. At that point, addMethod() can reliably dynamic_cast its 'this' pointer to the new subclass. But because of the way LazyEventAPI queues up add() calls, they're invoked in the (new) LLEventAPI constructor itself. The subclass constructor body hasn't even started running, and LLEventDispatcher::addMethod()'s dynamic_cast to the LLEventAPI subclass returns nullptr. addMethod() claims the new subclass isn't derived from LLEventDispatcher, which is confusing since it is. It works to change addMethod()'s dynamic_cast to static_cast. Flesh out lazyeventapi_test.cpp. post() maps with "op" keys to actually try to engage the registered operation. Give the operation an observable side effect; use ensure_mumble() to verify. Also verify that LazyEventAPI has captured the subject LLEventAPI's metadata in a way we can retrieve. --- indra/llcommon/lleventdispatcher.h | 2 +- indra/llcommon/tests/lazyeventapi_test.cpp | 47 ++++++++++++++++++++++++++---- 2 files changed, 42 insertions(+), 7 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index ce9d3775cc..2e140329f3 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -329,7 +329,7 @@ private: void addMethod(const std::string& name, const std::string& desc, const METHOD& method, const LLSD& required) { - CLASS* downcast = dynamic_cast(this); + CLASS* downcast = static_cast(this); if (! downcast) { addFail(name, typeid(CLASS).name()); diff --git a/indra/llcommon/tests/lazyeventapi_test.cpp b/indra/llcommon/tests/lazyeventapi_test.cpp index 6639c5e540..4c78fd7105 100644 --- a/indra/llcommon/tests/lazyeventapi_test.cpp +++ b/indra/llcommon/tests/lazyeventapi_test.cpp @@ -19,18 +19,25 @@ // other Linden headers #include "../test/lltut.h" #include "llevents.h" +#include "llsdutil.h" + +// observable side effect, solely for testing +static LLSD data; // LLEventAPI listener subclass class MyListener: public LLEventAPI { public: + // need this trivial forwarding constructor + // (of course do any other initialization your subclass requires) MyListener(const LL::LazyEventAPIParams& params): LLEventAPI(params) {} - void get(const LLSD& event) + // example operation, registered by LazyEventAPI subclass below + void set_data(const LLSD& event) { - std::cout << "MyListener::get() got " << event << std::endl; + data = event["data"]; } }; @@ -40,14 +47,17 @@ class MyRegistrar: public LL::LazyEventAPI using super = LL::LazyEventAPI; using super::listener; public: + // LazyEventAPI subclass initializes like a classic LLEventAPI subclass + // constructor, with API name and desc plus add() calls for the defined + // operations MyRegistrar(): super("Test", "This is a test LLEventAPI") { - add("get", "This is a get operation", &listener::get); + add("set", "This is a set operation", &listener::set_data); } }; // Normally we'd declare a static instance of MyRegistrar -- but because we -// may want to test with and without, defer declaration to individual test +// want to test both with and without, defer declaration to individual test // methods. /***************************************************************************** @@ -57,6 +67,11 @@ namespace tut { struct lazyeventapi_data { + lazyeventapi_data() + { + // before every test, reset 'data' + data.clear(); + } ~lazyeventapi_data() { // after every test, reset LLEventPumps @@ -74,7 +89,8 @@ namespace tut // this is where the magic (should) happen // 'register' still a keyword until C++17 MyRegistrar regster; - LLEventPumps::instance().obtain("Test").post("hey"); + LLEventPumps::instance().obtain("Test").post(llsd::map("op", "set", "data", "hey")); + ensure_equals("failed to set data", data.asString(), "hey"); } template<> template<> @@ -84,6 +100,25 @@ namespace tut // Because the MyRegistrar declaration in test<1>() is local, because // it has been destroyed, we fully expect NOT to reach a MyListener // instance with this post. - LLEventPumps::instance().obtain("Test").post("moot"); + LLEventPumps::instance().obtain("Test").post(llsd::map("op", "set", "data", "moot")); + ensure("accidentally set data", ! data.isDefined()); + } + + template<> template<> + void object::test<3>() + { + set_test_name("LazyEventAPI metadata"); + MyRegistrar regster; + const MyRegistrar* found = nullptr; + for (const auto& registrar : LL::LazyEventAPIBase::instance_snapshot()) + if ((found = dynamic_cast(®istrar))) + break; + ensure("Failed to find MyRegistrar via LLInstanceTracker", found); + ensure_equals("wrong API name", found->mParams.name, "Test"); + ensure_contains("wrong API desc", found->mParams.desc, "test LLEventAPI"); + ensure_equals("wrong API field", found->mParams.field, "op"); + ensure_equals("failed to find operations", found->mOperations.size(), 1); + ensure_equals("wrong operation name", found->mOperations[0].first, "set"); + ensure_contains("wrong operation desc", found->mOperations[0].second, "set operation"); } } // namespace tut -- cgit v1.2.3 From fa3a67f56b15d81bfd22f744314a7d9aa35bf90e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 21 Jun 2022 15:50:08 -0400 Subject: DRTVWR-564: Remove implementation notes from before implementation. --- indra/llcommon/lazyeventapi.h | 24 ------------------------ 1 file changed, 24 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lazyeventapi.h b/indra/llcommon/lazyeventapi.h index 2e947899dc..7267a3e4ec 100644 --- a/indra/llcommon/lazyeventapi.h +++ b/indra/llcommon/lazyeventapi.h @@ -42,30 +42,6 @@ namespace LL boost::signals2::signal init; }; - // The tricky part is: can we capture a sequence of add() calls in the - // LazyEventAPI subclass constructor and then, in effect, replay those - // add() calls on instantiation of the registered LLEventAPI subclass? so - // we don't have to duplicate the add() calls in both constructors? - - // Derive a subclass from LazyEventAPI. Its constructor must pass - // LazyEventAPI's constructor the name, desc, field params. Moreover the - // constructor body must call add(name, desc, *args) for any of the - // LLEventDispatcher add() methods, referencing the LLEventAPI subclass - // methods. - - // LazyEventAPI will store the name, desc, field params for the overall - // LLEventAPI. It will support a single generic add() call accepting name, - // desc, parameter pack. - - // It will hold a std::vector> for each operation. - // It will make all these strings available to LLLeapListener. - - // Maybe what we want is to store a vector of callables (a - // boost::signals2!) and populate it with lambdas, each of which accepts - // LLEventAPI* and calls the relevant add() method by forwarding exactly - // the name, desc and parameter pack. Then, on constructing the target - // LLEventAPI, we just fire the signal, passing the new instance pointer. - /** * LazyEventAPIBase implements most of the functionality of LazyEventAPI * (q.v.), but we need the LazyEventAPI template subclass so we can accept -- cgit v1.2.3 From f9d810ac2a02ef96c843e214c7479146dd4f4157 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 21 Jun 2022 17:24:03 -0400 Subject: DRTVWR-564: Per NickyD, need not test static_cast result for nullptr. --- indra/llcommon/lleventdispatcher.cpp | 7 ------- indra/llcommon/lleventdispatcher.h | 10 +--------- 2 files changed, 1 insertion(+), 16 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index bc53ec3da0..7ba8c5ada7 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -586,13 +586,6 @@ void LLEventDispatcher::add(const std::string& name, const std::string& desc, new LLSDDispatchEntry(desc, callable, required)))); } -void LLEventDispatcher::addFail(const std::string& name, const std::string& classname) const -{ - LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << ")::add(" << name - << "): " << classname << " is not a subclass " - << "of LLEventDispatcher" << LL_ENDL; -} - /// Unregister a callable bool LLEventDispatcher::remove(const std::string& name) { diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 2e140329f3..6d1df86fea 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -330,16 +330,8 @@ private: const METHOD& method, const LLSD& required) { CLASS* downcast = static_cast(this); - if (! downcast) - { - addFail(name, typeid(CLASS).name()); - } - else - { - add(name, desc, boost::bind(method, downcast, _1), required); - } + add(name, desc, boost::bind(method, downcast, _1), required); } - void addFail(const std::string& name, const std::string& classname) const; std::string try_call_log(const std::string& key, const std::string& name, const LLSD& event) const; std::string try_call(const std::string& key, const std::string& name, -- cgit v1.2.3 From 6b53036f7499a4e42813378009050eaf02c0b69d Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 22 Jun 2022 10:51:11 -0400 Subject: DRTVWR-564: Allow LLLeapListener to report LazyEventAPIs too. One important factor in the design of LazyEventAPI was the desire to allow LLLeapListener to query metadata for an LLEventAPI even if it hasn't yet been instantiated by LazyEventAPI. That's why LazyEventAPI requires the same metadata required by a classic LLEventAPI. Instead of just publicly exposing its data members, give LazyEventAPI a query API mimicking LLEventAPI / LLEventDispatcher. Protect data members and private methods. Adapt lazyeventapi_test.cpp accordingly. Extend LLLeapListener::getAPIs() and getAPI() to look through LazyEventAPIBase instances after first checking existing LLEventAPI instances. Because the query API for LazyEventAPIBase mimics LLEventAPI's, extract getAPI()'s actual metadata reporting to a new internal template function reportAPI(). While we're touching LLLeapListener, we no longer need BOOST_FOREACH(). --- indra/llcommon/lazyeventapi.cpp | 19 ++++++++ indra/llcommon/lazyeventapi.h | 36 ++++++++++++--- indra/llcommon/llleaplistener.cpp | 70 ++++++++++++++++++++++-------- indra/llcommon/tests/lazyeventapi_test.cpp | 24 +++++++--- 4 files changed, 120 insertions(+), 29 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lazyeventapi.cpp b/indra/llcommon/lazyeventapi.cpp index aefc2db6da..028af9f33f 100644 --- a/indra/llcommon/lazyeventapi.cpp +++ b/indra/llcommon/lazyeventapi.cpp @@ -15,9 +15,11 @@ #include "lazyeventapi.h" // STL headers // std headers +#include // std::find_if // external library headers // other Linden headers #include "llevents.h" +#include "llsdutil.h" LL::LazyEventAPIBase::LazyEventAPIBase( const std::string& name, const std::string& desc, const std::string& field) @@ -51,3 +53,20 @@ LL::LazyEventAPIBase::~LazyEventAPIBase() LLEventPumps::instance().unregisterPumpFactory(mParams.name); } } + +LLSD LL::LazyEventAPIBase::getMetadata(const std::string& name) const +{ + // Since mOperations is a vector rather than a map, just search. + auto found = std::find_if(mOperations.begin(), mOperations.end(), + [&name](const auto& namedesc) + { return (namedesc.first == name); }); + if (found == mOperations.end()) + return {}; + + // LLEventDispatcher() supplements the returned metadata in different + // ways, depending on metadata provided to the specific add() method. + // Don't try to emulate all that. At some point we might consider more + // closely unifying LLEventDispatcher machinery with LazyEventAPI, but for + // now this will have to do. + return llsd::map("name", found->first, "desc", found->second); +} diff --git a/indra/llcommon/lazyeventapi.h b/indra/llcommon/lazyeventapi.h index 7267a3e4ec..a815b119f0 100644 --- a/indra/llcommon/lazyeventapi.h +++ b/indra/llcommon/lazyeventapi.h @@ -63,9 +63,6 @@ namespace LL LazyEventAPIBase& operator=(const LazyEventAPIBase&) = delete; LazyEventAPIBase& operator=(LazyEventAPIBase&&) = delete; - // actually instantiate the companion LLEventAPI subclass - virtual LLEventPump* construct(const std::string& name) = 0; - // capture add() calls we want to play back on LLEventAPI construction template void add(const std::string& name, const std::string& desc, ARGS&&... rest) @@ -96,12 +93,40 @@ namespace LL }); } + // The following queries mimic the LLEventAPI / LLEventDispatcher + // query API. + + // Get the string name of the subject LLEventAPI + std::string getName() const { return mParams.name; } + // Get the documentation string + std::string getDesc() const { return mParams.desc; } + // Retrieve the LLSD key we use for dispatching + std::string getDispatchKey() const { return mParams.field; } + + // operations + using NameDesc = std::pair; + + private: // metadata that might be queried by LLLeapListener - std::vector> mOperations; + std::vector mOperations; + + public: + using const_iterator = decltype(mOperations)::const_iterator; + const_iterator begin() const { return mOperations.begin(); } + const_iterator end() const { return mOperations.end(); } + LLSD getMetadata(const std::string& name) const; + + protected: // Params with which to instantiate the companion LLEventAPI subclass LazyEventAPIParams mParams; private: + // true if we successfully registered our LLEventAPI on construction + bool mRegistered; + + // actually instantiate the companion LLEventAPI subclass + virtual LLEventPump* construct(const std::string& name) = 0; + // Passing an overloaded function to any function that accepts an // arbitrary callable is a PITB because you have to specify the // correct overload. What we want is for the compiler to select the @@ -117,8 +142,6 @@ namespace LL { instance->add(std::forward(args)...); } - - bool mRegistered; }; /** @@ -168,6 +191,7 @@ namespace LL LazyEventAPIBase(name, desc, field) {} + private: LLEventPump* construct(const std::string& /*name*/) override { // base class has carefully assembled LazyEventAPIParams embedded diff --git a/indra/llcommon/llleaplistener.cpp b/indra/llcommon/llleaplistener.cpp index 11bfec1b31..471f52e91c 100644 --- a/indra/llcommon/llleaplistener.cpp +++ b/indra/llcommon/llleaplistener.cpp @@ -14,14 +14,16 @@ // associated header #include "llleaplistener.h" // STL headers -#include +#include // std::find_if #include +#include +#include // std headers // external library headers -#include // other Linden headers -#include "lluuid.h" +#include "lazyeventapi.h" #include "llsdutil.h" +#include "lluuid.h" #include "stringize.h" /***************************************************************************** @@ -110,7 +112,7 @@ LLLeapListener::~LLLeapListener() // value_type, and Bad Things would happen if you copied an // LLTempBoundListener. (Destruction of the original would disconnect the // listener, invalidating every stored connection.) - BOOST_FOREACH(ListenersMap::value_type& pair, mListeners) + for (ListenersMap::value_type& pair : mListeners) { pair.second.disconnect(); } @@ -208,31 +210,65 @@ void LLLeapListener::getAPIs(const LLSD& request) const { Response reply(LLSD(), request); + // first, traverse existing LLEventAPI instances + std::set instances; for (auto& ea : LLEventAPI::instance_snapshot()) { - LLSD info; - info["desc"] = ea.getDesc(); - reply[ea.getName()] = info; + // remember which APIs are actually instantiated + instances.insert(ea.getName()); + reply[ea.getName()] = llsd::map("desc", ea.getDesc()); + } + // supplement that with *potential* instances: that is, instances of + // LazyEventAPI that can each instantiate an LLEventAPI on demand + for (const auto& lea : LL::LazyEventAPIBase::instance_snapshot()) + { + // skip any LazyEventAPI that's already instantiated its LLEventAPI + if (instances.find(lea.getName()) == instances.end()) + { + reply[lea.getName()] = llsd::map("desc", lea.getDesc()); + } } } +// Because LazyEventAPI deliberately mimics LLEventAPI's query API, this +// function can be passed either -- even though they're unrelated types. +template +void reportAPI(LLEventAPI::Response& reply, const API& api) +{ + reply["name"] = api.getName(); + reply["desc"] = api.getDesc(); + reply["key"] = api.getDispatchKey(); + LLSD ops; + for (const auto& namedesc : api) + { + ops.append(api.getMetadata(namedesc.first)); + } + reply["ops"] = ops; +} + void LLLeapListener::getAPI(const LLSD& request) const { Response reply(LLSD(), request); - auto found = LLEventAPI::getInstance(request["api"]); - if (found) + // check first among existing LLEventAPI instances + auto foundea = LLEventAPI::getInstance(request["api"]); + if (foundea) + { + reportAPI(reply, *foundea); + } + else { - reply["name"] = found->getName(); - reply["desc"] = found->getDesc(); - reply["key"] = found->getDispatchKey(); - LLSD ops; - for (LLEventAPI::const_iterator oi(found->begin()), oend(found->end()); - oi != oend; ++oi) + // Here the requested LLEventAPI doesn't yet exist, but do we have a + // registered LazyEventAPI for it? + LL::LazyEventAPIBase::instance_snapshot snap; + auto foundlea = std::find_if(snap.begin(), snap.end(), + [api = request["api"].asString()] + (const auto& lea) + { return (lea.getName() == api); }); + if (foundlea != snap.end()) { - ops.append(found->getMetadata(oi->first)); + reportAPI(reply, *foundlea); } - reply["ops"] = ops; } } diff --git a/indra/llcommon/tests/lazyeventapi_test.cpp b/indra/llcommon/tests/lazyeventapi_test.cpp index 4c78fd7105..31b2d6d17f 100644 --- a/indra/llcommon/tests/lazyeventapi_test.cpp +++ b/indra/llcommon/tests/lazyeventapi_test.cpp @@ -109,16 +109,28 @@ namespace tut { set_test_name("LazyEventAPI metadata"); MyRegistrar regster; + // Of course we have 'regster' in hand; we don't need to search for + // it. But this next test verifies that we can find (all) LazyEventAPI + // instances using LazyEventAPIBase::instance_snapshot. Normally we + // wouldn't search; normally we'd just look at each instance in the + // loop body. const MyRegistrar* found = nullptr; for (const auto& registrar : LL::LazyEventAPIBase::instance_snapshot()) if ((found = dynamic_cast(®istrar))) break; ensure("Failed to find MyRegistrar via LLInstanceTracker", found); - ensure_equals("wrong API name", found->mParams.name, "Test"); - ensure_contains("wrong API desc", found->mParams.desc, "test LLEventAPI"); - ensure_equals("wrong API field", found->mParams.field, "op"); - ensure_equals("failed to find operations", found->mOperations.size(), 1); - ensure_equals("wrong operation name", found->mOperations[0].first, "set"); - ensure_contains("wrong operation desc", found->mOperations[0].second, "set operation"); + + ensure_equals("wrong API name", found->getName(), "Test"); + ensure_contains("wrong API desc", found->getDesc(), "test LLEventAPI"); + ensure_equals("wrong API field", found->getDispatchKey(), "op"); + // Normally we'd just iterate over *found. But for test purposes, + // actually capture the range of NameDesc pairs in a vector. + std::vector ops{ found->begin(), found->end() }; + ensure_equals("failed to find operations", ops.size(), 1); + ensure_equals("wrong operation name", ops[0].first, "set"); + ensure_contains("wrong operation desc", ops[0].second, "set operation"); + LLSD metadata{ found->getMetadata(ops[0].first) }; + ensure_equals("bad metadata name", metadata["name"].asString(), ops[0].first); + ensure_equals("bad metadata desc", metadata["desc"].asString(), ops[0].second); } } // namespace tut -- cgit v1.2.3 From 1559ad47924711f64ecd540bc605d9aa1f7221e6 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Fri, 24 Jun 2022 13:09:24 +0300 Subject: SL-17649 Icon for Material type in Inventory --- indra/llcommon/llassettype.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llassettype.cpp b/indra/llcommon/llassettype.cpp index 0bb1f1a0fd..f08cc18036 100644 --- a/indra/llcommon/llassettype.cpp +++ b/indra/llcommon/llassettype.cpp @@ -96,7 +96,7 @@ LLAssetDictionary::LLAssetDictionary() addEntry(LLAssetType::AT_WIDGET, new AssetEntry("WIDGET", "widget", "widget", false, false, false)); addEntry(LLAssetType::AT_PERSON, new AssetEntry("PERSON", "person", "person", false, false, false)); addEntry(LLAssetType::AT_SETTINGS, new AssetEntry("SETTINGS", "settings", "settings blob", true, true, true)); - addEntry(LLAssetType::AT_MATERIAL, new AssetEntry("MATERIAL", "material", "render material", true, true, true)); + addEntry(LLAssetType::AT_MATERIAL, new AssetEntry("MATERIAL", "material", "render material", true, true, true)); addEntry(LLAssetType::AT_UNKNOWN, new AssetEntry("UNKNOWN", "invalid", NULL, false, false, false)); addEntry(LLAssetType::AT_NONE, new AssetEntry("NONE", "-1", NULL, FALSE, FALSE, FALSE)); -- cgit v1.2.3 From 2c692f635da67990f842f20adf3b42d870d42fdf Mon Sep 17 00:00:00 2001 From: "Brad Payne (Vir Linden)" Date: Thu, 1 Sep 2022 15:11:56 +0100 Subject: DRTVWR-559 refcount max change --- indra/llcommon/llrefcount.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llrefcount.cpp b/indra/llcommon/llrefcount.cpp index 5cbd346411..6852b5536a 100644 --- a/indra/llcommon/llrefcount.cpp +++ b/indra/llcommon/llrefcount.cpp @@ -30,7 +30,7 @@ #include "llerror.h" // maximum reference count before sounding memory leak alarm -const S32 gMaxRefCount = 65536; +const S32 gMaxRefCount = S32_MAX; LLRefCount::LLRefCount(const LLRefCount& other) : mRef(0) -- cgit v1.2.3 From e0c226b04d6f2ac566a9ea5841509f7cdfa98c11 Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Thu, 1 Sep 2022 18:06:15 -0500 Subject: SL-18078, SL-18065 -- Experimentally allow uploading of lossless normal maps, fix for crash on shutdown. --- indra/llcommon/llqueuedthread.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llqueuedthread.cpp b/indra/llcommon/llqueuedthread.cpp index 155e32ebae..e5060a1076 100644 --- a/indra/llcommon/llqueuedthread.cpp +++ b/indra/llcommon/llqueuedthread.cpp @@ -146,7 +146,7 @@ S32 LLQueuedThread::updateQueue(F32 max_time_ms) // schedule a call to threadedUpdate for every call to updateQueue if (!isQuitting()) { - mRequestQueue.post([=]() + mRequestQueue.postIfOpen([=]() { LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qt - update"); mIdleThread = FALSE; -- cgit v1.2.3 From abf788175c451046f82cc9aeaddc894c47863ffa Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Tue, 6 Sep 2022 15:07:22 -0500 Subject: SL-18096 WIP -- partial support for double sided rendering. Shadow map and picking support TBD. --- indra/llcommon/llworkerthread.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llworkerthread.cpp b/indra/llcommon/llworkerthread.cpp index 02ce4823b8..bd2eb2089c 100644 --- a/indra/llcommon/llworkerthread.cpp +++ b/indra/llcommon/llworkerthread.cpp @@ -97,6 +97,7 @@ S32 LLWorkerThread::update(F32 max_time_ms) { if (worker->getFlags(LLWorkerClass::WCF_WORK_FINISHED)) { + worker->setFlags(LLWorkerClass::WCF_DELETE_REQUESTED); delete_list.push_back(worker); mDeleteList.erase(curiter); } -- cgit v1.2.3 From 7d426815019ac5394e5821f1c6ba374e12606aaa Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Fri, 30 Sep 2022 10:57:01 -0500 Subject: SL-18239 Unify PBR and non-PBR treatment of ambient/SSAO/irradiance. Restore SSAO to release version. --- indra/llcommon/llworkerthread.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llworkerthread.cpp b/indra/llcommon/llworkerthread.cpp index bd2eb2089c..cd4d587752 100644 --- a/indra/llcommon/llworkerthread.cpp +++ b/indra/llcommon/llworkerthread.cpp @@ -73,6 +73,7 @@ void LLWorkerThread::clearDeleteList() { (*iter)->mRequestHandle = LLWorkerThread::nullHandle(); (*iter)->clearFlags(LLWorkerClass::WCF_HAVE_WORK); + (*iter)->clearFlags(LLWorkerClass::WCF_WORKING); delete *iter ; } mDeleteList.clear() ; -- cgit v1.2.3 From be1cdc1aaa67eca71fee8cbbc16b4c85bcbdb258 Mon Sep 17 00:00:00 2001 From: Geenz Date: Wed, 5 Oct 2022 09:48:18 -0700 Subject: Initial pass at adding KHR_debug support This still needs some work - I'm not super satisfied with the overall structure of the code. Will continue to iterate as I add in proper RenderDoc support. --- indra/llcommon/llprofiler.h | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llprofiler.h b/indra/llcommon/llprofiler.h index c0f5868db3..ac20209062 100644 --- a/indra/llcommon/llprofiler.h +++ b/indra/llcommon/llprofiler.h @@ -153,12 +153,16 @@ extern thread_local bool gProfilerEnabled; // disable memory tracking (incompatible with GPU tracing #define LL_PROFILE_ALLOC(ptr, size) (void)(ptr); (void)(size); #define LL_PROFILE_FREE(ptr) (void)(ptr); + +#define LL_LABEL_OBJECT_GL(type, name, length, label) glObjectLabel(type, name, length, label) #else #define LL_PROFILE_GPU_ZONE(name) (void)name; #define LL_PROFILE_GPU_ZONEC(name,color) (void)name;(void)color; #define LL_PROFILER_GPU_COLLECT #define LL_PROFILER_GPU_CONTEXT +#define LL_LABEL_OBJECT_GL(type, name, length, label) + #if LL_PROFILER_CONFIG > 1 #define LL_PROFILE_ALLOC(ptr, size) TracyAlloc(ptr, size) #define LL_PROFILE_FREE(ptr) TracyFree(ptr) -- cgit v1.2.3 From ec5009d70eb0fd729126b2321b9d7d6118741573 Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Thu, 13 Oct 2022 19:51:40 -0500 Subject: SL-18190 Potential fix for sapping CPU when "sleeping" --- indra/llcommon/lltimer.cpp | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lltimer.cpp b/indra/llcommon/lltimer.cpp index 39dfee3755..8739eeef00 100644 --- a/indra/llcommon/lltimer.cpp +++ b/indra/llcommon/lltimer.cpp @@ -65,6 +65,9 @@ LLTimer* LLTimer::sTimer = NULL; //--------------------------------------------------------------------------- #if LL_WINDOWS + + +#if 0 void ms_sleep(U32 ms) { LL_PROFILE_ZONE_SCOPED; @@ -83,6 +86,31 @@ U32 micro_sleep(U64 us, U32 max_yields) ms_sleep((U32)(us / 1000)); return 0; } + +#else + +U32 micro_sleep(U64 us, U32 max_yields) +{ + LL_PROFILE_ZONE_SCOPED + LARGE_INTEGER ft; + ft.QuadPart = -static_cast(us * 10); // '-' using relative time + + HANDLE timer = CreateWaitableTimer(NULL, TRUE, NULL); + SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0); + WaitForSingleObject(timer, INFINITE); + CloseHandle(timer); + + return 0; +} + +void ms_sleep(U32 ms) +{ + LL_PROFILE_ZONE_SCOPED + micro_sleep(ms * 1000, 0); +} + +#endif + #elif LL_LINUX || LL_DARWIN static void _sleep_loop(struct timespec& thiswait) { -- cgit v1.2.3 From 0451d51f4da9780e2f650b67672a29b3d07cb386 Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Thu, 27 Oct 2022 11:27:21 -0500 Subject: SL-18459 WIP -- fix for assert in setGLTFMaterial --- indra/llcommon/llpointer.h | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llpointer.h b/indra/llcommon/llpointer.h index 9a6453ea48..f9de0c7929 100644 --- a/indra/llcommon/llpointer.h +++ b/indra/llcommon/llpointer.h @@ -340,4 +340,28 @@ private: bool mStayUnique; }; + +// boost hash adapter +template +struct boost::hash> +{ + typedef LLPointer argument_type; + typedef std::size_t result_type; + result_type operator()(argument_type const& s) const + { + return (std::size_t) s.get(); + } +}; + +// Adapt boost hash to std hash +namespace std +{ + template struct hash> + { + std::size_t operator()(LLPointer const& s) const noexcept + { + return boost::hash>()(s); + } + }; +} #endif -- cgit v1.2.3 From 8d2ac419b22c8c9475f2efb312dd198ac8eb9fb7 Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Wed, 16 Nov 2022 14:49:17 -0600 Subject: SL-18154 Profile guided optimizations vs release viewer. Trim some unused abilities and remove some more fast timers. --- indra/llcommon/llprofiler.h | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llprofiler.h b/indra/llcommon/llprofiler.h index ac20209062..c03b2e93f4 100644 --- a/indra/llcommon/llprofiler.h +++ b/indra/llcommon/llprofiler.h @@ -86,8 +86,12 @@ extern thread_local bool gProfilerEnabled; #define TRACY_ONLY_IPV4 1 #include "Tracy.hpp" - // Disable memory tracing when enabled, but enabled + // Enable OpenGL profiling #define LL_PROFILER_ENABLE_TRACY_OPENGL 1 + + // Enable RenderDoc labeling + #define LL_PROFILER_ENABLE_RENDER_DOC 0 + #endif #if LL_PROFILER_CONFIGURATION == LL_PROFILER_CONFIG_TRACY @@ -153,8 +157,6 @@ extern thread_local bool gProfilerEnabled; // disable memory tracking (incompatible with GPU tracing #define LL_PROFILE_ALLOC(ptr, size) (void)(ptr); (void)(size); #define LL_PROFILE_FREE(ptr) (void)(ptr); - -#define LL_LABEL_OBJECT_GL(type, name, length, label) glObjectLabel(type, name, length, label) #else #define LL_PROFILE_GPU_ZONE(name) (void)name; #define LL_PROFILE_GPU_ZONEC(name,color) (void)name;(void)color; @@ -173,6 +175,12 @@ extern thread_local bool gProfilerEnabled; #endif +#if LL_PROFILER_ENABLE_RENDER_DOC +#define LL_LABEL_OBJECT_GL(type, name, length, label) glObjectLabel(type, name, length, label) +#else +#define LL_LABEL_OBJECT_GL(type, name, length, label) +#endif + #include "llprofilercategories.h" #endif // LL_PROFILER_H -- cgit v1.2.3 From 87bb72a47a1fc64b98e498120e332de76e6a9211 Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Wed, 30 Nov 2022 13:25:00 -0600 Subject: SL-18154 WIP -- CPU sampling (AMD uProf) profile guided optimizations to reduce CPU usage of background threads. --- indra/llcommon/llqueuedthread.cpp | 9 +++++++-- indra/llcommon/llthread.cpp | 10 ++++++++++ indra/llcommon/lltimer.cpp | 4 ++++ 3 files changed, 21 insertions(+), 2 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llqueuedthread.cpp b/indra/llcommon/llqueuedthread.cpp index e5060a1076..9b1de2e9a5 100644 --- a/indra/llcommon/llqueuedthread.cpp +++ b/indra/llcommon/llqueuedthread.cpp @@ -477,9 +477,14 @@ void LLQueuedThread::processRequest(LLQueuedThread::QueuedRequest* req) mRequestQueue.post([=] { LL_PROFILE_ZONE_NAMED("processRequest - retry"); - while (LL::WorkQueue::TimePoint::clock::now() < retry_time) + if (LL::WorkQueue::TimePoint::clock::now() < retry_time) { - std::this_thread::yield(); //note: don't use LLThread::yield here to avoid + auto sleep_time = std::chrono::duration_cast(retry_time - LL::WorkQueue::TimePoint::clock::now()); + + if (sleep_time.count() > 0) + { + ms_sleep(sleep_time.count()); + } } processRequest(req); }); diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp index a807acc56e..4eaa05c335 100644 --- a/indra/llcommon/llthread.cpp +++ b/indra/llcommon/llthread.cpp @@ -42,6 +42,7 @@ #ifdef LL_WINDOWS + const DWORD MS_VC_EXCEPTION=0x406D1388; #pragma pack(push,8) @@ -133,6 +134,15 @@ void LLThread::threadRun() { #ifdef LL_WINDOWS set_thread_name(-1, mName.c_str()); + +#if 0 // probably a bad idea, see usage of SetThreadIdealProcessor in LLWindowWin32) + HANDLE hThread = GetCurrentThread(); + if (hThread) + { + SetThreadAffinityMask(hThread, (DWORD_PTR) 0xFFFFFFFFFFFFFFFE); + } +#endif + #endif LL_PROFILER_SET_THREAD_NAME( mName.c_str() ); diff --git a/indra/llcommon/lltimer.cpp b/indra/llcommon/lltimer.cpp index 8739eeef00..24eaa4c1a9 100644 --- a/indra/llcommon/lltimer.cpp +++ b/indra/llcommon/lltimer.cpp @@ -92,6 +92,7 @@ U32 micro_sleep(U64 us, U32 max_yields) U32 micro_sleep(U64 us, U32 max_yields) { LL_PROFILE_ZONE_SCOPED +#if 0 LARGE_INTEGER ft; ft.QuadPart = -static_cast(us * 10); // '-' using relative time @@ -99,6 +100,9 @@ U32 micro_sleep(U64 us, U32 max_yields) SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0); WaitForSingleObject(timer, INFINITE); CloseHandle(timer); +#else + Sleep(us / 1000); +#endif return 0; } -- cgit v1.2.3 From e98091e29859ee6a784ee6920048837f2b4536c2 Mon Sep 17 00:00:00 2001 From: RunitaiLinden Date: Wed, 7 Dec 2022 12:53:59 -0600 Subject: DRTVWR-559: Try using custom fiber scheduler for ThreadPool threads. (#30) Co-authored-by: Nat Goodspeed --- indra/llcommon/threadpool.cpp | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/threadpool.cpp b/indra/llcommon/threadpool.cpp index f49dd40a8b..b0d2016be0 100644 --- a/indra/llcommon/threadpool.cpp +++ b/indra/llcommon/threadpool.cpp @@ -23,6 +23,37 @@ #include "llsd.h" #include "stringize.h" +#include + +/***************************************************************************** +* Custom fiber scheduler for worker threads +*****************************************************************************/ +// As of 2022-12-06, each of our worker threads only runs a single (default) +// fiber: we don't launch explicit fibers within worker threads, nor do we +// anticipate doing so. So a worker thread that's simply waiting for incoming +// tasks should really sleep a little. Override the default fiber scheduler to +// implement that. +struct sleepy_robin: public boost::fibers::algo::round_robin +{ + virtual void suspend_until( std::chrono::steady_clock::time_point const&) noexcept + { + // round_robin holds a std::condition_variable, and + // round_robin::suspend_until() calls + // std::condition_variable::wait_until(). On Windows, that call seems + // busier than it ought to be. Try just sleeping. + Sleep(1); + } + + virtual void notify() noexcept + { + // Since our Sleep() call above will wake up on its own, we need not + // take any special action to wake it. + } +}; + +/***************************************************************************** +* ThreadPool +*****************************************************************************/ LL::ThreadPool::ThreadPool(const std::string& name, size_t threads, size_t capacity): super(name), mQueue(name, capacity), @@ -80,6 +111,11 @@ void LL::ThreadPool::close() void LL::ThreadPool::run(const std::string& name) { +#if LL_WINDOWS + // Try using sleepy_robin fiber scheduler. + boost::fibers::use_scheduling_algorithm(); +#endif // LL_WINDOWS + LL_DEBUGS("ThreadPool") << name << " starting" << LL_ENDL; run(); LL_DEBUGS("ThreadPool") << name << " stopping" << LL_ENDL; -- cgit v1.2.3 From e3b34fec6962e6deda3dd9dd83bf9fa20ab594af Mon Sep 17 00:00:00 2001 From: Brad Kittenbrink Date: Thu, 8 Dec 2022 12:53:51 -0800 Subject: Fix for non-windows build of DRTVWR-559 use usleep() for sleepy_robin scheduler --- indra/llcommon/threadpool.cpp | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/threadpool.cpp b/indra/llcommon/threadpool.cpp index b0d2016be0..1b29b8b69f 100644 --- a/indra/llcommon/threadpool.cpp +++ b/indra/llcommon/threadpool.cpp @@ -37,11 +37,17 @@ struct sleepy_robin: public boost::fibers::algo::round_robin { virtual void suspend_until( std::chrono::steady_clock::time_point const&) noexcept { +#if LL_WINDOWS // round_robin holds a std::condition_variable, and // round_robin::suspend_until() calls // std::condition_variable::wait_until(). On Windows, that call seems // busier than it ought to be. Try just sleeping. Sleep(1); +#else + // currently unused other than windows, but might as well have something here + // different units than Sleep(), but we actually just want to sleep for any de-minimis duration + usleep(1); +#endif } virtual void notify() noexcept -- cgit v1.2.3 From 00478b1e7671cb109771a1ad4fb40d47d15ab756 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 9 Dec 2022 13:16:39 -0500 Subject: DRTVWR-559: Introduce LLInstanceTrackerSubclass mediator class. Deriving your tracked class T from LLInstanceTracker gives you T::getInstance() et al. But what about a subclass S derived from T? S::getInstance() still delivers a pointer to T, requiring explicit downcast. And so on for other LLInstanceTracker methods. Instead, derive S from LLInstanceTrackerSubclass. This implies that S is a grandchild class of T, but it also recasts the LLInstanceTracker methods to deliver results for S rather than for T. --- indra/llcommon/CMakeLists.txt | 1 + indra/llcommon/llinstancetracker.h | 97 ++++++++++++++++++----------- indra/llcommon/llinstancetrackersubclass.h | 98 ++++++++++++++++++++++++++++++ 3 files changed, 160 insertions(+), 36 deletions(-) create mode 100644 indra/llcommon/llinstancetrackersubclass.h (limited to 'indra/llcommon') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 21998f0b78..0fe61108ff 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -189,6 +189,7 @@ set(llcommon_HEADER_FILES llinitdestroyclass.h llinitparam.h llinstancetracker.h + llinstancetrackersubclass.h llkeybind.h llkeythrottle.h llleap.h diff --git a/indra/llcommon/llinstancetracker.h b/indra/llcommon/llinstancetracker.h index 02535a59e7..97f7817e74 100644 --- a/indra/llcommon/llinstancetracker.h +++ b/indra/llcommon/llinstancetracker.h @@ -104,22 +104,26 @@ public: return LockStatic()->mMap.size(); } - // snapshot of std::pair> pairs - class snapshot + // snapshot of std::pair> pairs, for + // some SUBCLASS derived from T + template + class snapshot_of { // It's very important that what we store in this snapshot are // weak_ptrs, NOT shared_ptrs. That's how we discover whether any // instance has been deleted during the lifespan of a snapshot. typedef std::vector> VectorType; - // Dereferencing our iterator produces a std::shared_ptr for each - // instance that still exists. Since we store weak_ptrs, that involves - // two chained transformations: + // Dereferencing the iterator we publish produces a + // std::shared_ptr for each instance that still exists. + // Since we store weak_ptr, that involves two chained + // transformations: // - a transform_iterator to lock the weak_ptr and return a shared_ptr - // - a filter_iterator to skip any shared_ptr that has become invalid. + // - a filter_iterator to skip any shared_ptr that has become + // invalid or references any T instance that isn't SUBCLASS. // It is very important that we filter lazily, that is, during // traversal. Any one of our stored weak_ptrs might expire during // traversal. - typedef std::pair strong_pair; + typedef std::pair> strong_pair; // Note for future reference: nat has not yet had any luck (up to // Boost 1.67) trying to use boost::transform_iterator with a hand- // coded functor, only with actual functions. In my experience, an @@ -127,7 +131,7 @@ public: // result_type typedef. But this works. static strong_pair strengthen(typename VectorType::value_type& pair) { - return { pair.first, pair.second.lock() }; + return { pair.first, std::dynamic_pointer_cast(pair.second.lock()) }; } static bool dead_skipper(const strong_pair& pair) { @@ -135,7 +139,7 @@ public: } public: - snapshot(): + snapshot_of(): // populate our vector with a snapshot of (locked!) InstanceMap // note, this assigns pair to pair mData(mLock->mMap.begin(), mLock->mMap.end()) @@ -184,44 +188,51 @@ public: #endif // LL_WINDOWS VectorType mData; }; + using snapshot = snapshot_of; - // iterate over this for references to each instance - class instance_snapshot: public snapshot + // iterate over this for references to each SUBCLASS instance + template + class instance_snapshot_of: public snapshot_of { private: - static T& instance_getter(typename snapshot::iterator::reference pair) + using super = snapshot_of; + static T& instance_getter(typename super::iterator::reference pair) { return *pair.second; } public: typedef boost::transform_iterator iterator; - iterator begin() { return iterator(snapshot::begin(), instance_getter); } - iterator end() { return iterator(snapshot::end(), instance_getter); } + typename super::iterator> iterator; + iterator begin() { return iterator(super::begin(), instance_getter); } + iterator end() { return iterator(super::end(), instance_getter); } void deleteAll() { - for (auto it(snapshot::begin()), end(snapshot::end()); it != end; ++it) + for (auto it(super::begin()), end(super::end()); it != end; ++it) { delete it->second.get(); } } - }; + }; + using instance_snapshot = instance_snapshot_of; // iterate over this for each key - class key_snapshot: public snapshot + template + class key_snapshot_of: public snapshot_of { private: - static KEY key_getter(typename snapshot::iterator::reference pair) + using super = snapshot_of; + static KEY key_getter(typename super::iterator::reference pair) { return pair.first; } public: typedef boost::transform_iterator iterator; - iterator begin() { return iterator(snapshot::begin(), key_getter); } - iterator end() { return iterator(snapshot::end(), key_getter); } + typename super::iterator> iterator; + iterator begin() { return iterator(super::begin(), key_getter); } + iterator end() { return iterator(super::end(), key_getter); } }; + using key_snapshot = key_snapshot_of; static ptr_t getInstance(const KEY& k) { @@ -368,22 +379,25 @@ public: return LockStatic()->mSet.size(); } - // snapshot of std::shared_ptr pointers - class snapshot + // snapshot of std::shared_ptr pointers + template + class snapshot_of { // It's very important that what we store in this snapshot are // weak_ptrs, NOT shared_ptrs. That's how we discover whether any // instance has been deleted during the lifespan of a snapshot. typedef std::vector VectorType; - // Dereferencing our iterator produces a std::shared_ptr for each - // instance that still exists. Since we store weak_ptrs, that involves - // two chained transformations: + // Dereferencing the iterator we publish produces a + // std::shared_ptr for each instance that still exists. + // Since we store weak_ptrs, that involves two chained + // transformations: // - a transform_iterator to lock the weak_ptr and return a shared_ptr - // - a filter_iterator to skip any shared_ptr that has become invalid. - typedef std::shared_ptr strong_ptr; + // - a filter_iterator to skip any shared_ptr that has become invalid + // or references any T instance that isn't SUBCLASS. + typedef std::shared_ptr strong_ptr; static strong_ptr strengthen(typename VectorType::value_type& ptr) { - return ptr.lock(); + return std::dynamic_pointer_cast(ptr.lock()); } static bool dead_skipper(const strong_ptr& ptr) { @@ -391,7 +405,7 @@ public: } public: - snapshot(): + snapshot_of(): // populate our vector with a snapshot of (locked!) InstanceSet // note, this assigns stored shared_ptrs to weak_ptrs for snapshot mData(mLock->mSet.begin(), mLock->mSet.end()) @@ -437,22 +451,33 @@ public: #endif // LL_WINDOWS VectorType mData; }; + using snapshot = snapshot_of; // iterate over this for references to each instance - struct instance_snapshot: public snapshot + template + class instance_snapshot_of: public snapshot_of { - typedef boost::indirect_iterator iterator; - iterator begin() { return iterator(snapshot::begin()); } - iterator end() { return iterator(snapshot::end()); } + private: + using super = snapshot_of; + + public: + typedef boost::indirect_iterator iterator; + iterator begin() { return iterator(super::begin()); } + iterator end() { return iterator(super::end()); } void deleteAll() { - for (auto it(snapshot::begin()), end(snapshot::end()); it != end; ++it) + for (auto it(super::begin()), end(super::end()); it != end; ++it) { delete it->get(); } } }; + using instance_snapshot = instance_snapshot_of; + // key_snapshot_of isn't really meaningful, but define it anyway to avoid + // requiring two different LLInstanceTrackerSubclass implementations. + template + using key_snapshot_of = instance_snapshot_of; protected: LLInstanceTracker() diff --git a/indra/llcommon/llinstancetrackersubclass.h b/indra/llcommon/llinstancetrackersubclass.h new file mode 100644 index 0000000000..ea9a38200f --- /dev/null +++ b/indra/llcommon/llinstancetrackersubclass.h @@ -0,0 +1,98 @@ +/** + * @file llinstancetrackersubclass.h + * @author Nat Goodspeed + * @date 2022-12-09 + * @brief Intermediate class to get subclass-specific types from + * LLInstanceTracker instance-retrieval methods. + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Copyright (c) 2022, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLINSTANCETRACKERSUBCLASS_H) +#define LL_LLINSTANCETRACKERSUBCLASS_H + +#include // std::shared_ptr, std::weak_ptr + +/** + * Derive your subclass S of a subclass T of LLInstanceTracker from + * LLInstanceTrackerSubclass to perform appropriate downcasting and + * filtering for LLInstanceTracker access methods. + * + * LLInstanceTracker uses CRTP, so that getWeak(), getInstance(), snapshot + * and instance_snapshot return pointers and references to T. The trouble is + * that subclasses T0 and T1 derived from T also get pointers and references + * to their base class T, requiring explicit downcasting. Moreover, + * T0::getInstance() shouldn't find an instance of any T subclass other than + * T0. Nor should T0::snapshot. + * + * @code + * class Tracked: public LLInstanceTracker + * { + * private: + * using super = LLInstanceTracker; + * public: + * Tracked(const std::string& name): super(name) {} + * // All references to Tracked::ptr_t, Tracked::getInstance() etc. + * // appropriately use Tracked. + * // ... + * }; + * + * // But now we derive SubTracked from Tracked. We need SubTracked::ptr_t, + * // SubTracked::getInstance() etc. to use SubTracked, not Tracked. + * // This LLInstanceTrackerSubclass specialization is itself derived from + * // Tracked. + * class SubTracked: public LLInstanceTrackerSubclass + * { + * private: + * using super = LLInstanceTrackerSubclass; + * public: + * // LLInstanceTrackerSubclass's constructor forwards to Tracked's. + * SubTracked(const std::string& name): super(name) {} + * // SubTracked::getInstance() returns std::shared_ptr, etc. + * // ... + * @endcode + */ +template +class LLInstanceTrackerSubclass: public T +{ +public: + using ptr_t = std::shared_ptr; + using weak_t = std::weak_ptr; + + // forward any constructor call to the corresponding T ctor + template + LLInstanceTrackerSubclass(ARGS&&... args): + T(std::forward(args)...) + {} + + weak_t getWeak() + { + // call base-class getWeak(), try to lock, downcast to SUBCLASS + return std::dynamic_pointer_cast(T::getWeak().lock()); + } + + template + static ptr_t getInstance(const KEY& k) + { + return std::dynamic_pointer_cast(T::getInstance(k)); + } + + using snapshot = typename T::template snapshot_of; + using instance_snapshot = typename T::template instance_snapshot_of; + using key_snapshot = typename T::template key_snapshot_of; + + static size_t instanceCount() + { + // T::instanceCount() lies because our snapshot, et al., won't + // necessarily return all the T instances -- only those that are also + // SUBCLASS instances. Count those. + size_t count = 0; + for (const auto& pair : snapshot()) + ++count; + return count; + } +}; + +#endif /* ! defined(LL_LLINSTANCETRACKERSUBCLASS_H) */ -- cgit v1.2.3 From fc424a0db90fd2d2e44e85a19750ad6eaa57b28a Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 9 Dec 2022 13:21:45 -0500 Subject: SL-18809: Add WorkSchedule; remove timestamps from WorkQueue. For work queues that don't need timestamped tasks, eliminate the overhead of a priority queue ordered by timestamp. Timestamped task support moves to WorkSchedule. WorkQueue is a simpler queue that just waits for work. Both WorkQueue and WorkSchedule can be accessed via new WorkQueueBase API. Of course the WorkQueueBase API doesn't deal with timestamps, but a WorkSchedule can be accessed directly to post timestamped tasks and then handled normally (e.g. by ThreadPool) to run them. Most ThreadPool functionality migrates to new ThreadPoolBase class, with template subclass ThreadPoolUsing or ThreadPoolUsing depending on need. ThreadPool is now an alias for ThreadPoolUsing. Importantly, ThreadPoolUsing::getQueue() delivers a reference to the specific queue subclass type, so you can post timestamped tasks on a queue retrieved from ThreadPoolUsing::getQueue(). Since ThreadPool is no longer a simple class but an alias for a particular template specialization, introduce threadpool_fwd.h to forward-declare it. Recast workqueue_test.cpp to exercise WorkSchedule, since some of the tests are time-based. A future todo would be to exercise each applicable test with both WorkQueue and WorkSchedule. --- indra/llcommon/CMakeLists.txt | 1 + indra/llcommon/tests/workqueue_test.cpp | 26 +- indra/llcommon/threadpool.cpp | 27 ++- indra/llcommon/threadpool.h | 60 ++++- indra/llcommon/threadpool_fwd.h | 25 ++ indra/llcommon/workqueue.cpp | 194 +++++++++++---- indra/llcommon/workqueue.h | 416 +++++++++++++++++++------------- 7 files changed, 493 insertions(+), 256 deletions(-) create mode 100644 indra/llcommon/threadpool_fwd.h (limited to 'indra/llcommon') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 0fe61108ff..96fdb1f924 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -262,6 +262,7 @@ set(llcommon_HEADER_FILES stdtypes.h stringize.h threadpool.h + threadpool_fwd.h threadsafeschedule.h timer.h tuple.h diff --git a/indra/llcommon/tests/workqueue_test.cpp b/indra/llcommon/tests/workqueue_test.cpp index 1d73f7aa0d..41aa858084 100644 --- a/indra/llcommon/tests/workqueue_test.cpp +++ b/indra/llcommon/tests/workqueue_test.cpp @@ -38,7 +38,7 @@ namespace tut { struct workqueue_data { - WorkQueue queue{"queue"}; + WorkSchedule queue{"queue"}; }; typedef test_group workqueue_group; typedef workqueue_group::object object; @@ -49,8 +49,8 @@ namespace tut { set_test_name("name"); ensure_equals("didn't capture name", queue.getKey(), "queue"); - ensure("not findable", WorkQueue::getInstance("queue") == queue.getWeak().lock()); - WorkQueue q2; + ensure("not findable", WorkSchedule::getInstance("queue") == queue.getWeak().lock()); + WorkSchedule q2; ensure("has no name", LLStringUtil::startsWith(q2.getKey(), "WorkQueue")); } @@ -73,16 +73,16 @@ namespace tut { set_test_name("postEvery"); // record of runs - using Shared = std::deque; + using Shared = std::deque; // This is an example of how to share data between the originator of - // postEvery(work) and the work item itself, since usually a WorkQueue + // postEvery(work) and the work item itself, since usually a WorkSchedule // is used to dispatch work to a different thread. Neither of them // should call any of LLCond's wait methods: you don't want to stall // either the worker thread or the originating thread (conventionally // main). Use LLCond or a subclass even if all you want to do is // signal the work item that it can quit; consider LLOneShotCond. LLCond data; - auto start = WorkQueue::TimePoint::clock::now(); + auto start = WorkSchedule::TimePoint::clock::now(); auto interval = 100ms; queue.postEvery( interval, @@ -93,7 +93,7 @@ namespace tut data.update_one( [](Shared& data) { - data.push_back(WorkQueue::TimePoint::clock::now()); + data.push_back(WorkSchedule::TimePoint::clock::now()); }); // by the 3rd call, return false to stop return (++count < 3); @@ -102,7 +102,7 @@ namespace tut // postEvery() running, so run until we have exhausted the iterations // or we time out waiting for (auto finish = start + 10*interval; - WorkQueue::TimePoint::clock::now() < finish && + WorkSchedule::TimePoint::clock::now() < finish && data.get([](const Shared& data){ return data.size(); }) < 3; ) { queue.runPending(); @@ -139,8 +139,8 @@ namespace tut void object::test<4>() { set_test_name("postTo"); - WorkQueue main("main"); - auto qptr = WorkQueue::getInstance("queue"); + WorkSchedule main("main"); + auto qptr = WorkSchedule::getInstance("queue"); int result = 0; main.postTo( qptr, @@ -171,8 +171,8 @@ namespace tut void object::test<5>() { set_test_name("postTo with void return"); - WorkQueue main("main"); - auto qptr = WorkQueue::getInstance("queue"); + WorkSchedule main("main"); + auto qptr = WorkSchedule::getInstance("queue"); std::string observe; main.postTo( qptr, @@ -194,7 +194,7 @@ namespace tut std::string stored; // Try to call waitForResult() on this thread's main coroutine. It // should throw because the main coroutine must service the queue. - auto what{ catch_what( + auto what{ catch_what( [this, &stored](){ stored = queue.waitForResult( [](){ return "should throw"; }); }) }; ensure("lambda should not have run", stored.empty()); diff --git a/indra/llcommon/threadpool.cpp b/indra/llcommon/threadpool.cpp index f49dd40a8b..856306d8f4 100644 --- a/indra/llcommon/threadpool.cpp +++ b/indra/llcommon/threadpool.cpp @@ -23,14 +23,15 @@ #include "llsd.h" #include "stringize.h" -LL::ThreadPool::ThreadPool(const std::string& name, size_t threads, size_t capacity): +LL::ThreadPoolBase::ThreadPoolBase(const std::string& name, size_t threads, + WorkQueueBase* queue): super(name), - mQueue(name, capacity), mName("ThreadPool:" + name), - mThreadCount(getConfiguredWidth(name, threads)) + mThreadCount(getConfiguredWidth(name, threads)), + mQueue(queue) {} -void LL::ThreadPool::start() +void LL::ThreadPoolBase::start() { for (size_t i = 0; i < mThreadCount; ++i) { @@ -58,17 +59,17 @@ void LL::ThreadPool::start() }); } -LL::ThreadPool::~ThreadPool() +LL::ThreadPoolBase::~ThreadPoolBase() { close(); } -void LL::ThreadPool::close() +void LL::ThreadPoolBase::close() { - if (! mQueue.isClosed()) + if (! mQueue->isClosed()) { LL_DEBUGS("ThreadPool") << mName << " closing queue and joining threads" << LL_ENDL; - mQueue.close(); + mQueue->close(); for (auto& pair: mThreads) { LL_DEBUGS("ThreadPool") << mName << " waiting on thread " << pair.first << LL_ENDL; @@ -78,20 +79,20 @@ void LL::ThreadPool::close() } } -void LL::ThreadPool::run(const std::string& name) +void LL::ThreadPoolBase::run(const std::string& name) { LL_DEBUGS("ThreadPool") << name << " starting" << LL_ENDL; run(); LL_DEBUGS("ThreadPool") << name << " stopping" << LL_ENDL; } -void LL::ThreadPool::run() +void LL::ThreadPoolBase::run() { - mQueue.runUntilClose(); + mQueue->runUntilClose(); } //static -size_t LL::ThreadPool::getConfiguredWidth(const std::string& name, size_t dft) +size_t LL::ThreadPoolBase::getConfiguredWidth(const std::string& name, size_t dft) { LLSD poolSizes; try @@ -132,7 +133,7 @@ size_t LL::ThreadPool::getConfiguredWidth(const std::string& name, size_t dft) } //static -size_t LL::ThreadPool::getWidth(const std::string& name, size_t dft) +size_t LL::ThreadPoolBase::getWidth(const std::string& name, size_t dft) { auto instance{ getInstance(name) }; if (instance) diff --git a/indra/llcommon/threadpool.h b/indra/llcommon/threadpool.h index b49d511257..60f4a0ce1b 100644 --- a/indra/llcommon/threadpool.h +++ b/indra/llcommon/threadpool.h @@ -13,7 +13,9 @@ #if ! defined(LL_THREADPOOL_H) #define LL_THREADPOOL_H +#include "threadpool_fwd.h" #include "workqueue.h" +#include // std::unique_ptr #include #include #include // std::pair @@ -22,26 +24,24 @@ namespace LL { - class ThreadPool: public LLInstanceTracker + class ThreadPoolBase: public LLInstanceTracker { private: - using super = LLInstanceTracker; + using super = LLInstanceTracker; + public: /** - * Pass ThreadPool a string name. This can be used to look up the + * Pass ThreadPoolBase a string name. This can be used to look up the * relevant WorkQueue. * * The number of threads you pass sets the compile-time default. But * if the user has overridden the LLSD map in the "ThreadPoolSizes" * setting with a key matching this ThreadPool name, that setting * overrides this parameter. - * - * Pass an explicit capacity to limit the size of the queue. - * Constraining the queue can cause a submitter to block. Do not - * constrain any ThreadPool accepting work from the main thread. */ - ThreadPool(const std::string& name, size_t threads=1, size_t capacity=1024*1024); - virtual ~ThreadPool(); + ThreadPoolBase(const std::string& name, size_t threads, + WorkQueueBase* queue); + virtual ~ThreadPoolBase(); /** * Launch the ThreadPool. Until this call, a constructed ThreadPool @@ -59,8 +59,6 @@ namespace LL std::string getName() const { return mName; } size_t getWidth() const { return mThreads.size(); } - /// obtain a non-const reference to the WorkQueue to post work to it - WorkQueue& getQueue() { return mQueue; } /** * Override run() if you need special processing. The default run() @@ -87,15 +85,53 @@ namespace LL static size_t getWidth(const std::string& name, size_t dft); + protected: + std::unique_ptr mQueue; + private: void run(const std::string& name); - WorkQueue mQueue; std::string mName; size_t mThreadCount; std::vector> mThreads; }; + /** + * Specialize with WorkQueue or, for timestamped tasks, WorkSchedule + */ + template + struct ThreadPoolUsing: public ThreadPoolBase + { + using queue_t = QUEUE; + + /** + * Pass ThreadPoolUsing a string name. This can be used to look up the + * relevant WorkQueue. + * + * The number of threads you pass sets the compile-time default. But + * if the user has overridden the LLSD map in the "ThreadPoolSizes" + * setting with a key matching this ThreadPool name, that setting + * overrides this parameter. + * + * Pass an explicit capacity to limit the size of the queue. + * Constraining the queue can cause a submitter to block. Do not + * constrain any ThreadPool accepting work from the main thread. + */ + ThreadPoolUsing(const std::string& name, size_t threads=1, size_t capacity=1024*1024): + ThreadPoolBase(name, threads, new queue_t(name, capacity)) + {} + ~ThreadPoolUsing() override {} + + /** + * obtain a non-const reference to the specific WorkQueue subclass to + * post work to it + */ + queue_t& getQueue() { return static_cast(*mQueue); } + }; + + /// ThreadPool is shorthand for using the simpler WorkQueue + using ThreadPool = ThreadPoolUsing; + } // namespace LL #endif /* ! defined(LL_THREADPOOL_H) */ diff --git a/indra/llcommon/threadpool_fwd.h b/indra/llcommon/threadpool_fwd.h new file mode 100644 index 0000000000..1aa3c4a0e2 --- /dev/null +++ b/indra/llcommon/threadpool_fwd.h @@ -0,0 +1,25 @@ +/** + * @file threadpool_fwd.h + * @author Nat Goodspeed + * @date 2022-12-09 + * @brief Forward declarations for ThreadPool et al. + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Copyright (c) 2022, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_THREADPOOL_FWD_H) +#define LL_THREADPOOL_FWD_H + +#include "workqueue.h" + +namespace LL +{ + template + struct ThreadPoolUsing; + + using ThreadPool = ThreadPoolUsing; +} // namespace LL + +#endif /* ! defined(LL_THREADPOOL_FWD_H) */ diff --git a/indra/llcommon/workqueue.cpp b/indra/llcommon/workqueue.cpp index eb06890468..83e0216ae7 100644 --- a/indra/llcommon/workqueue.cpp +++ b/indra/llcommon/workqueue.cpp @@ -26,83 +26,65 @@ using Mutex = LLCoros::Mutex; using Lock = LLCoros::LockType; -LL::WorkQueue::WorkQueue(const std::string& name, size_t capacity): - super(makeName(name)), - mQueue(capacity) +/***************************************************************************** +* WorkQueueBase +*****************************************************************************/ +LL::WorkQueueBase::WorkQueueBase(const std::string& name): + super(makeName(name)) { // TODO: register for "LLApp" events so we can implicitly close() on // viewer shutdown. } -void LL::WorkQueue::close() -{ - mQueue.close(); -} - -size_t LL::WorkQueue::size() -{ - return mQueue.size(); -} - -bool LL::WorkQueue::isClosed() -{ - return mQueue.isClosed(); -} - -bool LL::WorkQueue::done() -{ - return mQueue.done(); -} - -void LL::WorkQueue::runUntilClose() +void LL::WorkQueueBase::runUntilClose() { try { for (;;) { LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; - callWork(mQueue.pop()); + callWork(pop_()); } } - catch (const Queue::Closed&) + catch (const Closed&) { } } -bool LL::WorkQueue::runPending() +bool LL::WorkQueueBase::runPending() { LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; - for (Work work; mQueue.tryPop(work); ) + for (Work work; tryPop_(work); ) { callWork(work); } - return ! mQueue.done(); + return ! done(); } -bool LL::WorkQueue::runOne() +bool LL::WorkQueueBase::runOne() { Work work; - if (mQueue.tryPop(work)) + if (tryPop_(work)) { callWork(work); } - return ! mQueue.done(); + return ! done(); } -bool LL::WorkQueue::runUntil(const TimePoint& until) +bool LL::WorkQueueBase::runUntil(const TimePoint& until) { LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; // Should we subtract some slop to allow for typical Work execution time? // How much slop? // runUntil() is simply a time-bounded runPending(). - for (Work work; TimePoint::clock::now() < until && mQueue.tryPop(work); ) + for (Work work; TimePoint::clock::now() < until && tryPop_(work); ) { callWork(work); } - return ! mQueue.done(); + return ! done(); } -std::string LL::WorkQueue::makeName(const std::string& name) +std::string LL::WorkQueueBase::makeName(const std::string& name) { if (! name.empty()) return name; @@ -120,14 +102,7 @@ std::string LL::WorkQueue::makeName(const std::string& name) return STRINGIZE("WorkQueue" << num); } -void LL::WorkQueue::callWork(const Queue::DataTuple& work) -{ - // ThreadSafeSchedule::pop() always delivers a tuple, even when - // there's only one data field per item, as for us. - callWork(std::get<0>(work)); -} - -void LL::WorkQueue::callWork(const Work& work) +void LL::WorkQueueBase::callWork(const Work& work) { LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; try @@ -142,12 +117,12 @@ void LL::WorkQueue::callWork(const Work& work) } } -void LL::WorkQueue::error(const std::string& msg) +void LL::WorkQueueBase::error(const std::string& msg) { LL_ERRS("WorkQueue") << msg << LL_ENDL; } -void LL::WorkQueue::checkCoroutine(const std::string& method) +void LL::WorkQueueBase::checkCoroutine(const std::string& method) { // By convention, the default coroutine on each thread has an empty name // string. See also LLCoros::logname(). @@ -156,3 +131,130 @@ void LL::WorkQueue::checkCoroutine(const std::string& method) LLTHROW(Error("Do not call " + method + " from a thread's default coroutine")); } } + +/***************************************************************************** +* WorkQueue +*****************************************************************************/ +LL::WorkQueue::WorkQueue(const std::string& name, size_t capacity): + super(name), + mQueue(capacity) +{ +} + +void LL::WorkQueue::close() +{ + mQueue.close(); +} + +size_t LL::WorkQueue::size() +{ + return mQueue.size(); +} + +bool LL::WorkQueue::isClosed() +{ + return mQueue.isClosed(); +} + +bool LL::WorkQueue::done() +{ + return mQueue.done(); +} + +void LL::WorkQueue::post(const Work& callable) +{ + mQueue.push(callable); +} + +bool LL::WorkQueue::postIfOpen(const Work& callable) +{ + return mQueue.pushIfOpen(callable); +} + +bool LL::WorkQueue::tryPost(const Work& callable) +{ + return mQueue.tryPush(callable); +} + +LL::WorkQueue::Work LL::WorkQueue::pop_() +{ + return mQueue.pop(); +} + +bool LL::WorkQueue::tryPop_(Work& work) +{ + return mQueue.tryPop(work); +} + +/***************************************************************************** +* WorkSchedule +*****************************************************************************/ +LL::WorkSchedule::WorkSchedule(const std::string& name, size_t capacity): + super(name), + mQueue(capacity) +{ +} + +void LL::WorkSchedule::close() +{ + mQueue.close(); +} + +size_t LL::WorkSchedule::size() +{ + return mQueue.size(); +} + +bool LL::WorkSchedule::isClosed() +{ + return mQueue.isClosed(); +} + +bool LL::WorkSchedule::done() +{ + return mQueue.done(); +} + +void LL::WorkSchedule::post(const Work& callable) +{ + // Use TimePoint::clock::now() instead of TimePoint's representation of + // the epoch because this WorkSchedule may contain a mix of past-due + // TimedWork items and TimedWork items scheduled for the future. Sift this + // new item into the correct place. + post(callable, TimePoint::clock::now()); +} + +void LL::WorkSchedule::post(const Work& callable, const TimePoint& time) +{ + mQueue.push(TimedWork(time, callable)); +} + +bool LL::WorkSchedule::postIfOpen(const Work& callable) +{ + return postIfOpen(callable, TimePoint::clock::now()); +} + +bool LL::WorkSchedule::postIfOpen(const Work& callable, const TimePoint& time) +{ + return mQueue.pushIfOpen(TimedWork(time, callable)); +} + +bool LL::WorkSchedule::tryPost(const Work& callable) +{ + return tryPost(callable, TimePoint::clock::now()); +} + +bool LL::WorkSchedule::tryPost(const Work& callable, const TimePoint& time) +{ + return mQueue.tryPush(TimedWork(time, callable)); +} + +LL::WorkSchedule::Work LL::WorkSchedule::pop_() +{ + return std::get<0>(mQueue.pop()); +} + +bool LL::WorkSchedule::tryPop_(Work& work) +{ + return mQueue.tryPop(work); +} diff --git a/indra/llcommon/workqueue.h b/indra/llcommon/workqueue.h index 28a0b5e040..eea8886a7a 100644 --- a/indra/llcommon/workqueue.h +++ b/indra/llcommon/workqueue.h @@ -15,6 +15,7 @@ #include "llcoros.h" #include "llexception.h" #include "llinstancetracker.h" +#include "llinstancetrackersubclass.h" #include "threadsafeschedule.h" #include #include // std::current_exception @@ -23,27 +24,23 @@ namespace LL { + +/***************************************************************************** +* WorkQueueBase: API for WorkQueue and WorkSchedule +*****************************************************************************/ /** * A typical WorkQueue has a string name that can be used to find it. */ - class WorkQueue: public LLInstanceTracker + class WorkQueueBase: public LLInstanceTracker { private: - using super = LLInstanceTracker; + using super = LLInstanceTracker; public: using Work = std::function; - - private: - using Queue = ThreadSafeSchedule; - // helper for postEvery() - template - class BackJack; - - public: - using TimePoint = Queue::TimePoint; - using TimedWork = Queue::TimeTuple; - using Closed = Queue::Closed; + using Closed = LLThreadSafeQueueInterrupt; + // for runFor() + using TimePoint = std::chrono::steady_clock::time_point; struct Error: public LLException { @@ -51,18 +48,18 @@ namespace LL }; /** - * You may omit the WorkQueue name, in which case a unique name is + * You may omit the WorkQueueBase name, in which case a unique name is * synthesized; for practical purposes that makes it anonymous. */ - WorkQueue(const std::string& name = std::string(), size_t capacity=1024); + WorkQueueBase(const std::string& name); /** * Since the point of WorkQueue is to pass work to some other worker - * thread(s) asynchronously, it's important that the WorkQueue continue - * to exist until the worker thread(s) have drained it. To communicate - * that it's time for them to quit, close() the queue. + * thread(s) asynchronously, it's important that it continue to exist + * until the worker thread(s) have drained it. To communicate that + * it's time for them to quit, close() the queue. */ - void close(); + virtual void close() = 0; /** * WorkQueue supports multiple producers and multiple consumers. In @@ -78,158 +75,60 @@ namespace LL * * If you're the only consumer, noticing that size() > 0 is * meaningful. */ - size_t size(); + virtual size_t size() = 0; /// producer end: are we prevented from pushing any additional items? - bool isClosed(); + virtual bool isClosed() = 0; /// consumer end: are we done, is the queue entirely drained? - bool done(); + virtual bool done() = 0; /*---------------------- fire and forget API -----------------------*/ - /// fire-and-forget, but at a particular (future?) time - template - void post(const TimePoint& time, CALLABLE&& callable) - { - // Defer reifying an arbitrary CALLABLE until we hit this or - // postIfOpen(). All other methods should accept CALLABLEs of - // arbitrary type to avoid multiple levels of std::function - // indirection. - mQueue.push(TimedWork(time, std::move(callable))); - } - /// fire-and-forget - template - void post(CALLABLE&& callable) - { - // We use TimePoint::clock::now() instead of TimePoint's - // representation of the epoch because this WorkQueue may contain - // a mix of past-due TimedWork items and TimedWork items scheduled - // for the future. Sift this new item into the correct place. - post(TimePoint::clock::now(), std::move(callable)); - } - - /** - * post work for a particular time, unless the queue is closed before - * we can post - */ - template - bool postIfOpen(const TimePoint& time, CALLABLE&& callable) - { - // Defer reifying an arbitrary CALLABLE until we hit this or - // post(). All other methods should accept CALLABLEs of arbitrary - // type to avoid multiple levels of std::function indirection. - return mQueue.pushIfOpen(TimedWork(time, std::move(callable))); - } + virtual void post(const Work&) = 0; /** * post work, unless the queue is closed before we can post */ - template - bool postIfOpen(CALLABLE&& callable) - { - return postIfOpen(TimePoint::clock::now(), std::move(callable)); - } + virtual bool postIfOpen(const Work&) = 0; /** - * Post work to be run at a specified time to another WorkQueue, which - * may or may not still exist and be open. Return true if we were able - * to post. + * post work, unless the queue is full */ - template - static bool postMaybe(weak_t target, const TimePoint& time, CALLABLE&& callable); + virtual bool tryPost(const Work&) = 0; /** * Post work to another WorkQueue, which may or may not still exist - * and be open. Return true if we were able to post. - */ - template - static bool postMaybe(weak_t target, CALLABLE&& callable) - { - return postMaybe(target, TimePoint::clock::now(), - std::forward(callable)); - } - - /** - * Launch a callable returning bool that will trigger repeatedly at - * specified interval, until the callable returns false. - * - * If you need to signal that callable from outside, DO NOT bind a - * reference to a simple bool! That's not thread-safe. Instead, bind - * an LLCond variant, e.g. LLOneShotCond or LLBoolCond. + * and be open. Support any post() overload. Return true if we were + * able to post. */ - template - void postEvery(const std::chrono::duration& interval, - CALLABLE&& callable); - - template - bool tryPost(const TimePoint& time, CALLABLE&& callable) - { - return mQueue.tryPush(TimedWork(time, std::move(callable))); - } - - template - bool tryPost(CALLABLE&& callable) - { - return mQueue.tryPush(TimePoint::clock::now(), std::move(callable)); - } + template + static bool postMaybe(weak_t target, ARGS&&... args); /*------------------------- handshake API --------------------------*/ - /** - * Post work to another WorkQueue to be run at a specified time, - * requesting a specific callback to be run on this WorkQueue on - * completion. - * - * Returns true if able to post, false if the other WorkQueue is - * inaccessible. - */ - // Apparently some Microsoft header file defines a macro CALLBACK? The - // natural template argument name CALLBACK produces very weird Visual - // Studio compile errors that seem utterly unrelated to this source - // code. - template - bool postTo(weak_t target, - const TimePoint& time, CALLABLE&& callable, FOLLOWUP&& callback); - /** * Post work to another WorkQueue, requesting a specific callback to - * be run on this WorkQueue on completion. + * be run on this WorkQueue on completion. Optional final argument is + * TimePoint for WorkSchedule. * * Returns true if able to post, false if the other WorkQueue is * inaccessible. */ - template - bool postTo(weak_t target, CALLABLE&& callable, FOLLOWUP&& callback) - { - return postTo(target, TimePoint::clock::now(), - std::move(callable), std::move(callback)); - } - - /** - * Post work to another WorkQueue to be run at a specified time, - * blocking the calling coroutine until then, returning the result to - * caller on completion. - * - * In general, we assume that each thread's default coroutine is busy - * servicing its WorkQueue or whatever. To try to prevent mistakes, we - * forbid calling waitForResult() from a thread's default coroutine. - */ - template - auto waitForResult(const TimePoint& time, CALLABLE&& callable); + template + bool postTo(weak_t target, CALLABLE&& callable, FOLLOWUP&& callback, + ARGS&&... args); /** * Post work to another WorkQueue, blocking the calling coroutine - * until then, returning the result to caller on completion. + * until then, returning the result to caller on completion. Optional + * final argument is TimePoint for WorkSchedule. * * In general, we assume that each thread's default coroutine is busy * servicing its WorkQueue or whatever. To try to prevent mistakes, we * forbid calling waitForResult() from a thread's default coroutine. */ - template - auto waitForResult(CALLABLE&& callable) - { - return waitForResult(TimePoint::clock::now(), std::move(callable)); - } + template + auto waitForResult(CALLABLE&& callable, ARGS&&... args); /*--------------------------- worker API ---------------------------*/ @@ -276,7 +175,7 @@ namespace LL */ bool runUntil(const TimePoint& until); - private: + protected: template static auto makeReplyLambda(CALLABLE&& callable, FOLLOWUP&& callback); /// general case: arbitrary C++ return type @@ -296,13 +195,179 @@ namespace LL static void checkCoroutine(const std::string& method); static void error(const std::string& msg); static std::string makeName(const std::string& name); - void callWork(const Queue::DataTuple& work); void callWork(const Work& work); + + private: + virtual Work pop_() = 0; + virtual bool tryPop_(Work&) = 0; + }; + +/***************************************************************************** +* WorkQueue: no timestamped task support +*****************************************************************************/ + class WorkQueue: public LLInstanceTrackerSubclass + { + private: + using super = LLInstanceTrackerSubclass; + + public: + /** + * You may omit the WorkQueue name, in which case a unique name is + * synthesized; for practical purposes that makes it anonymous. + */ + WorkQueue(const std::string& name = std::string(), size_t capacity=1024); + + /** + * Since the point of WorkQueue is to pass work to some other worker + * thread(s) asynchronously, it's important that it continue to exist + * until the worker thread(s) have drained it. To communicate that + * it's time for them to quit, close() the queue. + */ + void close() override; + + /** + * WorkQueue supports multiple producers and multiple consumers. In + * the general case it's misleading to test size(), since any other + * thread might change it the nanosecond the lock is released. On that + * basis, some might argue against publishing a size() method at all. + * + * But there are two specific cases in which a test based on size() + * might be reasonable: + * + * * If you're the only producer, noticing that size() == 0 is + * meaningful. + * * If you're the only consumer, noticing that size() > 0 is + * meaningful. + */ + size_t size() override; + /// producer end: are we prevented from pushing any additional items? + bool isClosed() override; + /// consumer end: are we done, is the queue entirely drained? + bool done() override; + + /*---------------------- fire and forget API -----------------------*/ + + /// fire-and-forget + void post(const Work&) override; + + /** + * post work, unless the queue is closed before we can post + */ + bool postIfOpen(const Work&) override; + + /** + * post work, unless the queue is full + */ + bool tryPost(const Work&) override; + + private: + using Queue = LLThreadSafeQueue; Queue mQueue; + + Work pop_() override; + bool tryPop_(Work&) override; + }; + +/***************************************************************************** +* WorkSchedule: add support for timestamped tasks +*****************************************************************************/ + class WorkSchedule: public LLInstanceTrackerSubclass + { + private: + using super = LLInstanceTrackerSubclass; + using Queue = ThreadSafeSchedule; + // helper for postEvery() + template + class BackJack; + + public: + using TimePoint = Queue::TimePoint; + using TimedWork = Queue::TimeTuple; + + /** + * You may omit the WorkSchedule name, in which case a unique name is + * synthesized; for practical purposes that makes it anonymous. + */ + WorkSchedule(const std::string& name = std::string(), size_t capacity=1024); + + /** + * Since the point of WorkSchedule is to pass work to some other worker + * thread(s) asynchronously, it's important that the WorkSchedule continue + * to exist until the worker thread(s) have drained it. To communicate + * that it's time for them to quit, close() the queue. + */ + void close() override; + + /** + * WorkSchedule supports multiple producers and multiple consumers. In + * the general case it's misleading to test size(), since any other + * thread might change it the nanosecond the lock is released. On that + * basis, some might argue against publishing a size() method at all. + * + * But there are two specific cases in which a test based on size() + * might be reasonable: + * + * * If you're the only producer, noticing that size() == 0 is + * meaningful. + * * If you're the only consumer, noticing that size() > 0 is + * meaningful. + */ + size_t size() override; + /// producer end: are we prevented from pushing any additional items? + bool isClosed() override; + /// consumer end: are we done, is the queue entirely drained? + bool done() override; + + /*---------------------- fire and forget API -----------------------*/ + + /// fire-and-forget + void post(const Work& callable) override; + + /// fire-and-forget, but at a particular (future?) time + void post(const Work& callable, const TimePoint& time); + + /** + * post work, unless the queue is closed before we can post + */ + bool postIfOpen(const Work& callable) override; + + /** + * post work for a particular time, unless the queue is closed before + * we can post + */ + bool postIfOpen(const Work& callable, const TimePoint& time); + + /** + * post work, unless the queue is full + */ + bool tryPost(const Work& callable) override; + + /** + * post work for a particular time, unless the queue is full + */ + bool tryPost(const Work& callable, const TimePoint& time); + + /** + * Launch a callable returning bool that will trigger repeatedly at + * specified interval, until the callable returns false. + * + * If you need to signal that callable from outside, DO NOT bind a + * reference to a simple bool! That's not thread-safe. Instead, bind + * an LLCond variant, e.g. LLOneShotCond or LLBoolCond. + */ + template + void postEvery(const std::chrono::duration& interval, + CALLABLE&& callable); + + private: + Queue mQueue; + + Work pop_() override; + bool tryPop_(Work&) override; }; /** - * BackJack is, in effect, a hand-rolled lambda, binding a WorkQueue, a + * BackJack is, in effect, a hand-rolled lambda, binding a WorkSchedule, a * CALLABLE that returns bool, a TimePoint and an interval at which to * relaunch it. As long as the callable continues returning true, BackJack * keeps resubmitting it to the target WorkQueue. @@ -311,7 +376,7 @@ namespace LL // class method gets its own 'this' pointer -- which we need to resubmit // the whole BackJack callable. template - class WorkQueue::BackJack + class WorkSchedule::BackJack { public: // bind the desired data @@ -325,9 +390,10 @@ namespace LL mCallable(std::move(callable)) {} - // Call by target WorkQueue -- note that although WE require a - // callable returning bool, WorkQueue wants a void callable. We - // consume the bool. + // This operator() method, called by target WorkSchedule, is what + // makes this object a Work item. Although WE require a callable + // returning bool, WorkSchedule wants a void callable. We consume the + // bool. void operator()() { // If mCallable() throws an exception, don't catch it here: if it @@ -343,7 +409,7 @@ namespace LL // register our intent to fire at exact mIntervals. mStart += mInterval; - // We're being called at this moment by the target WorkQueue. + // We're being called at this moment by the target WorkSchedule. // Assume it still exists, rather than checking the result of // lock(). // Resubmit the whole *this callable: that's why we're a class @@ -353,7 +419,8 @@ namespace LL // moved-from. try { - mTarget.lock()->post(mStart, std::move(*this)); + std::dynamic_pointer_cast(mTarget.lock())-> + post(std::move(*this), mStart); } catch (const Closed&) { @@ -370,8 +437,8 @@ namespace LL }; template - void WorkQueue::postEvery(const std::chrono::duration& interval, - CALLABLE&& callable) + void WorkSchedule::postEvery(const std::chrono::duration& interval, + CALLABLE&& callable) { if (interval.count() <= 0) { @@ -394,7 +461,7 @@ namespace LL /// general case: arbitrary C++ return type template - struct WorkQueue::MakeReplyLambda + struct WorkQueueBase::MakeReplyLambda { auto operator()(CALLABLE&& callable, FOLLOWUP&& callback) { @@ -415,7 +482,7 @@ namespace LL /// specialize for CALLABLE returning void template - struct WorkQueue::MakeReplyLambda + struct WorkQueueBase::MakeReplyLambda { auto operator()(CALLABLE&& callable, FOLLOWUP&& callback) { @@ -427,16 +494,16 @@ namespace LL }; template - auto WorkQueue::makeReplyLambda(CALLABLE&& callable, FOLLOWUP&& callback) + auto WorkQueueBase::makeReplyLambda(CALLABLE&& callable, FOLLOWUP&& callback) { return MakeReplyLambda(callable)())>() (std::move(callable), std::move(callback)); } - template - bool WorkQueue::postTo(weak_t target, - const TimePoint& time, CALLABLE&& callable, FOLLOWUP&& callback) + template + bool WorkQueueBase::postTo(weak_t target, CALLABLE&& callable, FOLLOWUP&& callback, + ARGS&&... args) { LL_PROFILE_ZONE_SCOPED; // We're being asked to post to the WorkQueue at target. @@ -450,12 +517,11 @@ namespace LL // lambda that packages our callable, our callback and a weak_ptr // to this originating WorkQueue. tptr->post( - time, [reply = super::getWeak(), callable = std::move(callable), callback = std::move(callback)] - () - mutable { + () mutable + { // Use postMaybe() below in case this originating WorkQueue // has been closed or destroyed. Remember, the outer lambda is // now running on a thread servicing the target WorkQueue, and @@ -478,14 +544,16 @@ namespace LL // originating WorkQueue. Once there, rethrow it. [exc = std::current_exception()](){ std::rethrow_exception(exc); }); } - }); + }, + // if caller passed a TimePoint, pass it along to post() + std::forward(args)...); // looks like we were able to post() return true; } - template - bool WorkQueue::postMaybe(weak_t target, const TimePoint& time, CALLABLE&& callable) + template + bool WorkQueueBase::postMaybe(weak_t target, ARGS&&... args) { LL_PROFILE_ZONE_SCOPED; // target is a weak_ptr: have to lock it to check it @@ -494,7 +562,7 @@ namespace LL { try { - tptr->post(time, std::forward(callable)); + tptr->post(std::forward(args)...); // we were able to post() return true; } @@ -509,13 +577,13 @@ namespace LL /// general case: arbitrary C++ return type template - struct WorkQueue::WaitForResult + struct WorkQueueBase::WaitForResult { - auto operator()(WorkQueue* self, const TimePoint& time, CALLABLE&& callable) + template + auto operator()(WorkQueueBase* self, CALLABLE&& callable, ARGS&&... args) { LLCoros::Promise promise; self->post( - time, // We dare to bind a reference to Promise because it's // specifically designed for cross-thread communication. [&promise, callable = std::move(callable)]() @@ -529,7 +597,9 @@ namespace LL { promise.set_exception(std::current_exception()); } - }); + }, + // if caller passed a TimePoint, pass it to post() + std::forward(args)...); auto future{ LLCoros::getFuture(promise) }; // now, on the calling thread, wait for that result LLCoros::TempStatus st("waiting for WorkQueue::waitForResult()"); @@ -539,13 +609,13 @@ namespace LL /// specialize for CALLABLE returning void template - struct WorkQueue::WaitForResult + struct WorkQueueBase::WaitForResult { - void operator()(WorkQueue* self, const TimePoint& time, CALLABLE&& callable) + template + void operator()(WorkQueueBase* self, CALLABLE&& callable, ARGS&&... args) { LLCoros::Promise promise; self->post( - time, // &promise is designed for cross-thread access [&promise, callable = std::move(callable)]() mutable { @@ -558,7 +628,9 @@ namespace LL { promise.set_exception(std::current_exception()); } - }); + }, + // if caller passed a TimePoint, pass it to post() + std::forward(args)...); auto future{ LLCoros::getFuture(promise) }; // block until set_value() LLCoros::TempStatus st("waiting for void WorkQueue::waitForResult()"); @@ -566,13 +638,13 @@ namespace LL } }; - template - auto WorkQueue::waitForResult(const TimePoint& time, CALLABLE&& callable) + template + auto WorkQueueBase::waitForResult(CALLABLE&& callable, ARGS&&... args) { checkCoroutine("waitForResult()"); // derive callable's return type so we can specialize for void return WaitForResult(callable)())>() - (this, time, std::forward(callable)); + (this, std::forward(callable), std::forward(args)...); } } // namespace LL -- cgit v1.2.3 From 424d3ef83cdb354e66789f22f65394f4db523128 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 13 Dec 2022 20:49:01 -0500 Subject: DRTVWR-559: Fix broken workqueue_test.cpp. Apparently Visual Studio and Xcode disagree on the intended lifespan of a certain temporary expression. Capturing it in a named variable works. --- indra/llcommon/workqueue.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/workqueue.h b/indra/llcommon/workqueue.h index eea8886a7a..5461ce6c23 100644 --- a/indra/llcommon/workqueue.h +++ b/indra/llcommon/workqueue.h @@ -419,8 +419,8 @@ namespace LL // moved-from. try { - std::dynamic_pointer_cast(mTarget.lock())-> - post(std::move(*this), mStart); + auto target{ std::dynamic_pointer_cast(mTarget.lock()) }; + target->post(std::move(*this), mStart); } catch (const Closed&) { -- cgit v1.2.3 From 274da636a00fd0469f7857dad7995cb11552e4ab Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Mon, 23 Jan 2023 11:48:43 -0600 Subject: SL-18869 Followup -- AMD optimization pass. --- indra/llcommon/llprofiler.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llprofiler.h b/indra/llcommon/llprofiler.h index c03b2e93f4..394fbd05f6 100644 --- a/indra/llcommon/llprofiler.h +++ b/indra/llcommon/llprofiler.h @@ -87,7 +87,7 @@ extern thread_local bool gProfilerEnabled; #include "Tracy.hpp" // Enable OpenGL profiling - #define LL_PROFILER_ENABLE_TRACY_OPENGL 1 + #define LL_PROFILER_ENABLE_TRACY_OPENGL 0 // Enable RenderDoc labeling #define LL_PROFILER_ENABLE_RENDER_DOC 0 -- cgit v1.2.3 From 473ade269628d1fb6cbc7b96a91e1c0aec1b8e59 Mon Sep 17 00:00:00 2001 From: Henri Beauchamp Date: Tue, 31 Jan 2023 17:42:51 +0100 Subject: SL-19110 Fast hashing classes for use in place of the slow LLMD5, where speed matters. (#64) This commit adds the HBXX64 and HBXX128 classes for use as a drop-in replacement for the slow LLMD5 hashing class, where speed matters and backward compatibility (with standard hashing algorithms) and/or cryptographic hashing qualities are not required. It also replaces LLMD5 with HBXX* in a few existing hot (well, ok, just "warm" for some) paths meeting the above requirements, while paving the way for future use cases, such as in the DRTVWR-559 and sibling branches where the slow LLMD5 is used (e.g. to hash materials and vertex buffer cache entries), and could be use such a (way) faster algorithm with very significant benefits and no negative impact. Here is the comment I added in indra/llcommon/hbxx.h: // HBXXH* classes are to be used where speed matters and cryptographic quality // is not required (no "one-way" guarantee, though they are likely not worst in // this respect than MD5 which got busted and is now considered too weak). The // xxHash code they are built upon is vectorized and about 50 times faster than // MD5. A 64 bits hash class is also provided for when 128 bits of entropy are // not needed. The hashes collision rate is similar to MD5's. // See https://github.com/Cyan4973/xxHash#readme for details. --- indra/llcommon/CMakeLists.txt | 2 + indra/llcommon/hbxxh.cpp | 377 ++++++++++++++++++++++++++++++++++++++++++ indra/llcommon/hbxxh.h | 259 +++++++++++++++++++++++++++++ indra/llcommon/lluuid.cpp | 28 +--- 4 files changed, 646 insertions(+), 20 deletions(-) create mode 100644 indra/llcommon/hbxxh.cpp create mode 100644 indra/llcommon/hbxxh.h (limited to 'indra/llcommon') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 991c3f70a1..fab7550c9a 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -119,6 +119,7 @@ set(llcommon_SOURCE_FILES lluriparser.cpp lluuid.cpp llworkerthread.cpp + hbxxh.cpp u64.cpp threadpool.cpp workqueue.cpp @@ -257,6 +258,7 @@ set(llcommon_HEADER_FILES llwin32headers.h llwin32headerslean.h llworkerthread.h + hbxxh.h lockstatic.h stdtypes.h stringize.h diff --git a/indra/llcommon/hbxxh.cpp b/indra/llcommon/hbxxh.cpp new file mode 100644 index 0000000000..e94581d415 --- /dev/null +++ b/indra/llcommon/hbxxh.cpp @@ -0,0 +1,377 @@ +/** + * @file hbxxh.cpp + * @brief High performances vectorized hashing based on xxHash. + * + * $LicenseInfo:firstyear=2023&license=viewergpl$ + * Second Life Viewer Source Code + * Copyright (c) 2023, Henri Beauchamp. + * + * 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" + +// This define ensures that xxHash will be compiled within this module, with +// vectorized (*) and inlined functions (with no exported API symbol); our +// xxhash "pre-built library" package actually only contains the xxhash.h +// header (no library needed at link time). +// (*) SSE2 is normally used for x86(_64) builds, unless you enabled AVX2 +// in your build, in which case the latter would be used instead. For ARM64 +// builds, this would also automatically enable NEON vectorization. +#define XXH_INLINE_ALL +#include "xxhash.h" + +#include "hbxxh.h" + +// How many bytes to grab at a time when hashing files or streams +constexpr size_t BLOCK_LEN = 4096; + +/////////////////////////////////////////////////////////////////////////////// +// HBXXH64 class +/////////////////////////////////////////////////////////////////////////////// + +//static +U64 HBXXH64::digest(const void* buffer, size_t len) +{ + return XXH3_64bits(buffer, len); +} + +//static +U64 HBXXH64::digest(const char* str) +{ + return XXH3_64bits((const void*)str, strlen(str)); +} + +//static +U64 HBXXH64::digest(const std::string& str) +{ + return XXH3_64bits((const void*)str.c_str(), str.size()); +} + +// Must be called by all constructors. +void HBXXH64::init() +{ + mDigest = 0; + mState = (void*)XXH3_createState(); + if (!mState || XXH3_64bits_reset((XXH3_state_t*)mState) != XXH_OK) + { + LL_WARNS() << "Failed to initialize state !" << LL_ENDL; + } +} + +HBXXH64::~HBXXH64() +{ + if (mState) + { + XXH3_freeState((XXH3_state_t*)mState); + } +} + +void HBXXH64::update(const void* buffer, size_t len) +{ + if (mState) + { + XXH3_64bits_update((XXH3_state_t*)mState, buffer, len); + } + else + { + LL_WARNS() << "Cannot update a finalized digest !" << LL_ENDL; + } +} + +void HBXXH64::update(const std::string& str) +{ + if (mState) + { + XXH3_64bits_update((XXH3_state_t*)mState, (const void*)str.c_str(), + str.length()); + } + else + { + LL_WARNS() << "Cannot update a finalized digest !" << LL_ENDL; + } +} + +void HBXXH64::update(std::istream& stream) +{ + if (!mState) + { + LL_WARNS() << "Cannot update a finalized digest !" << LL_ENDL; + return; + } + + char buffer[BLOCK_LEN]; + size_t len; + while (stream.good()) + { + stream.read(buffer, BLOCK_LEN); + len = stream.gcount(); + XXH3_64bits_update((XXH3_state_t*)mState, (const void*)buffer, len); + } +} + +void HBXXH64::update(FILE* file) +{ + if (!mState) + { + LL_WARNS() << "Cannot update a finalized digest !" << LL_ENDL; + return; + } + + char buffer[BLOCK_LEN]; + size_t len; + while ((len = fread((void*)buffer, 1, BLOCK_LEN, file))) + { + XXH3_64bits_update((XXH3_state_t*)mState, (const void*)buffer, len); + } + fclose(file); +} + +void HBXXH64::finalize() +{ + if (!mState) + { + LL_WARNS() << "Already finalized !" << LL_ENDL; + return; + } + mDigest = XXH3_64bits_digest((XXH3_state_t*)mState); + XXH3_freeState((XXH3_state_t*)mState); + mState = NULL; +} + +U64 HBXXH64::digest() const +{ + return mState ? XXH3_64bits_digest((XXH3_state_t*)mState) : mDigest; +} + +std::ostream& operator<<(std::ostream& stream, HBXXH64 context) +{ + stream << context.digest(); + return stream; +} + +/////////////////////////////////////////////////////////////////////////////// +// HBXXH128 class +/////////////////////////////////////////////////////////////////////////////// + +//static +LLUUID HBXXH128::digest(const void* buffer, size_t len) +{ + XXH128_hash_t hash = XXH3_128bits(buffer, len); + LLUUID id; + U64* data = (U64*)id.mData; + // Note: we do not check endianness here and we just store in the same + // order as XXH128_hash_t, that is low word "first". + data[0] = hash.low64; + data[1] = hash.high64; + return id; +} + +//static +LLUUID HBXXH128::digest(const char* str) +{ + XXH128_hash_t hash = XXH3_128bits((const void*)str, strlen(str)); + LLUUID id; + U64* data = (U64*)id.mData; + // Note: we do not check endianness here and we just store in the same + // order as XXH128_hash_t, that is low word "first". + data[0] = hash.low64; + data[1] = hash.high64; + return id; +} + +//static +LLUUID HBXXH128::digest(const std::string& str) +{ + XXH128_hash_t hash = XXH3_128bits((const void*)str.c_str(), str.size()); + LLUUID id; + U64* data = (U64*)id.mData; + // Note: we do not check endianness here and we just store in the same + // order as XXH128_hash_t, that is low word "first". + data[0] = hash.low64; + data[1] = hash.high64; + return id; +} + +//static +void HBXXH128::digest(LLUUID& result, const void* buffer, size_t len) +{ + XXH128_hash_t hash = XXH3_128bits(buffer, len); + U64* data = (U64*)result.mData; + // Note: we do not check endianness here and we just store in the same + // order as XXH128_hash_t, that is low word "first". + data[0] = hash.low64; + data[1] = hash.high64; +} + +//static +void HBXXH128::digest(LLUUID& result, const char* str) +{ + XXH128_hash_t hash = XXH3_128bits((const void*)str, strlen(str)); + U64* data = (U64*)result.mData; + // Note: we do not check endianness here and we just store in the same + // order as XXH128_hash_t, that is low word "first". + data[0] = hash.low64; + data[1] = hash.high64; +} + +//static +void HBXXH128::digest(LLUUID& result, const std::string& str) +{ + XXH128_hash_t hash = XXH3_128bits((const void*)str.c_str(), str.size()); + U64* data = (U64*)result.mData; + // Note: we do not check endianness here and we just store in the same + // order as XXH128_hash_t, that is low word "first". + data[0] = hash.low64; + data[1] = hash.high64; +} + +// Must be called by all constructors. +void HBXXH128::init() +{ + mState = (void*)XXH3_createState(); + if (!mState || XXH3_128bits_reset((XXH3_state_t*)mState) != XXH_OK) + { + LL_WARNS() << "Failed to initialize state !" << LL_ENDL; + } +} + +HBXXH128::~HBXXH128() +{ + if (mState) + { + XXH3_freeState((XXH3_state_t*)mState); + } +} + +void HBXXH128::update(const void* buffer, size_t len) +{ + if (mState) + { + XXH3_128bits_update((XXH3_state_t*)mState, buffer, len); + } + else + { + LL_WARNS() << "Cannot update a finalized digest !" << LL_ENDL; + } +} + +void HBXXH128::update(const std::string& str) +{ + if (mState) + { + XXH3_128bits_update((XXH3_state_t*)mState, (const void*)str.c_str(), + str.length()); + } + else + { + LL_WARNS() << "Cannot update a finalized digest !" << LL_ENDL; + } +} + +void HBXXH128::update(std::istream& stream) +{ + if (!mState) + { + LL_WARNS() << "Cannot update a finalized digest !" << LL_ENDL; + return; + } + + char buffer[BLOCK_LEN]; + size_t len; + while (stream.good()) + { + stream.read(buffer, BLOCK_LEN); + len = stream.gcount(); + XXH3_128bits_update((XXH3_state_t*)mState, (const void*)buffer, len); + } +} + +void HBXXH128::update(FILE* file) +{ + if (!mState) + { + LL_WARNS() << "Cannot update a finalized digest !" << LL_ENDL; + return; + } + + char buffer[BLOCK_LEN]; + size_t len; + while ((len = fread((void*)buffer, 1, BLOCK_LEN, file))) + { + XXH3_128bits_update((XXH3_state_t*)mState, (const void*)buffer, len); + } + fclose(file); +} + +void HBXXH128::finalize() +{ + if (!mState) + { + LL_WARNS() << "Already finalized !" << LL_ENDL; + return; + } + XXH128_hash_t hash = XXH3_128bits_digest((XXH3_state_t*)mState); + U64* data = (U64*)mDigest.mData; + // Note: we do not check endianness here and we just store in the same + // order as XXH128_hash_t, that is low word "first". + data[0] = hash.low64; + data[1] = hash.high64; + XXH3_freeState((XXH3_state_t*)mState); + mState = NULL; +} + +const LLUUID& HBXXH128::digest() const +{ + if (mState) + { + XXH128_hash_t hash = XXH3_128bits_digest((XXH3_state_t*)mState); + // We cheat the const-ness of the method here, but this is OK, since + // mDigest is private and cannot be accessed indirectly by other + // methods than digest() ones, that do check for mState to decide + // wether mDigest's current value may be provided as is or not. This + // cheat saves us a temporary LLLUID copy. + U64* data = (U64*)mDigest.mData; + // Note: we do not check endianness here and we just store in the same + // order as XXH128_hash_t, that is low word "first". + data[0] = hash.low64; + data[1] = hash.high64; + } + return mDigest; +} + +void HBXXH128::digest(LLUUID& result) const +{ + if (!mState) + { + result = mDigest; + return; + } + XXH128_hash_t hash = XXH3_128bits_digest((XXH3_state_t*)mState); + U64* data = (U64*)result.mData; + // Note: we do not check endianness here and we just store in the same + // order as XXH128_hash_t, that is low word "first". + data[0] = hash.low64; + data[1] = hash.high64; +} + +std::ostream& operator<<(std::ostream& stream, HBXXH128 context) +{ + stream << context.digest(); + return stream; +} diff --git a/indra/llcommon/hbxxh.h b/indra/llcommon/hbxxh.h new file mode 100644 index 0000000000..8a5f977648 --- /dev/null +++ b/indra/llcommon/hbxxh.h @@ -0,0 +1,259 @@ +/** + * @file hbxxh.h + * @brief High performances vectorized hashing based on xxHash. + * + * $LicenseInfo:firstyear=2023&license=viewergpl$ + * Second Life Viewer Source Code + * Copyright (c) 2023, Henri Beauchamp. + * + * 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_HBXXH_H +#define LL_HBXXH_H + +#include "lluuid.h" + +// HBXXH* classes are to be used where speed matters and cryptographic quality +// is not required (no "one-way" guarantee, though they are likely not worst in +// this respect than MD5 which got busted and is now considered too weak). The +// xxHash code they are built upon is vectorized and about 50 times faster than +// MD5. A 64 bits hash class is also provided for when 128 bits of entropy are +// not needed. The hashes collision rate is similar to MD5's. +// See https://github.com/Cyan4973/xxHash#readme for details. + +// 64 bits hashing class + +class HBXXH64 +{ + friend std::ostream& operator<<(std::ostream&, HBXXH64); + +protected: + LOG_CLASS(HBXXH64); + +public: + inline HBXXH64() { init(); } + + // Constructors for special circumstances; they all digest the first passed + // parameter. Set 'do_finalize' to false if you do not want to finalize the + // context, which is useful/needed when you want to update() it afterwards. + // Ideally, the compiler should be smart enough to get our clue and + // optimize out the const bool test during inlining... + + inline HBXXH64(const void* buffer, size_t len, + const bool do_finalize = true) + { + init(); + update(buffer, len); + if (do_finalize) + { + finalize(); + } + } + + inline HBXXH64(const std::string& str, const bool do_finalize = true) + { + init(); + update(str); + if (do_finalize) + { + finalize(); + } + } + + inline HBXXH64(std::istream& s, const bool do_finalize = true) + { + init(); + update(s); + if (do_finalize) + { + finalize(); + } + } + + inline HBXXH64(FILE* file, const bool do_finalize = true) + { + init(); + update(file); + if (do_finalize) + { + finalize(); + } + } + + ~HBXXH64(); + + void update(const void* buffer, size_t len); + void update(const std::string& str); + void update(std::istream& s); + void update(FILE* file); + + // Note that unlike what happens with LLMD5, you do not need to finalize() + // HBXXH64 before using digest(), and you may keep updating() it even after + // you got a first digest() (the next digest would of course change after + // any update). It is still useful to use finalize() when you do not want + // to store a final digest() result in a separate U64; after this method + // has been called, digest() simply returns mDigest value. + void finalize(); + + U64 digest() const; + + // Fast static methods. Use them when hashing just one contiguous block of + // data. + static U64 digest(const void* buffer, size_t len); + static U64 digest(const char* str); // str must be NUL-terminated + static U64 digest(const std::string& str); + +private: + void init(); + +private: + // We use a void pointer to avoid including xxhash.h here for XXH3_state_t + // (which cannot either be trivially forward-declared, due to complex API + // related pre-processor macros in xxhash.h). + void* mState; + U64 mDigest; +}; + +inline bool operator==(const HBXXH64& a, const HBXXH64& b) +{ + return a.digest() == b.digest(); +} + +inline bool operator!=(const HBXXH64& a, const HBXXH64& b) +{ + return a.digest() != b.digest(); +} + +// 128 bits hashing class + +class HBXXH128 +{ + friend std::ostream& operator<<(std::ostream&, HBXXH128); + +protected: + LOG_CLASS(HBXXH128); + +public: + inline HBXXH128() { init(); } + + // Constructors for special circumstances; they all digest the first passed + // parameter. Set 'do_finalize' to false if you do not want to finalize the + // context, which is useful/needed when you want to update() it afterwards. + // Ideally, the compiler should be smart enough to get our clue and + // optimize out the const bool test during inlining... + + inline HBXXH128(const void* buffer, size_t len, + const bool do_finalize = true) + { + init(); + update(buffer, len); + if (do_finalize) + { + finalize(); + } + } + + inline HBXXH128(const std::string& str, const bool do_finalize = true) + { + init(); + update(str); + if (do_finalize) + { + finalize(); + } + } + + inline HBXXH128(std::istream& s, const bool do_finalize = true) + { + init(); + update(s); + if (do_finalize) + { + finalize(); + } + } + + inline HBXXH128(FILE* file, const bool do_finalize = true) + { + init(); + update(file); + if (do_finalize) + { + finalize(); + } + } + + ~HBXXH128(); + + void update(const void* buffer, size_t len); + void update(const std::string& str); + void update(std::istream& s); + void update(FILE* file); + + // Note that unlike what happens with LLMD5, you do not need to finalize() + // HBXXH128 before using digest(), and you may keep updating() it even + // after you got a first digest() (the next digest would of course change + // after any update). It is still useful to use finalize() when you do not + // want to store a final digest() result in a separate LLUUID; after this + // method has been called, digest() simply returns a reference on mDigest. + void finalize(); + + // We use an LLUUID for the digest, since this is a 128 bits wide native + // type available in the viewer code, making it easy to manipulate. It also + // allows to use HBXXH128 efficiently in LLUUID generate() and combine() + // methods. + const LLUUID& digest() const; + + // Here, we avoid an LLUUID copy whenever we already got one to store the + // result *and* we did not yet call finalize(). + void digest(LLUUID& result) const; + + // Fast static methods. Use them when hashing just one contiguous block of + // data. + static LLUUID digest(const void* buffer, size_t len); + static LLUUID digest(const char* str); // str must be NUL-terminated + static LLUUID digest(const std::string& str); + // Same as above, but saves you from an LLUUID copy when you already got + // one for storage use. + static void digest(LLUUID& result, const void* buffer, size_t len); + static void digest(LLUUID& result, const char* str); // str NUL-terminated + static void digest(LLUUID& result, const std::string& str); + +private: + void init(); + +private: + // We use a void pointer to avoid including xxhash.h here for XXH3_state_t + // (which cannot either be trivially forward-declared, due to complex API + // related pre-processor macros in xxhash.h). + void* mState; + LLUUID mDigest; +}; + +inline bool operator==(const HBXXH128& a, const HBXXH128& b) +{ + return a.digest() == b.digest(); +} + +inline bool operator!=(const HBXXH128& a, const HBXXH128& b) +{ + return a.digest() != b.digest(); +} + +#endif // LL_HBXXH_H diff --git a/indra/llcommon/lluuid.cpp b/indra/llcommon/lluuid.cpp index acce8366ea..8ff6c45760 100644 --- a/indra/llcommon/lluuid.cpp +++ b/indra/llcommon/lluuid.cpp @@ -40,11 +40,11 @@ #include "lluuid.h" #include "llerror.h" #include "llrand.h" -#include "llmd5.h" #include "llstring.h" #include "lltimer.h" #include "llthread.h" #include "llmutex.h" +#include "hbxxh.h" const LLUUID LLUUID::null; const LLTransactionID LLTransactionID::tnull; @@ -402,11 +402,9 @@ LLUUID LLUUID::operator^(const LLUUID& rhs) const void LLUUID::combine(const LLUUID& other, LLUUID& result) const { - LLMD5 md5_uuid; - md5_uuid.update((unsigned char*)mData, 16); - md5_uuid.update((unsigned char*)other.mData, 16); - md5_uuid.finalize(); - md5_uuid.raw_digest(result.mData); + HBXXH128 hash((const void*)mData, 16, false); // false = do not finalize + hash.update((const void*)other.mData, 16); + hash.digest(result); } LLUUID LLUUID::combine(const LLUUID &other) const @@ -857,17 +855,12 @@ void LLUUID::generate() tmp >>= 8; mData[8] = (unsigned char) tmp; - LLMD5 md5_uuid; - - md5_uuid.update(mData,16); - md5_uuid.finalize(); - md5_uuid.raw_digest(mData); + HBXXH128::digest(*this, (const void*)mData, 16); } void LLUUID::generate(const std::string& hash_string) { - LLMD5 md5_uuid((U8*)hash_string.c_str()); - md5_uuid.raw_digest(mData); + HBXXH128::digest(*this, hash_string); } U32 LLUUID::getRandomSeed() @@ -885,13 +878,8 @@ U32 LLUUID::getRandomSeed() seed[7]=(unsigned char)(pid); getSystemTime((uuid_time_t *)(&seed[8])); - LLMD5 md5_seed; - - md5_seed.update(seed,16); - md5_seed.finalize(); - md5_seed.raw_digest(seed); - - return(*(U32 *)seed); + U64 seed64 = HBXXH64((const void*)seed, 16).digest(); + return U32(seed64) ^ U32(seed64 >> 32); } BOOL LLUUID::parseUUID(const std::string& buf, LLUUID* value) -- cgit v1.2.3 From b5b0a3fbf6b58c7f1a7e4377bfeb6adb3337e4bf Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 31 Jan 2023 18:48:16 +0200 Subject: SL-19110 Fix xxhash build link and properly register contribution --- indra/llcommon/hbxxh.cpp | 4 ++-- indra/llcommon/hbxxh.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/hbxxh.cpp b/indra/llcommon/hbxxh.cpp index e94581d415..388269d6c8 100644 --- a/indra/llcommon/hbxxh.cpp +++ b/indra/llcommon/hbxxh.cpp @@ -2,7 +2,7 @@ * @file hbxxh.cpp * @brief High performances vectorized hashing based on xxHash. * - * $LicenseInfo:firstyear=2023&license=viewergpl$ + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (c) 2023, Henri Beauchamp. * @@ -34,7 +34,7 @@ // in your build, in which case the latter would be used instead. For ARM64 // builds, this would also automatically enable NEON vectorization. #define XXH_INLINE_ALL -#include "xxhash.h" +#include "xxhash/xxhash.h" #include "hbxxh.h" diff --git a/indra/llcommon/hbxxh.h b/indra/llcommon/hbxxh.h index 8a5f977648..236716722a 100644 --- a/indra/llcommon/hbxxh.h +++ b/indra/llcommon/hbxxh.h @@ -2,7 +2,7 @@ * @file hbxxh.h * @brief High performances vectorized hashing based on xxHash. * - * $LicenseInfo:firstyear=2023&license=viewergpl$ + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (c) 2023, Henri Beauchamp. * -- cgit v1.2.3 From 3561e9b5d5a7fef8e6a710c7b6f9b13243a4d51c Mon Sep 17 00:00:00 2001 From: Henri Beauchamp Date: Mon, 13 Feb 2023 18:34:00 +0100 Subject: SL-19110 revert LLUUID::combine() to old algorithm to match server code. (#75) As it happens, the change in the LLUUID::combine() algorithm introduced by one of my previous commits is causing invalid assets creation (seen with some clothing items, such as Shape and Universal types); obviously, the server is using the old algorithm for UUID validation purpose of these assets. This commit reverts LLUUID::combine() code to use LLMD5. --- indra/llcommon/lluuid.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lluuid.cpp b/indra/llcommon/lluuid.cpp index 47c6bc71c8..fc04dca08d 100644 --- a/indra/llcommon/lluuid.cpp +++ b/indra/llcommon/lluuid.cpp @@ -44,6 +44,7 @@ #include "lltimer.h" #include "llthread.h" #include "llmutex.h" +#include "llmd5.h" #include "hbxxh.h" const LLUUID LLUUID::null; @@ -400,11 +401,16 @@ LLUUID LLUUID::operator^(const LLUUID& rhs) const return id; } +// WARNING: this algorithm SHALL NOT be changed. It is also used by the server +// and plays a role in some assets validation (e.g. clothing items). Changing +// it would cause invalid assets. void LLUUID::combine(const LLUUID& other, LLUUID& result) const { - HBXXH128 hash((const void*)mData, 16, false); // false = do not finalize - hash.update((const void*)other.mData, 16); - hash.digest(result); + LLMD5 md5_uuid; + md5_uuid.update((unsigned char*)mData, 16); + md5_uuid.update((unsigned char*)other.mData, 16); + md5_uuid.finalize(); + md5_uuid.raw_digest(result.mData); } LLUUID LLUUID::combine(const LLUUID &other) const -- cgit v1.2.3 From 81f2d5b1813288fac77c5d2884fe16aaa00709f3 Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Mon, 6 Mar 2023 10:58:46 -0600 Subject: DRTVWR-559 Return to MD5 for LLUUID operations. Existing LLUUID API implementations must not change, but may add "fast" variants where appropriate in the future. --- indra/llcommon/lluuid.cpp | 1443 +++++++++++++++++++++++---------------------- 1 file changed, 725 insertions(+), 718 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lluuid.cpp b/indra/llcommon/lluuid.cpp index fc04dca08d..3619a64ea9 100644 --- a/indra/llcommon/lluuid.cpp +++ b/indra/llcommon/lluuid.cpp @@ -1,31 +1,31 @@ -/** +/** * @file lluuid.cpp * * $LicenseInfo:firstyear=2000&license=viewerlgpl$ * Second Life Viewer Source Code * 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. - * + * * 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" -// We can't use WIN32_LEAN_AND_MEAN here, needs lots of includes. + // We can't use WIN32_LEAN_AND_MEAN here, needs lots of includes. #if LL_WINDOWS #include "llwin32headers.h" // ugh, this is ugly. We need to straighten out our linking for this library @@ -40,18 +40,17 @@ #include "lluuid.h" #include "llerror.h" #include "llrand.h" +#include "llmd5.h" #include "llstring.h" #include "lltimer.h" #include "llthread.h" #include "llmutex.h" -#include "llmd5.h" -#include "hbxxh.h" const LLUUID LLUUID::null; const LLTransactionID LLTransactionID::tnull; // static -LLMutex * LLUUID::mMutex = NULL; +LLMutex* LLUUID::mMutex = NULL; @@ -60,15 +59,15 @@ LLMutex * LLUUID::mMutex = NULL; NOT DONE YET!!! static char BASE85_TABLE[] = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', - 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', - 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', - 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', - 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', - 'y', 'z', '!', '#', '$', '%', '&', '(', ')', '*', - '+', '-', ';', '[', '=', '>', '?', '@', '^', '_', - '`', '{', '|', '}', '~', '\0' + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', + 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', + 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', + 'y', 'z', '!', '#', '$', '%', '&', '(', ')', '*', + '+', '-', ';', '[', '=', '>', '?', '@', '^', '_', + '`', '{', '|', '}', '~', '\0' }; @@ -94,28 +93,28 @@ return ret; void LLUUID::toBase85(char* out) { - U32* me = (U32*)&(mData[0]); - for(S32 i = 0; i < 4; ++i) - { - char* o = &out[i*i]; - for(S32 j = 0; j < 5; ++j) - { - o[4-j] = BASE85_TABLE[ me[i] % 85]; - word /= 85; - } - } + U32* me = (U32*)&(mData[0]); + for(S32 i = 0; i < 4; ++i) + { + char* o = &out[i*i]; + for(S32 j = 0; j < 5; ++j) + { + o[4-j] = BASE85_TABLE[ me[i] % 85]; + word /= 85; + } + } } unsigned int decode( char const * fiveChars ) throw( bad_input_data ) { - unsigned int ret = 0; - for( S32 ix = 0; ix < 5; ++ix ) - { - char * s = strchr( encodeTable, fiveChars[ ix ] ); - ret = ret * 85 + (s-encodeTable); - } - return ret; -} + unsigned int ret = 0; + for( S32 ix = 0; ix < 5; ++ix ) + { + char * s = strchr( encodeTable, fiveChars[ ix ] ); + ret = ret * 85 + (s-encodeTable); + } + return ret; +} */ #define LL_USE_JANKY_RANDOM_NUMBER_GENERATOR 0 @@ -130,8 +129,8 @@ static U64 sJankyRandomSeed(LLUUID::getRandomSeed()); */ U32 janky_fast_random_bytes() { - sJankyRandomSeed = U64L(1664525) * sJankyRandomSeed + U64L(1013904223); - return (U32)sJankyRandomSeed; + sJankyRandomSeed = U64L(1664525) * sJankyRandomSeed + U64L(1013904223); + return (U32)sJankyRandomSeed; } /** @@ -139,8 +138,8 @@ U32 janky_fast_random_bytes() */ U32 janky_fast_random_byes_range(U32 val) { - sJankyRandomSeed = U64L(1664525) * sJankyRandomSeed + U64L(1013904223); - return (U32)(sJankyRandomSeed) % val; + sJankyRandomSeed = U64L(1664525) * sJankyRandomSeed + U64L(1013904223); + return (U32)(sJankyRandomSeed) % val; } /** @@ -148,365 +147,362 @@ U32 janky_fast_random_byes_range(U32 val) */ U32 janky_fast_random_seeded_bytes(U32 seed, U32 val) { - seed = U64L(1664525) * (U64)(seed) + U64L(1013904223); - return (U32)(seed) % val; + seed = U64L(1664525) * (U64)(seed)+U64L(1013904223); + return (U32)(seed) % val; } #endif // Common to all UUID implementations void LLUUID::toString(std::string& out) const { - out = llformat( - "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", - (U8)(mData[0]), - (U8)(mData[1]), - (U8)(mData[2]), - (U8)(mData[3]), - (U8)(mData[4]), - (U8)(mData[5]), - (U8)(mData[6]), - (U8)(mData[7]), - (U8)(mData[8]), - (U8)(mData[9]), - (U8)(mData[10]), - (U8)(mData[11]), - (U8)(mData[12]), - (U8)(mData[13]), - (U8)(mData[14]), - (U8)(mData[15])); + out = llformat( + "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + (U8)(mData[0]), + (U8)(mData[1]), + (U8)(mData[2]), + (U8)(mData[3]), + (U8)(mData[4]), + (U8)(mData[5]), + (U8)(mData[6]), + (U8)(mData[7]), + (U8)(mData[8]), + (U8)(mData[9]), + (U8)(mData[10]), + (U8)(mData[11]), + (U8)(mData[12]), + (U8)(mData[13]), + (U8)(mData[14]), + (U8)(mData[15])); } // *TODO: deprecate -void LLUUID::toString(char *out) const +void LLUUID::toString(char* out) const { - std::string buffer; - toString(buffer); - strcpy(out,buffer.c_str()); /* Flawfinder: ignore */ + std::string buffer; + toString(buffer); + strcpy(out, buffer.c_str()); /* Flawfinder: ignore */ } void LLUUID::toCompressedString(std::string& out) const { - char bytes[UUID_BYTES+1]; - memcpy(bytes, mData, UUID_BYTES); /* Flawfinder: ignore */ - bytes[UUID_BYTES] = '\0'; - out.assign(bytes, UUID_BYTES); + char bytes[UUID_BYTES + 1]; + memcpy(bytes, mData, UUID_BYTES); /* Flawfinder: ignore */ + bytes[UUID_BYTES] = '\0'; + out.assign(bytes, UUID_BYTES); } // *TODO: deprecate -void LLUUID::toCompressedString(char *out) const +void LLUUID::toCompressedString(char* out) const { - memcpy(out, mData, UUID_BYTES); /* Flawfinder: ignore */ - out[UUID_BYTES] = '\0'; + memcpy(out, mData, UUID_BYTES); /* Flawfinder: ignore */ + out[UUID_BYTES] = '\0'; } std::string LLUUID::getString() const { - return asString(); + return asString(); } std::string LLUUID::asString() const { - std::string str; - toString(str); - return str; + std::string str; + toString(str); + return str; } BOOL LLUUID::set(const char* in_string, BOOL emit) { - return set(ll_safe_string(in_string),emit); + return set(ll_safe_string(in_string), emit); } BOOL LLUUID::set(const std::string& in_string, BOOL emit) { - BOOL broken_format = FALSE; - - // empty strings should make NULL uuid - if (in_string.empty()) - { - setNull(); - return TRUE; - } - - if (in_string.length() != (UUID_STR_LENGTH - 1)) /* Flawfinder: ignore */ - { - // I'm a moron. First implementation didn't have the right UUID format. - // Shouldn't see any of these any more - if (in_string.length() == (UUID_STR_LENGTH - 2)) /* Flawfinder: ignore */ - { - if(emit) - { - LL_WARNS() << "Warning! Using broken UUID string format" << LL_ENDL; - } - broken_format = TRUE; - } - else - { - // Bad UUID string. Spam as INFO, as most cases we don't care. - if(emit) - { - //don't spam the logs because a resident can't spell. - LL_WARNS() << "Bad UUID string: " << in_string << LL_ENDL; - } - setNull(); - return FALSE; - } - } - - U8 cur_pos = 0; - S32 i; - for (i = 0; i < UUID_BYTES; i++) - { - if ((i == 4) || (i == 6) || (i == 8) || (i == 10)) - { - cur_pos++; - if (broken_format && (i==10)) - { - // Missing - in the broken format - cur_pos--; - } - } - - mData[i] = 0; - - if ((in_string[cur_pos] >= '0') && (in_string[cur_pos] <= '9')) - { - mData[i] += (U8)(in_string[cur_pos] - '0'); - } - else if ((in_string[cur_pos] >= 'a') && (in_string[cur_pos] <='f')) - { - mData[i] += (U8)(10 + in_string[cur_pos] - 'a'); - } - else if ((in_string[cur_pos] >= 'A') && (in_string[cur_pos] <='F')) - { - mData[i] += (U8)(10 + in_string[cur_pos] - 'A'); - } - else - { - if(emit) - { - LL_WARNS() << "Invalid UUID string character" << LL_ENDL; - } - setNull(); - return FALSE; - } - - mData[i] = mData[i] << 4; - cur_pos++; - - if ((in_string[cur_pos] >= '0') && (in_string[cur_pos] <= '9')) - { - mData[i] += (U8)(in_string[cur_pos] - '0'); - } - else if ((in_string[cur_pos] >= 'a') && (in_string[cur_pos] <='f')) - { - mData[i] += (U8)(10 + in_string[cur_pos] - 'a'); - } - else if ((in_string[cur_pos] >= 'A') && (in_string[cur_pos] <='F')) - { - mData[i] += (U8)(10 + in_string[cur_pos] - 'A'); - } - else - { - if(emit) - { - LL_WARNS() << "Invalid UUID string character" << LL_ENDL; - } - setNull(); - return FALSE; - } - cur_pos++; - } - - return TRUE; + BOOL broken_format = FALSE; + + // empty strings should make NULL uuid + if (in_string.empty()) + { + setNull(); + return TRUE; + } + + if (in_string.length() != (UUID_STR_LENGTH - 1)) /* Flawfinder: ignore */ + { + // I'm a moron. First implementation didn't have the right UUID format. + // Shouldn't see any of these any more + if (in_string.length() == (UUID_STR_LENGTH - 2)) /* Flawfinder: ignore */ + { + if (emit) + { + LL_WARNS() << "Warning! Using broken UUID string format" << LL_ENDL; + } + broken_format = TRUE; + } + else + { + // Bad UUID string. Spam as INFO, as most cases we don't care. + if (emit) + { + //don't spam the logs because a resident can't spell. + LL_WARNS() << "Bad UUID string: " << in_string << LL_ENDL; + } + setNull(); + return FALSE; + } + } + + U8 cur_pos = 0; + S32 i; + for (i = 0; i < UUID_BYTES; i++) + { + if ((i == 4) || (i == 6) || (i == 8) || (i == 10)) + { + cur_pos++; + if (broken_format && (i == 10)) + { + // Missing - in the broken format + cur_pos--; + } + } + + mData[i] = 0; + + if ((in_string[cur_pos] >= '0') && (in_string[cur_pos] <= '9')) + { + mData[i] += (U8)(in_string[cur_pos] - '0'); + } + else if ((in_string[cur_pos] >= 'a') && (in_string[cur_pos] <= 'f')) + { + mData[i] += (U8)(10 + in_string[cur_pos] - 'a'); + } + else if ((in_string[cur_pos] >= 'A') && (in_string[cur_pos] <= 'F')) + { + mData[i] += (U8)(10 + in_string[cur_pos] - 'A'); + } + else + { + if (emit) + { + LL_WARNS() << "Invalid UUID string character" << LL_ENDL; + } + setNull(); + return FALSE; + } + + mData[i] = mData[i] << 4; + cur_pos++; + + if ((in_string[cur_pos] >= '0') && (in_string[cur_pos] <= '9')) + { + mData[i] += (U8)(in_string[cur_pos] - '0'); + } + else if ((in_string[cur_pos] >= 'a') && (in_string[cur_pos] <= 'f')) + { + mData[i] += (U8)(10 + in_string[cur_pos] - 'a'); + } + else if ((in_string[cur_pos] >= 'A') && (in_string[cur_pos] <= 'F')) + { + mData[i] += (U8)(10 + in_string[cur_pos] - 'A'); + } + else + { + if (emit) + { + LL_WARNS() << "Invalid UUID string character" << LL_ENDL; + } + setNull(); + return FALSE; + } + cur_pos++; + } + + return TRUE; } BOOL LLUUID::validate(const std::string& in_string) { - BOOL broken_format = FALSE; - if (in_string.length() != (UUID_STR_LENGTH - 1)) /* Flawfinder: ignore */ - { - // I'm a moron. First implementation didn't have the right UUID format. - if (in_string.length() == (UUID_STR_LENGTH - 2)) /* Flawfinder: ignore */ - { - broken_format = TRUE; - } - else - { - return FALSE; - } - } - - U8 cur_pos = 0; - for (U32 i = 0; i < 16; i++) - { - if ((i == 4) || (i == 6) || (i == 8) || (i == 10)) - { - cur_pos++; - if (broken_format && (i==10)) - { - // Missing - in the broken format - cur_pos--; - } - } - - if ((in_string[cur_pos] >= '0') && (in_string[cur_pos] <= '9')) - { - } - else if ((in_string[cur_pos] >= 'a') && (in_string[cur_pos] <='f')) - { - } - else if ((in_string[cur_pos] >= 'A') && (in_string[cur_pos] <='F')) - { - } - else - { - return FALSE; - } - - cur_pos++; - - if ((in_string[cur_pos] >= '0') && (in_string[cur_pos] <= '9')) - { - } - else if ((in_string[cur_pos] >= 'a') && (in_string[cur_pos] <='f')) - { - } - else if ((in_string[cur_pos] >= 'A') && (in_string[cur_pos] <='F')) - { - } - else - { - return FALSE; - } - cur_pos++; - } - return TRUE; + BOOL broken_format = FALSE; + if (in_string.length() != (UUID_STR_LENGTH - 1)) /* Flawfinder: ignore */ + { + // I'm a moron. First implementation didn't have the right UUID format. + if (in_string.length() == (UUID_STR_LENGTH - 2)) /* Flawfinder: ignore */ + { + broken_format = TRUE; + } + else + { + return FALSE; + } + } + + U8 cur_pos = 0; + for (U32 i = 0; i < 16; i++) + { + if ((i == 4) || (i == 6) || (i == 8) || (i == 10)) + { + cur_pos++; + if (broken_format && (i == 10)) + { + // Missing - in the broken format + cur_pos--; + } + } + + if ((in_string[cur_pos] >= '0') && (in_string[cur_pos] <= '9')) + { + } + else if ((in_string[cur_pos] >= 'a') && (in_string[cur_pos] <= 'f')) + { + } + else if ((in_string[cur_pos] >= 'A') && (in_string[cur_pos] <= 'F')) + { + } + else + { + return FALSE; + } + + cur_pos++; + + if ((in_string[cur_pos] >= '0') && (in_string[cur_pos] <= '9')) + { + } + else if ((in_string[cur_pos] >= 'a') && (in_string[cur_pos] <= 'f')) + { + } + else if ((in_string[cur_pos] >= 'A') && (in_string[cur_pos] <= 'F')) + { + } + else + { + return FALSE; + } + cur_pos++; + } + return TRUE; } const LLUUID& LLUUID::operator^=(const LLUUID& rhs) { - U32* me = (U32*)&(mData[0]); - const U32* other = (U32*)&(rhs.mData[0]); - for(S32 i = 0; i < 4; ++i) - { - me[i] = me[i] ^ other[i]; - } - return *this; + U32* me = (U32*)&(mData[0]); + const U32* other = (U32*)&(rhs.mData[0]); + for (S32 i = 0; i < 4; ++i) + { + me[i] = me[i] ^ other[i]; + } + return *this; } LLUUID LLUUID::operator^(const LLUUID& rhs) const { - LLUUID id(*this); - id ^= rhs; - return id; + LLUUID id(*this); + id ^= rhs; + return id; } -// WARNING: this algorithm SHALL NOT be changed. It is also used by the server -// and plays a role in some assets validation (e.g. clothing items). Changing -// it would cause invalid assets. void LLUUID::combine(const LLUUID& other, LLUUID& result) const { - LLMD5 md5_uuid; - md5_uuid.update((unsigned char*)mData, 16); - md5_uuid.update((unsigned char*)other.mData, 16); - md5_uuid.finalize(); - md5_uuid.raw_digest(result.mData); + LLMD5 md5_uuid; + md5_uuid.update((unsigned char*)mData, 16); + md5_uuid.update((unsigned char*)other.mData, 16); + md5_uuid.finalize(); + md5_uuid.raw_digest(result.mData); } -LLUUID LLUUID::combine(const LLUUID &other) const +LLUUID LLUUID::combine(const LLUUID& other) const { - LLUUID combination; - combine(other, combination); - return combination; + LLUUID combination; + combine(other, combination); + return combination; } -std::ostream& operator<<(std::ostream& s, const LLUUID &uuid) +std::ostream& operator<<(std::ostream& s, const LLUUID& uuid) { - std::string uuid_str; - uuid.toString(uuid_str); - s << uuid_str; - return s; + std::string uuid_str; + uuid.toString(uuid_str); + s << uuid_str; + return s; } -std::istream& operator>>(std::istream &s, LLUUID &uuid) +std::istream& operator>>(std::istream& s, LLUUID& uuid) { - U32 i; - char uuid_str[UUID_STR_LENGTH]; /* Flawfinder: ignore */ - for (i = 0; i < UUID_STR_LENGTH-1; i++) - { - s >> uuid_str[i]; - } - uuid_str[i] = '\0'; - uuid.set(std::string(uuid_str)); - return s; + U32 i; + char uuid_str[UUID_STR_LENGTH]; /* Flawfinder: ignore */ + for (i = 0; i < UUID_STR_LENGTH - 1; i++) + { + s >> uuid_str[i]; + } + uuid_str[i] = '\0'; + uuid.set(std::string(uuid_str)); + return s; } -static void get_random_bytes(void *buf, int nbytes) +static void get_random_bytes(void* buf, int nbytes) { - int i; - char *cp = (char *) buf; - - // *NOTE: If we are not using the janky generator ll_rand() - // generates at least 3 good bytes of data since it is 0 to - // RAND_MAX. This could be made more efficient by copying all the - // bytes. - for (i=0; i < nbytes; i++) + int i; + char* cp = (char*)buf; + + // *NOTE: If we are not using the janky generator ll_rand() + // generates at least 3 good bytes of data since it is 0 to + // RAND_MAX. This could be made more efficient by copying all the + // bytes. + for (i = 0; i < nbytes; i++) #if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR - *cp++ = janky_fast_random_bytes() & 0xFF; + * cp++ = janky_fast_random_bytes() & 0xFF; #else - *cp++ = ll_rand() & 0xFF; + * cp++ = ll_rand() & 0xFF; #endif - return; + return; } #if LL_WINDOWS typedef struct _ASTAT_ { - ADAPTER_STATUS adapt; - NAME_BUFFER NameBuff [30]; + ADAPTER_STATUS adapt; + NAME_BUFFER NameBuff[30]; }ASTAT, * PASTAT; // static -S32 LLUUID::getNodeID(unsigned char *node_id) +S32 LLUUID::getNodeID(unsigned char* node_id) { - ASTAT Adapter; - NCB Ncb; - UCHAR uRetCode; - LANA_ENUM lenum; - int i; - int retval = 0; - - memset( &Ncb, 0, sizeof(Ncb) ); - Ncb.ncb_command = NCBENUM; - Ncb.ncb_buffer = (UCHAR *)&lenum; - Ncb.ncb_length = sizeof(lenum); - uRetCode = Netbios( &Ncb ); - - for(i=0; i < lenum.length ;i++) - { - memset( &Ncb, 0, sizeof(Ncb) ); - Ncb.ncb_command = NCBRESET; - Ncb.ncb_lana_num = lenum.lana[i]; - - uRetCode = Netbios( &Ncb ); - - memset( &Ncb, 0, sizeof (Ncb) ); - Ncb.ncb_command = NCBASTAT; - Ncb.ncb_lana_num = lenum.lana[i]; - - strcpy( (char *)Ncb.ncb_callname, "* " ); /* Flawfinder: ignore */ - Ncb.ncb_buffer = (unsigned char *)&Adapter; - Ncb.ncb_length = sizeof(Adapter); - - uRetCode = Netbios( &Ncb ); - if ( uRetCode == 0 ) - { - memcpy(node_id,Adapter.adapt.adapter_address,6); /* Flawfinder: ignore */ - retval = 1; - } - } - return retval; + ASTAT Adapter; + NCB Ncb; + UCHAR uRetCode; + LANA_ENUM lenum; + int i; + int retval = 0; + + memset(&Ncb, 0, sizeof(Ncb)); + Ncb.ncb_command = NCBENUM; + Ncb.ncb_buffer = (UCHAR*)&lenum; + Ncb.ncb_length = sizeof(lenum); + uRetCode = Netbios(&Ncb); + + for (i = 0; i < lenum.length; i++) + { + memset(&Ncb, 0, sizeof(Ncb)); + Ncb.ncb_command = NCBRESET; + Ncb.ncb_lana_num = lenum.lana[i]; + + uRetCode = Netbios(&Ncb); + + memset(&Ncb, 0, sizeof(Ncb)); + Ncb.ncb_command = NCBASTAT; + Ncb.ncb_lana_num = lenum.lana[i]; + + strcpy((char*)Ncb.ncb_callname, "* "); /* Flawfinder: ignore */ + Ncb.ncb_buffer = (unsigned char*)&Adapter; + Ncb.ncb_length = sizeof(Adapter); + + uRetCode = Netbios(&Ncb); + if (uRetCode == 0) + { + memcpy(node_id, Adapter.adapt.adapter_address, 6); /* Flawfinder: ignore */ + retval = 1; + } + } + return retval; } #elif LL_DARWIN @@ -525,66 +521,66 @@ S32 LLUUID::getNodeID(unsigned char *node_id) #include #include -// static -S32 LLUUID::getNodeID(unsigned char *node_id) + // static +S32 LLUUID::getNodeID(unsigned char* node_id) { - int i; - unsigned char *a = NULL; - struct ifaddrs *ifap, *ifa; - int rv; - S32 result = 0; - - if ((rv=getifaddrs(&ifap))==-1) - { - return -1; - } - if (ifap == NULL) - { - return -1; - } - - for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) - { -// printf("Interface %s, address family %d, ", ifa->ifa_name, ifa->ifa_addr->sa_family); - for(i=0; i< ifa->ifa_addr->sa_len; i++) - { -// printf("%02X ", (unsigned char)ifa->ifa_addr->sa_data[i]); - } -// printf("\n"); - - if(ifa->ifa_addr->sa_family == AF_LINK) - { - // This is a link-level address - struct sockaddr_dl *lla = (struct sockaddr_dl *)ifa->ifa_addr; - -// printf("\tLink level address, type %02X\n", lla->sdl_type); - - if(lla->sdl_type == IFT_ETHER) - { - // Use the first ethernet MAC in the list. - // For some reason, the macro LLADDR() defined in net/if_dl.h doesn't expand correctly. This is what it would do. - a = (unsigned char *)&((lla)->sdl_data); - a += (lla)->sdl_nlen; - - if (!a[0] && !a[1] && !a[2] && !a[3] && !a[4] && !a[5]) - { - continue; - } - - if (node_id) - { - memcpy(node_id, a, 6); - result = 1; - } - - // We found one. - break; - } - } - } - freeifaddrs(ifap); - - return result; + int i; + unsigned char* a = NULL; + struct ifaddrs* ifap, * ifa; + int rv; + S32 result = 0; + + if ((rv = getifaddrs(&ifap)) == -1) + { + return -1; + } + if (ifap == NULL) + { + return -1; + } + + for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) + { + // printf("Interface %s, address family %d, ", ifa->ifa_name, ifa->ifa_addr->sa_family); + for (i = 0; i < ifa->ifa_addr->sa_len; i++) + { + // printf("%02X ", (unsigned char)ifa->ifa_addr->sa_data[i]); + } + // printf("\n"); + + if (ifa->ifa_addr->sa_family == AF_LINK) + { + // This is a link-level address + struct sockaddr_dl* lla = (struct sockaddr_dl*)ifa->ifa_addr; + + // printf("\tLink level address, type %02X\n", lla->sdl_type); + + if (lla->sdl_type == IFT_ETHER) + { + // Use the first ethernet MAC in the list. + // For some reason, the macro LLADDR() defined in net/if_dl.h doesn't expand correctly. This is what it would do. + a = (unsigned char*)&((lla)->sdl_data); + a += (lla)->sdl_nlen; + + if (!a[0] && !a[1] && !a[2] && !a[3] && !a[4] && !a[5]) + { + continue; + } + + if (node_id) + { + memcpy(node_id, a, 6); + result = 1; + } + + // We found one. + break; + } + } + } + freeifaddrs(ifap); + + return result; } #else @@ -611,22 +607,22 @@ S32 LLUUID::getNodeID(unsigned char *node_id) #endif #endif -// static -S32 LLUUID::getNodeID(unsigned char *node_id) + // static +S32 LLUUID::getNodeID(unsigned char* node_id) { - int sd; - struct ifreq ifr, *ifrp; - struct ifconf ifc; - char buf[1024]; - int n, i; - unsigned char *a; - -/* - * BSD 4.4 defines the size of an ifreq to be - * max(sizeof(ifreq), sizeof(ifreq.ifr_name)+ifreq.ifr_addr.sa_len - * However, under earlier systems, sa_len isn't present, so the size is - * just sizeof(struct ifreq) - */ + int sd; + struct ifreq ifr, * ifrp; + struct ifconf ifc; + char buf[1024]; + int n, i; + unsigned char* a; + + /* + * BSD 4.4 defines the size of an ifreq to be + * max(sizeof(ifreq), sizeof(ifreq.ifr_name)+ifreq.ifr_addr.sa_len + * However, under earlier systems, sa_len isn't present, so the size is + * just sizeof(struct ifreq) + */ #ifdef HAVE_SA_LEN #ifndef max #define max(a,b) ((a) > (b) ? (a) : (b)) @@ -637,345 +633,356 @@ S32 LLUUID::getNodeID(unsigned char *node_id) #define ifreq_size(i) sizeof(struct ifreq) #endif /* HAVE_SA_LEN*/ - sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); - if (sd < 0) { - return -1; - } - memset(buf, 0, sizeof(buf)); - ifc.ifc_len = sizeof(buf); - ifc.ifc_buf = buf; - if (ioctl (sd, SIOCGIFCONF, (char *)&ifc) < 0) { - close(sd); - return -1; - } - n = ifc.ifc_len; - for (i = 0; i < n; i+= ifreq_size(*ifr) ) { - ifrp = (struct ifreq *)((char *) ifc.ifc_buf+i); - strncpy(ifr.ifr_name, ifrp->ifr_name, IFNAMSIZ); /* Flawfinder: ignore */ + sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + if (sd < 0) { + return -1; + } + memset(buf, 0, sizeof(buf)); + ifc.ifc_len = sizeof(buf); + ifc.ifc_buf = buf; + if (ioctl(sd, SIOCGIFCONF, (char*)&ifc) < 0) { + close(sd); + return -1; + } + n = ifc.ifc_len; + for (i = 0; i < n; i += ifreq_size(*ifr)) { + ifrp = (struct ifreq*)((char*)ifc.ifc_buf + i); + strncpy(ifr.ifr_name, ifrp->ifr_name, IFNAMSIZ); /* Flawfinder: ignore */ #ifdef SIOCGIFHWADDR - if (ioctl(sd, SIOCGIFHWADDR, &ifr) < 0) - continue; - a = (unsigned char *) &ifr.ifr_hwaddr.sa_data; + if (ioctl(sd, SIOCGIFHWADDR, &ifr) < 0) + continue; + a = (unsigned char*)&ifr.ifr_hwaddr.sa_data; #else #ifdef SIOCGENADDR - if (ioctl(sd, SIOCGENADDR, &ifr) < 0) - continue; - a = (unsigned char *) ifr.ifr_enaddr; + if (ioctl(sd, SIOCGENADDR, &ifr) < 0) + continue; + a = (unsigned char*)ifr.ifr_enaddr; #else - /* - * XXX we don't have a way of getting the hardware - * address - */ - close(sd); - return 0; + /* + * XXX we don't have a way of getting the hardware + * address + */ + close(sd); + return 0; #endif /* SIOCGENADDR */ #endif /* SIOCGIFHWADDR */ - if (!a[0] && !a[1] && !a[2] && !a[3] && !a[4] && !a[5]) - continue; - if (node_id) { - memcpy(node_id, a, 6); /* Flawfinder: ignore */ - close(sd); - return 1; - } - } - close(sd); - return 0; + if (!a[0] && !a[1] && !a[2] && !a[3] && !a[4] && !a[5]) + continue; + if (node_id) { + memcpy(node_id, a, 6); /* Flawfinder: ignore */ + close(sd); + return 1; + } + } + close(sd); + return 0; } #endif -S32 LLUUID::cmpTime(uuid_time_t *t1, uuid_time_t *t2) +S32 LLUUID::cmpTime(uuid_time_t* t1, uuid_time_t* t2) { - // Compare two time values. + // Compare two time values. - if (t1->high < t2->high) return -1; - if (t1->high > t2->high) return 1; - if (t1->low < t2->low) return -1; - if (t1->low > t2->low) return 1; - return 0; + if (t1->high < t2->high) return -1; + if (t1->high > t2->high) return 1; + if (t1->low < t2->low) return -1; + if (t1->low > t2->low) return 1; + return 0; } -void LLUUID::getSystemTime(uuid_time_t *timestamp) +void LLUUID::getSystemTime(uuid_time_t* timestamp) { - // Get system time with 100ns precision. Time is since Oct 15, 1582. + // Get system time with 100ns precision. Time is since Oct 15, 1582. #if LL_WINDOWS - ULARGE_INTEGER time; - GetSystemTimeAsFileTime((FILETIME *)&time); - // NT keeps time in FILETIME format which is 100ns ticks since - // Jan 1, 1601. UUIDs use time in 100ns ticks since Oct 15, 1582. - // The difference is 17 Days in Oct + 30 (Nov) + 31 (Dec) - // + 18 years and 5 leap days. - time.QuadPart += - (unsigned __int64) (1000*1000*10) // seconds - * (unsigned __int64) (60 * 60 * 24) // days - * (unsigned __int64) (17+30+31+365*18+5); // # of days - - timestamp->high = time.HighPart; - timestamp->low = time.LowPart; + ULARGE_INTEGER time; + GetSystemTimeAsFileTime((FILETIME*)&time); + // NT keeps time in FILETIME format which is 100ns ticks since + // Jan 1, 1601. UUIDs use time in 100ns ticks since Oct 15, 1582. + // The difference is 17 Days in Oct + 30 (Nov) + 31 (Dec) + // + 18 years and 5 leap days. + time.QuadPart += + (unsigned __int64)(1000 * 1000 * 10) // seconds + * (unsigned __int64)(60 * 60 * 24) // days + * (unsigned __int64)(17 + 30 + 31 + 365 * 18 + 5); // # of days + + timestamp->high = time.HighPart; + timestamp->low = time.LowPart; #else - struct timeval tp; - gettimeofday(&tp, 0); - - // Offset between UUID formatted times and Unix formatted times. - // UUID UTC base time is October 15, 1582. - // Unix base time is January 1, 1970. - U64 uuid_time = ((U64)tp.tv_sec * 10000000) + (tp.tv_usec * 10) + - U64L(0x01B21DD213814000); - timestamp->high = (U32) (uuid_time >> 32); - timestamp->low = (U32) (uuid_time & 0xFFFFFFFF); + struct timeval tp; + gettimeofday(&tp, 0); + + // Offset between UUID formatted times and Unix formatted times. + // UUID UTC base time is October 15, 1582. + // Unix base time is January 1, 1970. + U64 uuid_time = ((U64)tp.tv_sec * 10000000) + (tp.tv_usec * 10) + + U64L(0x01B21DD213814000); + timestamp->high = (U32)(uuid_time >> 32); + timestamp->low = (U32)(uuid_time & 0xFFFFFFFF); #endif } -void LLUUID::getCurrentTime(uuid_time_t *timestamp) +void LLUUID::getCurrentTime(uuid_time_t* timestamp) { - // Get current time as 60 bit 100ns ticks since whenever. - // Compensate for the fact that real clock resolution is less - // than 100ns. - - const U32 uuids_per_tick = 1024; - - static uuid_time_t time_last; - static U32 uuids_this_tick; - static BOOL init = FALSE; - - if (!init) { - getSystemTime(&time_last); - uuids_this_tick = uuids_per_tick; - init = TRUE; - mMutex = new LLMutex(); - } - - uuid_time_t time_now = {0,0}; - - while (1) { - getSystemTime(&time_now); - - // if clock reading changed since last UUID generated - if (cmpTime(&time_last, &time_now)) { - // reset count of uuid's generated with this clock reading - uuids_this_tick = 0; - break; - } - if (uuids_this_tick < uuids_per_tick) { - uuids_this_tick++; - break; - } - // going too fast for our clock; spin - } - - time_last = time_now; - - if (uuids_this_tick != 0) { - if (time_now.low & 0x80000000) { - time_now.low += uuids_this_tick; - if (!(time_now.low & 0x80000000)) - time_now.high++; - } else - time_now.low += uuids_this_tick; - } - - timestamp->high = time_now.high; - timestamp->low = time_now.low; + // Get current time as 60 bit 100ns ticks since whenever. + // Compensate for the fact that real clock resolution is less + // than 100ns. + + const U32 uuids_per_tick = 1024; + + static uuid_time_t time_last; + static U32 uuids_this_tick; + static BOOL init = FALSE; + + if (!init) { + getSystemTime(&time_last); + uuids_this_tick = uuids_per_tick; + init = TRUE; + mMutex = new LLMutex(); + } + + uuid_time_t time_now = { 0,0 }; + + while (1) { + getSystemTime(&time_now); + + // if clock reading changed since last UUID generated + if (cmpTime(&time_last, &time_now)) { + // reset count of uuid's generated with this clock reading + uuids_this_tick = 0; + break; + } + if (uuids_this_tick < uuids_per_tick) { + uuids_this_tick++; + break; + } + // going too fast for our clock; spin + } + + time_last = time_now; + + if (uuids_this_tick != 0) { + if (time_now.low & 0x80000000) { + time_now.low += uuids_this_tick; + if (!(time_now.low & 0x80000000)) + time_now.high++; + } + else + time_now.low += uuids_this_tick; + } + + timestamp->high = time_now.high; + timestamp->low = time_now.low; } void LLUUID::generate() { - // Create a UUID. - uuid_time_t timestamp; - - static unsigned char node_id[6]; /* Flawfinder: ignore */ - static int has_init = 0; - - // Create a UUID. - static uuid_time_t time_last = {0,0}; - static U16 clock_seq = 0; + // Create a UUID. + uuid_time_t timestamp; + + static unsigned char node_id[6]; /* Flawfinder: ignore */ + static int has_init = 0; + + // Create a UUID. + static uuid_time_t time_last = { 0,0 }; + static U16 clock_seq = 0; #if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR - static U32 seed = 0L; // dummy seed. reset it below + static U32 seed = 0L; // dummy seed. reset it below #endif - if (!has_init) - { - has_init = 1; - if (getNodeID(node_id) <= 0) - { - get_random_bytes(node_id, 6); - /* - * Set multicast bit, to prevent conflicts - * with IEEE 802 addresses obtained from - * network cards - */ - node_id[0] |= 0x80; - } - - getCurrentTime(&time_last); + if (!has_init) + { + has_init = 1; + if (getNodeID(node_id) <= 0) + { + get_random_bytes(node_id, 6); + /* + * Set multicast bit, to prevent conflicts + * with IEEE 802 addresses obtained from + * network cards + */ + node_id[0] |= 0x80; + } + + getCurrentTime(&time_last); #if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR - seed = time_last.low; + seed = time_last.low; #endif #if LL_USE_JANKY_RANDOM_NUMBER_GENERATOR - clock_seq = (U16)janky_fast_random_seeded_bytes(seed, 65536); + clock_seq = (U16)janky_fast_random_seeded_bytes(seed, 65536); #else - clock_seq = (U16)ll_rand(65536); + clock_seq = (U16)ll_rand(65536); #endif - } - - // get current time - getCurrentTime(×tamp); - U16 our_clock_seq = clock_seq; - - // if clock hasn't changed or went backward, change clockseq - if (cmpTime(×tamp, &time_last) != 1) - { - LLMutexLock lock(mMutex); - clock_seq = (clock_seq + 1) & 0x3FFF; - if (clock_seq == 0) - clock_seq++; - our_clock_seq = clock_seq; // Ensure we're using a different clock_seq value from previous time - } + } + + // get current time + getCurrentTime(×tamp); + U16 our_clock_seq = clock_seq; + + // if clock hasn't changed or went backward, change clockseq + if (cmpTime(×tamp, &time_last) != 1) + { + LLMutexLock lock(mMutex); + clock_seq = (clock_seq + 1) & 0x3FFF; + if (clock_seq == 0) + clock_seq++; + our_clock_seq = clock_seq; // Ensure we're using a different clock_seq value from previous time + } time_last = timestamp; - memcpy(mData+10, node_id, 6); /* Flawfinder: ignore */ - U32 tmp; - tmp = timestamp.low; - mData[3] = (unsigned char) tmp; - tmp >>= 8; - mData[2] = (unsigned char) tmp; - tmp >>= 8; - mData[1] = (unsigned char) tmp; - tmp >>= 8; - mData[0] = (unsigned char) tmp; - - tmp = (U16) timestamp.high; - mData[5] = (unsigned char) tmp; - tmp >>= 8; - mData[4] = (unsigned char) tmp; - - tmp = (timestamp.high >> 16) | 0x1000; - mData[7] = (unsigned char) tmp; - tmp >>= 8; - mData[6] = (unsigned char) tmp; - - tmp = our_clock_seq; - - mData[9] = (unsigned char) tmp; - tmp >>= 8; - mData[8] = (unsigned char) tmp; - - HBXXH128::digest(*this, (const void*)mData, 16); + memcpy(mData + 10, node_id, 6); /* Flawfinder: ignore */ + U32 tmp; + tmp = timestamp.low; + mData[3] = (unsigned char)tmp; + tmp >>= 8; + mData[2] = (unsigned char)tmp; + tmp >>= 8; + mData[1] = (unsigned char)tmp; + tmp >>= 8; + mData[0] = (unsigned char)tmp; + + tmp = (U16)timestamp.high; + mData[5] = (unsigned char)tmp; + tmp >>= 8; + mData[4] = (unsigned char)tmp; + + tmp = (timestamp.high >> 16) | 0x1000; + mData[7] = (unsigned char)tmp; + tmp >>= 8; + mData[6] = (unsigned char)tmp; + + tmp = our_clock_seq; + + mData[9] = (unsigned char)tmp; + tmp >>= 8; + mData[8] = (unsigned char)tmp; + + LLMD5 md5_uuid; + + md5_uuid.update(mData, 16); + md5_uuid.finalize(); + md5_uuid.raw_digest(mData); } void LLUUID::generate(const std::string& hash_string) { - HBXXH128::digest(*this, hash_string); + LLMD5 md5_uuid((U8*)hash_string.c_str()); + md5_uuid.raw_digest(mData); } U32 LLUUID::getRandomSeed() { - static unsigned char seed[16]; /* Flawfinder: ignore */ - - getNodeID(&seed[0]); + static unsigned char seed[16]; /* Flawfinder: ignore */ - // Incorporate the pid into the seed to prevent - // processes that start on the same host at the same - // time from generating the same seed. - pid_t pid = LLApp::getPid(); + getNodeID(&seed[0]); - seed[6]=(unsigned char)(pid >> 8); - seed[7]=(unsigned char)(pid); - getSystemTime((uuid_time_t *)(&seed[8])); + // Incorporate the pid into the seed to prevent + // processes that start on the same host at the same + // time from generating the same seed. + pid_t pid = LLApp::getPid(); - U64 seed64 = HBXXH64((const void*)seed, 16).digest(); - return U32(seed64) ^ U32(seed64 >> 32); + seed[6] = (unsigned char)(pid >> 8); + seed[7] = (unsigned char)(pid); + getSystemTime((uuid_time_t*)(&seed[8])); + + LLMD5 md5_seed; + + md5_seed.update(seed, 16); + md5_seed.finalize(); + md5_seed.raw_digest(seed); + + return(*(U32*)seed); } BOOL LLUUID::parseUUID(const std::string& buf, LLUUID* value) { - if( buf.empty() || value == NULL) - { - return FALSE; - } - - std::string temp( buf ); - LLStringUtil::trim(temp); - if( LLUUID::validate( temp ) ) - { - value->set( temp ); - return TRUE; - } - return FALSE; + if (buf.empty() || value == NULL) + { + return FALSE; + } + + std::string temp(buf); + LLStringUtil::trim(temp); + if (LLUUID::validate(temp)) + { + value->set(temp); + return TRUE; + } + return FALSE; } //static LLUUID LLUUID::generateNewID(std::string hash_string) { - LLUUID new_id; - if (hash_string.empty()) - { - new_id.generate(); - } - else - { - new_id.generate(hash_string); - } - return new_id; + LLUUID new_id; + if (hash_string.empty()) + { + new_id.generate(); + } + else + { + new_id.generate(hash_string); + } + return new_id; } LLAssetID LLTransactionID::makeAssetID(const LLUUID& session) const { - LLAssetID result; - if (isNull()) - { - result.setNull(); - } - else - { - combine(session, result); - } - return result; + LLAssetID result; + if (isNull()) + { + result.setNull(); + } + else + { + combine(session, result); + } + return result; } // Construct LLUUID::LLUUID() { - setNull(); + setNull(); } // Faster than copying from memory - void LLUUID::setNull() +void LLUUID::setNull() { - U32 *word = (U32 *)mData; - word[0] = 0; - word[1] = 0; - word[2] = 0; - word[3] = 0; + U32* word = (U32*)mData; + word[0] = 0; + word[1] = 0; + word[2] = 0; + word[3] = 0; } // Compare - bool LLUUID::operator==(const LLUUID& rhs) const +bool LLUUID::operator==(const LLUUID& rhs) const { - U32 *tmp = (U32 *)mData; - U32 *rhstmp = (U32 *)rhs.mData; - // Note: binary & to avoid branching - return - (tmp[0] == rhstmp[0]) & - (tmp[1] == rhstmp[1]) & - (tmp[2] == rhstmp[2]) & - (tmp[3] == rhstmp[3]); + U32* tmp = (U32*)mData; + U32* rhstmp = (U32*)rhs.mData; + // Note: binary & to avoid branching + return + (tmp[0] == rhstmp[0]) & + (tmp[1] == rhstmp[1]) & + (tmp[2] == rhstmp[2]) & + (tmp[3] == rhstmp[3]); } - bool LLUUID::operator!=(const LLUUID& rhs) const +bool LLUUID::operator!=(const LLUUID& rhs) const { - U32 *tmp = (U32 *)mData; - U32 *rhstmp = (U32 *)rhs.mData; - // Note: binary | to avoid branching - return - (tmp[0] != rhstmp[0]) | - (tmp[1] != rhstmp[1]) | - (tmp[2] != rhstmp[2]) | - (tmp[3] != rhstmp[3]); + U32* tmp = (U32*)mData; + U32* rhstmp = (U32*)rhs.mData; + // Note: binary | to avoid branching + return + (tmp[0] != rhstmp[0]) | + (tmp[1] != rhstmp[1]) | + (tmp[2] != rhstmp[2]) | + (tmp[3] != rhstmp[3]); } /* @@ -983,94 +990,94 @@ LLUUID::LLUUID() // to integers, among other things. Use isNull() or notNull(). LLUUID::operator bool() const { - U32 *word = (U32 *)mData; - return (word[0] | word[1] | word[2] | word[3]) > 0; + U32 *word = (U32 *)mData; + return (word[0] | word[1] | word[2] | word[3]) > 0; } */ - BOOL LLUUID::notNull() const +BOOL LLUUID::notNull() const { - U32 *word = (U32 *)mData; - return (word[0] | word[1] | word[2] | word[3]) > 0; + U32* word = (U32*)mData; + return (word[0] | word[1] | word[2] | word[3]) > 0; } // Faster than == LLUUID::null because doesn't require // as much memory access. - BOOL LLUUID::isNull() const +BOOL LLUUID::isNull() const { - U32 *word = (U32 *)mData; - // If all bits are zero, return !0 == TRUE - return !(word[0] | word[1] | word[2] | word[3]); + U32* word = (U32*)mData; + // If all bits are zero, return !0 == TRUE + return !(word[0] | word[1] | word[2] | word[3]); } - LLUUID::LLUUID(const char *in_string) +LLUUID::LLUUID(const char* in_string) { - if (!in_string || in_string[0] == 0) - { - setNull(); - return; - } - - set(in_string); + if (!in_string || in_string[0] == 0) + { + setNull(); + return; + } + + set(in_string); } - LLUUID::LLUUID(const std::string& in_string) +LLUUID::LLUUID(const std::string& in_string) { - if (in_string.empty()) - { - setNull(); - return; - } + if (in_string.empty()) + { + setNull(); + return; + } - set(in_string); + set(in_string); } // IW: DON'T "optimize" these w/ U32s or you'll scoogie the sort order // IW: this will make me very sad - bool LLUUID::operator<(const LLUUID &rhs) const +bool LLUUID::operator<(const LLUUID& rhs) const { - U32 i; - for( i = 0; i < (UUID_BYTES - 1); i++ ) - { - if( mData[i] != rhs.mData[i] ) - { - return (mData[i] < rhs.mData[i]); - } - } - return (mData[UUID_BYTES - 1] < rhs.mData[UUID_BYTES - 1]); + U32 i; + for (i = 0; i < (UUID_BYTES - 1); i++) + { + if (mData[i] != rhs.mData[i]) + { + return (mData[i] < rhs.mData[i]); + } + } + return (mData[UUID_BYTES - 1] < rhs.mData[UUID_BYTES - 1]); } - bool LLUUID::operator>(const LLUUID &rhs) const +bool LLUUID::operator>(const LLUUID& rhs) const { - U32 i; - for( i = 0; i < (UUID_BYTES - 1); i++ ) - { - if( mData[i] != rhs.mData[i] ) - { - return (mData[i] > rhs.mData[i]); - } - } - return (mData[UUID_BYTES - 1] > rhs.mData[UUID_BYTES - 1]); + U32 i; + for (i = 0; i < (UUID_BYTES - 1); i++) + { + if (mData[i] != rhs.mData[i]) + { + return (mData[i] > rhs.mData[i]); + } + } + return (mData[UUID_BYTES - 1] > rhs.mData[UUID_BYTES - 1]); } - U16 LLUUID::getCRC16() const +U16 LLUUID::getCRC16() const { - // A UUID is 16 bytes, or 8 shorts. - U16 *short_data = (U16*)mData; - U16 out = 0; - out += short_data[0]; - out += short_data[1]; - out += short_data[2]; - out += short_data[3]; - out += short_data[4]; - out += short_data[5]; - out += short_data[6]; - out += short_data[7]; - return out; + // A UUID is 16 bytes, or 8 shorts. + U16* short_data = (U16*)mData; + U16 out = 0; + out += short_data[0]; + out += short_data[1]; + out += short_data[2]; + out += short_data[3]; + out += short_data[4]; + out += short_data[5]; + out += short_data[6]; + out += short_data[7]; + return out; } - U32 LLUUID::getCRC32() const +U32 LLUUID::getCRC32() const { - U32 *tmp = (U32*)mData; - return tmp[0] + tmp[1] + tmp[2] + tmp[3]; + U32* tmp = (U32*)mData; + return tmp[0] + tmp[1] + tmp[2] + tmp[3]; } -- cgit v1.2.3 From 8564da58315d015bc2cd60e5f15395e4173d9adc Mon Sep 17 00:00:00 2001 From: Brad Linden Date: Mon, 6 Mar 2023 11:31:29 -0800 Subject: Improved detail for llvertexbuffer attribute mask assertion failure in DRTVWR-559 --- indra/llcommon/llerror.h | 2 ++ 1 file changed, 2 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llerror.h b/indra/llcommon/llerror.h index d06c0e2132..b7dec3cb7f 100644 --- a/indra/llcommon/llerror.h +++ b/indra/llcommon/llerror.h @@ -82,9 +82,11 @@ const int LL_ERR_NOERR = 0; #ifdef SHOW_ASSERT #define llassert(func) llassert_always_msg(func, #func) +#define llassert_msg(func, msg) llassert_always_msg(func, msg) #define llverify(func) llassert_always_msg(func, #func) #else #define llassert(func) +#define llassert_msg(func, msg) #define llverify(func) do {if (func) {}} while(0) #endif -- cgit v1.2.3 From 25ede8638209fac8dde5b71bece4bc1dfa30ea16 Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Fri, 10 Mar 2023 10:52:35 -0600 Subject: SL-19172 Texture streaming tune up. Incidental decruft. --- indra/llcommon/llcommon.cpp | 29 +++++++++++++++++++++++++---- indra/llcommon/llprofiler.h | 6 +----- indra/llcommon/llprofilercategories.h | 2 +- 3 files changed, 27 insertions(+), 10 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llcommon.cpp b/indra/llcommon/llcommon.cpp index d2c4e66160..6e988260a9 100644 --- a/indra/llcommon/llcommon.cpp +++ b/indra/llcommon/llcommon.cpp @@ -37,12 +37,13 @@ thread_local bool gProfilerEnabled = false; #if (TRACY_ENABLE) // Override new/delete for tracy memory profiling -void *operator new(size_t size) + +void* ll_tracy_new(size_t size) { void* ptr; if (gProfilerEnabled) { - LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; + //LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; ptr = (malloc)(size); } else @@ -57,12 +58,22 @@ void *operator new(size_t size) return ptr; } -void operator delete(void *ptr) noexcept +void* operator new(size_t size) +{ + return ll_tracy_new(size); +} + +void* operator new[](std::size_t count) +{ + return ll_tracy_new(count); +} + +void ll_tracy_delete(void* ptr) { TracyFree(ptr); if (gProfilerEnabled) { - LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; + //LL_PROFILE_ZONE_SCOPED_CATEGORY_MEMORY; (free)(ptr); } else @@ -71,6 +82,16 @@ void operator delete(void *ptr) noexcept } } +void operator delete(void *ptr) noexcept +{ + ll_tracy_delete(ptr); +} + +void operator delete[](void* ptr) noexcept +{ + ll_tracy_delete(ptr); +} + // C-style malloc/free can't be so easily overridden, so we define tracy versions and use // a pre-processor #define in linden_common.h to redirect to them. The parens around the native // functions below prevents recursive substitution by the preprocessor. diff --git a/indra/llcommon/llprofiler.h b/indra/llcommon/llprofiler.h index 394fbd05f6..736a069f49 100644 --- a/indra/llcommon/llprofiler.h +++ b/indra/llcommon/llprofiler.h @@ -138,10 +138,6 @@ extern thread_local bool gProfilerEnabled; #define LL_PROFILE_ZONE_ERR(name) LL_PROFILE_ZONE_NAMED_COLOR( name, 0XFF0000 ) // RGB yellow #define LL_PROFILE_ZONE_INFO(name) LL_PROFILE_ZONE_NAMED_COLOR( name, 0X00FFFF ) // RGB cyan #define LL_PROFILE_ZONE_WARN(name) LL_PROFILE_ZONE_NAMED_COLOR( name, 0x0FFFF00 ) // RGB red - //#define LL_PROFILE_ALLOC(ptr, size) TracyAlloc(ptr, size) // memory allocation tracking currently not working - //#define LL_PROFILE_FREE(ptr) TracyFree(ptr) - #define LL_PROFILE_ALLOC(ptr, size) (void)(ptr); (void)(size); - #define LL_PROFILE_FREE(ptr) (void)(ptr); #endif #else #define LL_PROFILER_FRAME_END @@ -165,7 +161,7 @@ extern thread_local bool gProfilerEnabled; #define LL_LABEL_OBJECT_GL(type, name, length, label) -#if LL_PROFILER_CONFIG > 1 +#if LL_PROFILER_CONFIGURATION > 1 #define LL_PROFILE_ALLOC(ptr, size) TracyAlloc(ptr, size) #define LL_PROFILE_FREE(ptr) TracyFree(ptr) #else diff --git a/indra/llcommon/llprofilercategories.h b/indra/llcommon/llprofilercategories.h index 8db29468cc..617431f629 100644 --- a/indra/llcommon/llprofilercategories.h +++ b/indra/llcommon/llprofilercategories.h @@ -52,7 +52,7 @@ #define LL_PROFILER_CATEGORY_ENABLE_LOGGING 1 #define LL_PROFILER_CATEGORY_ENABLE_MATERIAL 1 #define LL_PROFILER_CATEGORY_ENABLE_MEDIA 1 -#define LL_PROFILER_CATEGORY_ENABLE_MEMORY 1 +#define LL_PROFILER_CATEGORY_ENABLE_MEMORY 0 #define LL_PROFILER_CATEGORY_ENABLE_NETWORK 1 #define LL_PROFILER_CATEGORY_ENABLE_OCTREE 1 #define LL_PROFILER_CATEGORY_ENABLE_PIPELINE 1 -- cgit v1.2.3 From e09475713b7abe6fcb916f4a770081a1696b57ab Mon Sep 17 00:00:00 2001 From: RunitaiLinden Date: Tue, 2 May 2023 18:47:21 -0500 Subject: DRTVWR-559 Optimization pass, make it so profileAvatar can read back GPU timer without a frame stall. --- indra/llcommon/llerror.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp index 56fb7c21ca..a7794cc045 100644 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -1611,6 +1611,7 @@ namespace LLError bool debugLoggingEnabled(const std::string& tag) { + LL_PROFILE_ZONE_SCOPED_CATEGORY_APP; LLMutexTrylock lock(getMutex(), 5); if (!lock.isLocked()) { -- cgit v1.2.3 From 2a10bd406c9155d30a82657ce2ee532b3a677d83 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 3 May 2023 09:55:31 -0400 Subject: DRTVWR-559: Replace debugLoggingEnabled() function with LL_DEBUGS(). The trouble with debugLoggingEnabled() is that it locked mutexes and searched maps every time that call was reached. LL_DEBUGS() has the same functionality (albeit with idiosyncratic syntax) but performs expensive lookups only once per session, caching the result in a local static variable. --- indra/llcommon/llcallstack.h | 12 ++++++------ indra/llcommon/llerror.cpp | 14 -------------- indra/llcommon/llerror.h | 24 +++++++++++++++++++++++- 3 files changed, 29 insertions(+), 21 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llcallstack.h b/indra/llcommon/llcallstack.h index 5acf04a49f..d5a2b7b157 100644 --- a/indra/llcommon/llcallstack.h +++ b/indra/llcommon/llcallstack.h @@ -79,9 +79,9 @@ struct LLContextStatus LL_COMMON_API std::ostream& operator<<(std::ostream& s, const LLContextStatus& context_status); -#define dumpStack(tag) \ - if (debugLoggingEnabled(tag)) \ - { \ - LLCallStack cs; \ - LL_DEBUGS(tag) << "STACK:\n" << "====================\n" << cs << "====================" << LL_ENDL; \ - } +#define dumpStack(tag) \ + LL_DEBUGS(tag) << "STACK:\n" \ + << "====================\n" \ + << LLCallStack() \ + << "====================" \ + << LL_ENDL; diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp index 56fb7c21ca..5aa8558878 100644 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -1609,19 +1609,5 @@ namespace LLError } } -bool debugLoggingEnabled(const std::string& tag) -{ - LLMutexTrylock lock(getMutex(), 5); - if (!lock.isLocked()) - { - return false; - } - - SettingsConfigPtr s = Globals::getInstance()->getSettingsConfig(); - LLError::ELevel level = LLError::LEVEL_DEBUG; - bool res = checkLevelMap(s->mTagLevelMap, tag, level); - return res; -} - diff --git a/indra/llcommon/llerror.h b/indra/llcommon/llerror.h index b7dec3cb7f..08eb323c4a 100644 --- a/indra/llcommon/llerror.h +++ b/indra/llcommon/llerror.h @@ -467,7 +467,29 @@ typedef LLError::NoClassInfo _LL_CLASS_TO_LOG; LLError::CallSite& _site(_sites[which]); \ lllog_test_() -// Check at run-time whether logging is enabled, without generating output +/* +// Check at run-time whether logging is enabled, without generating output. +Resist the temptation to add a function like this because it incurs the +expense of locking and map-searching every time control reaches it. bool debugLoggingEnabled(const std::string& tag); +Instead of: + +if debugLoggingEnabled("SomeTag") +{ + // ... presumably expensive operation ... + LL_DEBUGS("SomeTag") << ... << LL_ENDL; +} + +Use this: + +LL_DEBUGS("SomeTag"); +// ... presumably expensive operation ... +LL_CONT << ...; +LL_ENDL; + +LL_DEBUGS("SomeTag") performs the locking and map-searching ONCE, then caches +the result in a static variable. +*/ + #endif // LL_LLERROR_H -- cgit v1.2.3 From 1cadfd44fe5ddafaf3ba3560bd8ec8e1351b347d Mon Sep 17 00:00:00 2001 From: RunitaiLinden Date: Wed, 3 May 2023 17:30:02 -0500 Subject: DRTVWR-559 WIP - optimize ARC calculation et al. --- indra/llcommon/llmutex.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llmutex.h b/indra/llcommon/llmutex.h index 838d7d34c0..0d70da6178 100644 --- a/indra/llcommon/llmutex.h +++ b/indra/llcommon/llmutex.h @@ -36,7 +36,8 @@ //============================================================================ -#define MUTEX_DEBUG (LL_DEBUG || LL_RELEASE_WITH_DEBUG_INFO) +//#define MUTEX_DEBUG (LL_DEBUG || LL_RELEASE_WITH_DEBUG_INFO) +#define MUTEX_DEBUG 0 //disable mutex debugging as it's interfering with profiles #if MUTEX_DEBUG #include @@ -61,7 +62,7 @@ protected: mutable LLThread::id_t mLockingThread; #if MUTEX_DEBUG - std::map mIsLocked; + std::unordered_map mIsLocked; #endif }; -- cgit v1.2.3 From 855cae27ccde4896509e3e22c68c441d6404ccfb Mon Sep 17 00:00:00 2001 From: Rye Mutt Date: Fri, 5 May 2023 17:23:29 -0400 Subject: Fix LLThreadSafeQueueInterrupt in WorkQueueBase::postTo during shutdown by catching and returning false --- indra/llcommon/workqueue.h | 68 ++++++++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 30 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/workqueue.h b/indra/llcommon/workqueue.h index 5461ce6c23..5b704e7198 100644 --- a/indra/llcommon/workqueue.h +++ b/indra/llcommon/workqueue.h @@ -516,37 +516,45 @@ namespace LL // Here we believe target WorkQueue still exists. Post to it a // lambda that packages our callable, our callback and a weak_ptr // to this originating WorkQueue. - tptr->post( - [reply = super::getWeak(), - callable = std::move(callable), - callback = std::move(callback)] - () mutable - { - // Use postMaybe() below in case this originating WorkQueue - // has been closed or destroyed. Remember, the outer lambda is - // now running on a thread servicing the target WorkQueue, and - // real time has elapsed since postTo()'s tptr->post() call. - try - { - // Make a reply lambda to repost to THIS WorkQueue. - // Delegate to makeReplyLambda() so we can partially - // specialize on void return. - postMaybe(reply, makeReplyLambda(std::move(callable), std::move(callback))); - } - catch (...) + try + { + tptr->post( + [reply = super::getWeak(), + callable = std::move(callable), + callback = std::move(callback)] + () mutable { - // Either variant of makeReplyLambda() is responsible for - // calling the caller's callable. If that throws, return - // the exception to the originating thread. - postMaybe( - reply, - // Bind the current exception to transport back to the - // originating WorkQueue. Once there, rethrow it. - [exc = std::current_exception()](){ std::rethrow_exception(exc); }); - } - }, - // if caller passed a TimePoint, pass it along to post() - std::forward(args)...); + // Use postMaybe() below in case this originating WorkQueue + // has been closed or destroyed. Remember, the outer lambda is + // now running on a thread servicing the target WorkQueue, and + // real time has elapsed since postTo()'s tptr->post() call. + try + { + // Make a reply lambda to repost to THIS WorkQueue. + // Delegate to makeReplyLambda() so we can partially + // specialize on void return. + postMaybe(reply, makeReplyLambda(std::move(callable), std::move(callback))); + } + catch (...) + { + // Either variant of makeReplyLambda() is responsible for + // calling the caller's callable. If that throws, return + // the exception to the originating thread. + postMaybe( + reply, + // Bind the current exception to transport back to the + // originating WorkQueue. Once there, rethrow it. + [exc = std::current_exception()](){ std::rethrow_exception(exc); }); + } + }, + // if caller passed a TimePoint, pass it along to post() + std::forward(args)...); + } + catch (const Closed&) + { + // target WorkQueue still exists, but is Closed + return false; + } // looks like we were able to post() return true; -- cgit v1.2.3 From 026ef1935dbdb21ab79159d38fb78e126dd6ac95 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 8 May 2023 12:07:31 -0400 Subject: SL-19690: Follow up on Rye Mutt's fix for shutdown crashes. Rather than continuing to propagate try/catch (Closed) (aka LLThreadSafeQueueInterrupt) constructs through the code base, make WorkQueueBase::post() return bool indicating success (i.e. ! isClosed()). This obviates postIfOpen(), which no one was using anyway. In effect, postIfOpen() is renamed post(), bypassing the exception when isClosed(). Review existing try/catch blocks of that sort, changing to test for post() returning false. --- indra/llcommon/llqueuedthread.cpp | 2 +- indra/llcommon/workqueue.cpp | 21 +----- indra/llcommon/workqueue.h | 135 +++++++++++++++----------------------- 3 files changed, 57 insertions(+), 101 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llqueuedthread.cpp b/indra/llcommon/llqueuedthread.cpp index 9b1de2e9a5..7da7c1e026 100644 --- a/indra/llcommon/llqueuedthread.cpp +++ b/indra/llcommon/llqueuedthread.cpp @@ -146,7 +146,7 @@ S32 LLQueuedThread::updateQueue(F32 max_time_ms) // schedule a call to threadedUpdate for every call to updateQueue if (!isQuitting()) { - mRequestQueue.postIfOpen([=]() + mRequestQueue.post([=]() { LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("qt - update"); mIdleThread = FALSE; diff --git a/indra/llcommon/workqueue.cpp b/indra/llcommon/workqueue.cpp index 83e0216ae7..5a77d517dd 100644 --- a/indra/llcommon/workqueue.cpp +++ b/indra/llcommon/workqueue.cpp @@ -161,12 +161,7 @@ bool LL::WorkQueue::done() return mQueue.done(); } -void LL::WorkQueue::post(const Work& callable) -{ - mQueue.push(callable); -} - -bool LL::WorkQueue::postIfOpen(const Work& callable) +bool LL::WorkQueue::post(const Work& callable) { return mQueue.pushIfOpen(callable); } @@ -215,26 +210,16 @@ bool LL::WorkSchedule::done() return mQueue.done(); } -void LL::WorkSchedule::post(const Work& callable) +bool LL::WorkSchedule::post(const Work& callable) { // Use TimePoint::clock::now() instead of TimePoint's representation of // the epoch because this WorkSchedule may contain a mix of past-due // TimedWork items and TimedWork items scheduled for the future. Sift this // new item into the correct place. - post(callable, TimePoint::clock::now()); -} - -void LL::WorkSchedule::post(const Work& callable, const TimePoint& time) -{ - mQueue.push(TimedWork(time, callable)); -} - -bool LL::WorkSchedule::postIfOpen(const Work& callable) -{ return postIfOpen(callable, TimePoint::clock::now()); } -bool LL::WorkSchedule::postIfOpen(const Work& callable, const TimePoint& time) +bool LL::WorkSchedule::post(const Work& callable, const TimePoint& time) { return mQueue.pushIfOpen(TimedWork(time, callable)); } diff --git a/indra/llcommon/workqueue.h b/indra/llcommon/workqueue.h index 5b704e7198..87fdd1517f 100644 --- a/indra/llcommon/workqueue.h +++ b/indra/llcommon/workqueue.h @@ -83,13 +83,10 @@ namespace LL /*---------------------- fire and forget API -----------------------*/ - /// fire-and-forget - virtual void post(const Work&) = 0; - /** * post work, unless the queue is closed before we can post */ - virtual bool postIfOpen(const Work&) = 0; + virtual bool post(const Work&) = 0; /** * post work, unless the queue is full @@ -247,13 +244,10 @@ namespace LL /*---------------------- fire and forget API -----------------------*/ - /// fire-and-forget - void post(const Work&) override; - /** * post work, unless the queue is closed before we can post */ - bool postIfOpen(const Work&) override; + bool post(const Work&) override; /** * post work, unless the queue is full @@ -320,22 +314,16 @@ namespace LL /*---------------------- fire and forget API -----------------------*/ - /// fire-and-forget - void post(const Work& callable) override; - - /// fire-and-forget, but at a particular (future?) time - void post(const Work& callable, const TimePoint& time); - /** * post work, unless the queue is closed before we can post */ - bool postIfOpen(const Work& callable) override; + bool post(const Work& callable) override; /** * post work for a particular time, unless the queue is closed before * we can post */ - bool postIfOpen(const Work& callable, const TimePoint& time); + bool post(const Work& callable, const TimePoint& time); /** * post work, unless the queue is full @@ -356,7 +344,7 @@ namespace LL * an LLCond variant, e.g. LLOneShotCond or LLBoolCond. */ template - void postEvery(const std::chrono::duration& interval, + bool postEvery(const std::chrono::duration& interval, CALLABLE&& callable); private: @@ -417,15 +405,10 @@ namespace LL // move-only callable; but naturally this statement must be // the last time we reference this instance, which may become // moved-from. - try - { - auto target{ std::dynamic_pointer_cast(mTarget.lock()) }; - target->post(std::move(*this), mStart); - } - catch (const Closed&) - { - // Once this queue is closed, oh well, just stop - } + auto target{ std::dynamic_pointer_cast(mTarget.lock()) }; + // Discard bool return: once this queue is closed, oh well, + // just stop + target->post(std::move(*this), mStart); } } @@ -437,7 +420,7 @@ namespace LL }; template - void WorkSchedule::postEvery(const std::chrono::duration& interval, + bool WorkSchedule::postEvery(const std::chrono::duration& interval, CALLABLE&& callable) { if (interval.count() <= 0) @@ -454,7 +437,7 @@ namespace LL // Instantiate and post a suitable BackJack, binding a weak_ptr to // self, the current time, the desired interval and the desired // callable. - post( + return post( BackJack( getWeak(), TimePoint::clock::now(), interval, std::move(callable))); } @@ -516,48 +499,37 @@ namespace LL // Here we believe target WorkQueue still exists. Post to it a // lambda that packages our callable, our callback and a weak_ptr // to this originating WorkQueue. - try - { - tptr->post( - [reply = super::getWeak(), - callable = std::move(callable), - callback = std::move(callback)] - () mutable + return tptr->post( + [reply = super::getWeak(), + callable = std::move(callable), + callback = std::move(callback)] + () mutable + { + // Use postMaybe() below in case this originating WorkQueue + // has been closed or destroyed. Remember, the outer lambda is + // now running on a thread servicing the target WorkQueue, and + // real time has elapsed since postTo()'s tptr->post() call. + try { - // Use postMaybe() below in case this originating WorkQueue - // has been closed or destroyed. Remember, the outer lambda is - // now running on a thread servicing the target WorkQueue, and - // real time has elapsed since postTo()'s tptr->post() call. - try - { - // Make a reply lambda to repost to THIS WorkQueue. - // Delegate to makeReplyLambda() so we can partially - // specialize on void return. - postMaybe(reply, makeReplyLambda(std::move(callable), std::move(callback))); - } - catch (...) - { - // Either variant of makeReplyLambda() is responsible for - // calling the caller's callable. If that throws, return - // the exception to the originating thread. - postMaybe( - reply, - // Bind the current exception to transport back to the - // originating WorkQueue. Once there, rethrow it. - [exc = std::current_exception()](){ std::rethrow_exception(exc); }); - } - }, - // if caller passed a TimePoint, pass it along to post() - std::forward(args)...); - } - catch (const Closed&) - { - // target WorkQueue still exists, but is Closed - return false; - } - - // looks like we were able to post() - return true; + // Make a reply lambda to repost to THIS WorkQueue. + // Delegate to makeReplyLambda() so we can partially + // specialize on void return. + postMaybe(reply, makeReplyLambda(std::move(callable), std::move(callback))); + } + catch (...) + { + // Either variant of makeReplyLambda() is responsible for + // calling the caller's callable. If that throws, return + // the exception to the originating thread. + postMaybe( + reply, + // Bind the current exception to transport back to the + // originating WorkQueue. Once there, rethrow it. + [exc = std::current_exception()](){ std::rethrow_exception(exc); }); + } + }, + // if caller passed a TimePoint, pass it along to post() + std::forward(args)...); } template @@ -568,18 +540,9 @@ namespace LL auto tptr = target.lock(); if (tptr) { - try - { - tptr->post(std::forward(args)...); - // we were able to post() - return true; - } - catch (const Closed&) - { - // target WorkQueue still exists, but is Closed - } + return tptr->post(std::forward(args)...); } - // either target no longer exists, or its WorkQueue is Closed + // target no longer exists return false; } @@ -591,7 +554,7 @@ namespace LL auto operator()(WorkQueueBase* self, CALLABLE&& callable, ARGS&&... args) { LLCoros::Promise promise; - self->post( + bool posted = self->post( // We dare to bind a reference to Promise because it's // specifically designed for cross-thread communication. [&promise, callable = std::move(callable)]() @@ -608,6 +571,10 @@ namespace LL }, // if caller passed a TimePoint, pass it to post() std::forward(args)...); + if (! posted) + { + LLTHROW(Closed); + } auto future{ LLCoros::getFuture(promise) }; // now, on the calling thread, wait for that result LLCoros::TempStatus st("waiting for WorkQueue::waitForResult()"); @@ -623,7 +590,7 @@ namespace LL void operator()(WorkQueueBase* self, CALLABLE&& callable, ARGS&&... args) { LLCoros::Promise promise; - self->post( + bool posted = self->post( // &promise is designed for cross-thread access [&promise, callable = std::move(callable)]() mutable { @@ -639,6 +606,10 @@ namespace LL }, // if caller passed a TimePoint, pass it to post() std::forward(args)...); + if (! posted) + { + LLTHROW(Closed); + } auto future{ LLCoros::getFuture(promise) }; // block until set_value() LLCoros::TempStatus st("waiting for void WorkQueue::waitForResult()"); -- cgit v1.2.3 From d75ecde69c1289f4e3df8b75e9a74c5b05db318c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 8 May 2023 12:31:57 -0400 Subject: SL-19690: Properly qualify exception type. --- indra/llcommon/workqueue.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/workqueue.h b/indra/llcommon/workqueue.h index 87fdd1517f..ec0700a718 100644 --- a/indra/llcommon/workqueue.h +++ b/indra/llcommon/workqueue.h @@ -573,7 +573,7 @@ namespace LL std::forward(args)...); if (! posted) { - LLTHROW(Closed); + LLTHROW(WorkQueueBase::Closed()); } auto future{ LLCoros::getFuture(promise) }; // now, on the calling thread, wait for that result @@ -608,7 +608,7 @@ namespace LL std::forward(args)...); if (! posted) { - LLTHROW(Closed); + LLTHROW(WorkQueueBase::Closed()); } auto future{ LLCoros::getFuture(promise) }; // block until set_value() -- cgit v1.2.3 From f728808d938666a01f73a039c861358fc4b02a9e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 8 May 2023 12:45:27 -0400 Subject: SL-19690: Fix a lingering reference to WorkSchedule::postIfOpen() --- indra/llcommon/workqueue.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/workqueue.cpp b/indra/llcommon/workqueue.cpp index 5a77d517dd..cf80ce0656 100644 --- a/indra/llcommon/workqueue.cpp +++ b/indra/llcommon/workqueue.cpp @@ -216,7 +216,7 @@ bool LL::WorkSchedule::post(const Work& callable) // the epoch because this WorkSchedule may contain a mix of past-due // TimedWork items and TimedWork items scheduled for the future. Sift this // new item into the correct place. - return postIfOpen(callable, TimePoint::clock::now()); + return post(callable, TimePoint::clock::now()); } bool LL::WorkSchedule::post(const Work& callable, const TimePoint& time) -- cgit v1.2.3 From 324f0d9b8abad3a74a7c19a6e28f8c77c76b3b83 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 22 Aug 2022 21:00:42 -0400 Subject: DRTVWR-558: Fix builds on macOS 12.5 Monterey. Always search for python3[.exe] instead of plain 'python'. macOS Monterey no longer bundles Python 2 at all. Explicitly make PYTHON_EXECUTABLE a cached value so if the user edits it in CMakeCache.txt, it won't be overwritten by indra/cmake/Python.cmake. Do NOT set DYLD_LIBRARY_PATH for test executables! That has Bad Effects, as discussed in https://stackoverflow.com/q/73418423/5533635. Instead, create symlinks from build-mumble/sharedlibs/Resources -> Release/Resources and from build-mumble/test/Resources -> ../sharedlibs/Release/Resources. For test executables in sharedlibs/RelWithDebInfo and test/RelWithDebInfo, this supports our dylibs' baked-in load path @executable_path/../Resources. That load path assumes running in a standard app bundle (which the viewer in fact does), but we've been avoiding creating an app bundle for every test program. These symlinks allow us to continue doing that while avoiding DYLD_LIBRARY_PATH. Add indra/llcommon/apply.h. The LL::apply() function and its wrapper macro VAPPLY were very useful in diagnosing the problem. Tweak llleap_test.cpp. This source was modified extensively for diagnostic purposes; these are the small improvements that remain. (cherry picked from commit 15d37713b9113a6f70dde48c764df02c76e18cbc) (cherry picked from commit a1adcf1905d1fbc5fe07ff5a627295ccfe461ac4) --- indra/llcommon/tests/llleap_test.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index 7ee36a9ea6..168825a8cd 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -17,6 +17,7 @@ // std headers #include // external library headers +//#include #include #include // other Linden headers -- cgit v1.2.3 From c682603417e1ef8290aacf274ff49821bd204c0b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 19 Dec 2022 16:29:06 -0500 Subject: DRTVWR-558: Extend LL::apply() to LLSD array arguments. Make apply(function, std::array) and apply(function, std::vector) available even when we borrow the C++17 implementation of apply(function, std::tuple). Add apply(function, LLSD) with interpretations: * isUndefined() is treated as an empty array, for calling a nullary function * scalar LLSD is treated as a single-entry array, for calling a unary function * isArray() converts function parameters using LLSDParam * isMap() is an error. Add unit tests for all flavors of LL::apply(). (cherry picked from commit 3006c24251c6259d00df9e0f4f66b8a617e6026d) --- indra/llcommon/CMakeLists.txt | 2 + indra/llcommon/apply.h | 23 +++-- indra/llcommon/llsdutil.h | 81 ++++++++++++++++- indra/llcommon/tests/apply_test.cpp | 171 ++++++++++++++++++++++++++++++++++++ 4 files changed, 269 insertions(+), 8 deletions(-) create mode 100644 indra/llcommon/tests/apply_test.cpp (limited to 'indra/llcommon') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 91ae2440fa..941d2d7baf 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -293,6 +293,8 @@ if (LL_TESTS) #set(TEST_DEBUG on) set(test_libs llcommon) + LL_ADD_INTEGRATION_TEST(apply "" "${test_libs}") + LL_ADD_INTEGRATION_TEST(bitpack "" "${test_libs}") LL_ADD_INTEGRATION_TEST(classic_callback "" "${test_libs}") LL_ADD_INTEGRATION_TEST(commonmisc "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lazyeventapi "" "${test_libs}") diff --git a/indra/llcommon/apply.h b/indra/llcommon/apply.h index 7c58d63bc0..26753f5017 100644 --- a/indra/llcommon/apply.h +++ b/indra/llcommon/apply.h @@ -13,6 +13,7 @@ #define LL_APPLY_H #include +#include #include namespace LL @@ -54,6 +55,9 @@ namespace LL }, \ (ARGS)) +/***************************************************************************** +* apply(function, tuple) +*****************************************************************************/ #if __cplusplus >= 201703L // C++17 implementation @@ -63,8 +67,8 @@ using std::apply; // Derived from https://stackoverflow.com/a/20441189 // and https://en.cppreference.com/w/cpp/utility/apply -template -auto apply_impl(CALLABLE&& func, TUPLE&& args, std::index_sequence) +template +auto apply_impl(CALLABLE&& func, const std::tuple& args, std::index_sequence) { // call func(unpacked args) return std::forward(func)(std::move(std::get(args))...); @@ -81,6 +85,11 @@ auto apply(CALLABLE&& func, const std::tuple& args) std::index_sequence_for{}); } +#endif // C++14 + +/***************************************************************************** +* apply(function, std::array) +*****************************************************************************/ // per https://stackoverflow.com/a/57510428/5533635 template auto apply(CALLABLE&& func, const std::array& args) @@ -88,13 +97,15 @@ auto apply(CALLABLE&& func, const std::array& args) return apply(std::forward(func), std::tuple_cat(args)); } +/***************************************************************************** +* apply(function, std::vector) +*****************************************************************************/ // per https://stackoverflow.com/a/28411055/5533635 template auto apply_impl(CALLABLE&& func, const std::vector& args, std::index_sequence) { - return apply_impl(std::forward(func), - std::make_tuple(std::forward(args[I])...), - I...); + return apply(std::forward(func), + std::make_tuple(args[I]...)); } // this goes beyond C++17 std::apply() @@ -108,8 +119,6 @@ auto apply(CALLABLE&& func, const std::vector& args) std::make_index_sequence()); } -#endif // C++14 - } // namespace LL #endif /* ! defined(LL_APPLY_H) */ diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h index 372278c51a..eaf8825791 100644 --- a/indra/llcommon/llsdutil.h +++ b/indra/llcommon/llsdutil.h @@ -31,6 +31,7 @@ #include "llsd.h" #include +#include // U32 LL_COMMON_API LLSD ll_sd_from_U32(const U32); @@ -332,6 +333,31 @@ private: T _value; }; +/** + * LLSDParam is for when you don't already have the target parameter + * type in hand. Instantiate LLSDParam(your LLSD object), and the + * templated conversion operator will try to select a more specific LLSDParam + * specialization. + */ +template <> +class LLSDParam +{ +private: + LLSD value_; + +public: + LLSDParam(const LLSD& value): value_(value) {} + + /// if we're literally being asked for an LLSD parameter, avoid infinite + /// recursion + operator LLSD() const { return value_; } + + /// otherwise, instantiate a more specific LLSDParam to convert; that + /// preserves the existing customization mechanism + template + operator T() const { return LLSDParam(value_); } +}; + /** * Turns out that several target types could accept an LLSD param using any of * a few different conversions, e.g. LLUUID's constructor can accept LLUUID or @@ -350,7 +376,7 @@ class LLSDParam \ { \ public: \ LLSDParam(const LLSD& value): \ - _value((T)value.AS()) \ + _value((T)value.AS()) \ {} \ \ operator T() const { return _value; } \ @@ -555,4 +581,57 @@ struct hash } }; } + +namespace LL +{ + +/***************************************************************************** +* apply(function, LLSD array) +*****************************************************************************/ +// Derived from https://stackoverflow.com/a/20441189 +// and https://en.cppreference.com/w/cpp/utility/apply . +// We can't simply make a tuple from the LLSD array and then apply() that +// tuple to the function -- how would make_tuple() deduce the correct +// parameter type for each entry? We must go directly to the target function. +template +auto apply_impl(CALLABLE&& func, const LLSD& array, std::index_sequence) +{ + // call func(unpacked args), using generic LLSDParam to convert each + // entry in 'array' to the target parameter type + return std::forward(func)(LLSDParam(array[I])...); +} + +template +auto apply(CALLABLE&& func, const LLSD& args) +{ + LLSD array; + constexpr auto arity = boost::function_traits::arity; + // LLSD supports a number of types, two of which are aggregates: Map and + // Array. We don't try to support Map: supporting Map would seem to + // promise that we could somehow match the string key to 'func's parameter + // names. Uh sorry, maybe in some future version of C++ with reflection. + assert(! args.isMap()); + // We expect an LLSD array, but what the heck, treat isUndefined() as a + // zero-length array for calling a nullary 'func'. + if (args.isUndefined() || args.isArray()) + { + // this works because LLSD().size() == 0 + assert(args.size() == arity); + array = args; + } + else // args is one of the scalar types + { + // scalar_LLSD.size() == 0, so don't test that here. + // You can pass a scalar LLSD only to a unary 'func'. + assert(arity == 1); + // make an array of it + array = llsd::array(args); + } + return apply_impl(std::forward(func), + array, + std::make_index_sequence()); +} + +} // namespace LL + #endif // LL_LLSDUTIL_H diff --git a/indra/llcommon/tests/apply_test.cpp b/indra/llcommon/tests/apply_test.cpp new file mode 100644 index 0000000000..1f1085a702 --- /dev/null +++ b/indra/llcommon/tests/apply_test.cpp @@ -0,0 +1,171 @@ +/** + * @file apply_test.cpp + * @author Nat Goodspeed + * @date 2022-12-19 + * @brief Test for apply. + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Copyright (c) 2022, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "apply.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "../test/lltut.h" +#include "llsd.h" +#include "llsdutil.h" + +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + namespace statics + { + /*------------------------------ data ------------------------------*/ + // Although we're using types from the LLSD namespace, we're not + // constructing LLSD values, but rather instances of the C++ types + // supported by LLSD. + static LLSD::Boolean b{true}; + static LLSD::Integer i{17}; + static LLSD::Real f{3.14}; + static LLSD::String s{ "hello" }; + static LLSD::UUID uu{ "baadf00d-dead-beef-baad-feedb0ef" }; + static LLSD::Date dt{ "2022-12-19" }; + static LLSD::URI uri{ "http://secondlife.com" }; + static LLSD::Binary bin{ 0x01, 0x02, 0x03, 0x04, 0x05 }; + + static std::vector quick + { + "The", "quick", "brown", "fox", "etc." + }; + + static std::array fibs + { + 0, 1, 1, 2, 3 + }; + + // ensure that apply() actually reaches the target method -- + // lack of ensure_equals() failure could be due to no-op apply() + bool called{ false }; + + /*------------------------- test functions -------------------------*/ + void various(LLSD::Boolean b, LLSD::Integer i, LLSD::Real f, const LLSD::String& s, + const LLSD::UUID& uu, const LLSD::Date& dt, + const LLSD::URI& uri, const LLSD::Binary& bin) + { + called = true; + ensure_equals( "b mismatch", b, statics::b); + ensure_equals( "i mismatch", i, statics::i); + ensure_equals( "f mismatch", f, statics::f); + ensure_equals( "s mismatch", s, statics::s); + ensure_equals( "uu mismatch", uu, statics::uu); + ensure_equals( "dt mismatch", dt, statics::dt); + ensure_equals("uri mismatch", uri, statics::uri); + ensure_equals("bin mismatch", bin, statics::bin); + } + + void strings(std::string s0, std::string s1, std::string s2, std::string s3, std::string s4) + { + called = true; + ensure_equals("s0 mismatch", s0, statics::quick[0]); + ensure_equals("s1 mismatch", s1, statics::quick[1]); + ensure_equals("s2 mismatch", s2, statics::quick[2]); + ensure_equals("s3 mismatch", s3, statics::quick[3]); + ensure_equals("s4 mismatch", s4, statics::quick[4]); + } + + void ints(int i0, int i1, int i2, int i3, int i4) + { + called = true; + ensure_equals("i0 mismatch", i0, statics::fibs[0]); + ensure_equals("i1 mismatch", i1, statics::fibs[1]); + ensure_equals("i2 mismatch", i2, statics::fibs[2]); + ensure_equals("i3 mismatch", i3, statics::fibs[3]); + ensure_equals("i4 mismatch", i4, statics::fibs[4]); + } + + void intfunc(int i) + { + called = true; + ensure_equals("i mismatch", i, statics::i); + } + + void voidfunc() + { + called = true; + } + } // namespace statics + + struct apply_data + { + apply_data() + { + // reset called before each test + statics::called = false; + } + }; + typedef test_group apply_group; + typedef apply_group::object object; + apply_group applygrp("apply"); + + template<> template<> + void object::test<1>() + { + set_test_name("apply(tuple)"); + LL::apply(statics::various, + std::make_tuple(statics::b, statics::i, statics::f, statics::s, + statics::uu, statics::dt, statics::uri, statics::bin)); + ensure("apply(tuple) failed", statics::called); + } + + template<> template<> + void object::test<2>() + { + set_test_name("apply(array)"); + LL::apply(statics::ints, statics::fibs); + ensure("apply(array) failed", statics::called); + } + + template<> template<> + void object::test<3>() + { + set_test_name("apply(vector)"); + LL::apply(statics::strings, statics::quick); + ensure("apply(vector) failed", statics::called); + } + + // The various apply(LLSD) tests exercise only the success cases because + // the failure cases trigger assert() fail, which is hard to catch. + template<> template<> + void object::test<4>() + { + set_test_name("apply(LLSD())"); + LL::apply(statics::voidfunc, LLSD()); + ensure("apply(LLSD()) failed", statics::called); + } + + template<> template<> + void object::test<5>() + { + set_test_name("apply(LLSD scalar)"); + LL::apply(statics::intfunc, LLSD(statics::i)); + ensure("apply(LLSD scalar) failed", statics::called); + } + + template<> template<> + void object::test<6>() + { + set_test_name("apply(LLSD array)"); + LL::apply(statics::various, + llsd::array(statics::b, statics::i, statics::f, statics::s, + statics::uu, statics::dt, statics::uri, statics::bin)); + ensure("apply(LLSD array) failed", statics::called); + } +} // namespace tut -- cgit v1.2.3 From 196e49c1f8bd58ab9ce81843c10d452284ca7569 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 19 Dec 2022 17:27:36 -0500 Subject: DRTVWR-558: Add unit test for VAPPLY(). Add to apply_test.cpp a collect() function that incrementally accumulates an arbitrary number of arguments into a std::vector. Construct a std::array to pass it, using VAPPLY(). Clarify in header comments that LL::apply() can't call a variadic function with arguments of dynamic size: std::vector or LLSD. The compiler can deduce how many arguments to pass to a function with a fixed argument list; it can deduce how many arguments to pass to a variadic function with a fixed number of arguments. But it can't compile a call to a variadic function with an arguments data structure whose size can vary at runtime. (cherry picked from commit ceed33396266b123896f7cfb9b90abdf240e1eec) --- indra/llcommon/apply.h | 6 ++++- indra/llcommon/llsdutil.h | 6 +++++ indra/llcommon/tests/apply_test.cpp | 52 ++++++++++++++++++++++++++++++++++++- 3 files changed, 62 insertions(+), 2 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/apply.h b/indra/llcommon/apply.h index 26753f5017..9f4c268895 100644 --- a/indra/llcommon/apply.h +++ b/indra/llcommon/apply.h @@ -108,7 +108,11 @@ auto apply_impl(CALLABLE&& func, const std::vector& args, std::index_sequence std::make_tuple(args[I]...)); } -// this goes beyond C++17 std::apply() +/** + * apply(function, std::vector) goes beyond C++17 std::apply(). For this case + * @a function @emph cannot be variadic: the compiler must know at compile + * time how many arguments to pass. This isn't Python. + */ template auto apply(CALLABLE&& func, const std::vector& args) { diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h index eaf8825791..eddaa64bd2 100644 --- a/indra/llcommon/llsdutil.h +++ b/indra/llcommon/llsdutil.h @@ -31,6 +31,7 @@ #include "llsd.h" #include +#include #include // U32 @@ -601,6 +602,11 @@ auto apply_impl(CALLABLE&& func, const LLSD& array, std::index_sequence) return std::forward(func)(LLSDParam(array[I])...); } +/** + * apply(function, LLSD) goes beyond C++17 std::apply(). For this case + * @a function @emph cannot be variadic: the compiler must know at compile + * time how many arguments to pass. This isn't Python. + */ template auto apply(CALLABLE&& func, const LLSD& args) { diff --git a/indra/llcommon/tests/apply_test.cpp b/indra/llcommon/tests/apply_test.cpp index 1f1085a702..28ee3f9c81 100644 --- a/indra/llcommon/tests/apply_test.cpp +++ b/indra/llcommon/tests/apply_test.cpp @@ -15,12 +15,27 @@ #include "apply.h" // STL headers // std headers +#include // external library headers // other Linden headers -#include "../test/lltut.h" #include "llsd.h" #include "llsdutil.h" +// for ensure_equals +std::ostream& operator<<(std::ostream& out, const std::vector& stringvec) +{ + const char* delim = "["; + for (const auto& str : stringvec) + { + out << delim << std::quoted(str); + delim = ", "; + } + return out << ']'; +} + +// the above must be declared BEFORE ensure_equals(std::vector) +#include "../test/lltut.h" + /***************************************************************************** * TUT *****************************************************************************/ @@ -54,6 +69,8 @@ namespace tut // ensure that apply() actually reaches the target method -- // lack of ensure_equals() failure could be due to no-op apply() bool called{ false }; + // capture calls from collect() + std::vector collected; /*------------------------- test functions -------------------------*/ void various(LLSD::Boolean b, LLSD::Integer i, LLSD::Real f, const LLSD::String& s, @@ -101,6 +118,20 @@ namespace tut { called = true; } + + // recursion tail + void collect() + { + called = true; + } + + // collect(arbitrary) + template + void collect(const std::string& first, ARGS&&... rest) + { + statics::collected.push_back(first); + collect(std::forward(rest)...); + } } // namespace statics struct apply_data @@ -109,6 +140,7 @@ namespace tut { // reset called before each test statics::called = false; + statics::collected.clear(); } }; typedef test_group apply_group; @@ -168,4 +200,22 @@ namespace tut statics::uu, statics::dt, statics::uri, statics::bin)); ensure("apply(LLSD array) failed", statics::called); } + + template<> template<> + void object::test<7>() + { + set_test_name("VAPPLY()"); + // Make a std::array from statics::quick. We can't call a + // variadic function with a data structure of dynamic length. + std::array strray; + for (size_t i = 0; i < strray.size(); ++i) + strray[i] = statics::quick[i]; + // This doesn't work: the compiler doesn't know which overload of + // collect() to pass to LL::apply(). + // LL::apply(statics::collect, strray); + // That's what VAPPLY() is for. + VAPPLY(statics::collect, strray); + ensure("VAPPLY() failed", statics::called); + ensure_equals("collected mismatch", statics::collected, statics::quick); + } } // namespace tut -- cgit v1.2.3 From 8855a82f512286bce6bd131d87dcafd303f2a5f6 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 20 Dec 2022 09:48:36 -0500 Subject: DRTVWR-558: Add LL::apply() test for function(const LLSD&). (cherry picked from commit 7d33e00d925614911a7602da1bd79916cc849ad7) --- indra/llcommon/tests/apply_test.cpp | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/apply_test.cpp b/indra/llcommon/tests/apply_test.cpp index 28ee3f9c81..9a17afc18c 100644 --- a/indra/llcommon/tests/apply_test.cpp +++ b/indra/llcommon/tests/apply_test.cpp @@ -108,6 +108,12 @@ namespace tut ensure_equals("i4 mismatch", i4, statics::fibs[4]); } + void sdfunc(const LLSD& sd) + { + called = true; + ensure_equals("sd mismatch", sd.asInteger(), statics::i); + } + void intfunc(int i) { called = true; @@ -186,13 +192,23 @@ namespace tut template<> template<> void object::test<5>() { - set_test_name("apply(LLSD scalar)"); + set_test_name("apply(fn(int), LLSD scalar)"); LL::apply(statics::intfunc, LLSD(statics::i)); - ensure("apply(LLSD scalar) failed", statics::called); + ensure("apply(fn(int), LLSD scalar) failed", statics::called); } template<> template<> void object::test<6>() + { + set_test_name("apply(fn(LLSD), LLSD scalar)"); + // This test verifies that LLSDParam doesn't send the compiler + // into infinite recursion when the target is itself LLSD. + LL::apply(statics::sdfunc, LLSD(statics::i)); + ensure("apply(fn(LLSD), LLSD scalar) failed", statics::called); + } + + template<> template<> + void object::test<7>() { set_test_name("apply(LLSD array)"); LL::apply(statics::various, @@ -202,7 +218,7 @@ namespace tut } template<> template<> - void object::test<7>() + void object::test<8>() { set_test_name("VAPPLY()"); // Make a std::array from statics::quick. We can't call a -- cgit v1.2.3 From cc3d21cceac7d6c1b2fc9297330fa819855f6e5b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 20 Dec 2022 09:53:58 -0500 Subject: DRTVWR-558: Tweak LLEventDispatcher. Instead of std::map, use std::unique_ptr as the mapped_type, using emplace() to store new entries. This more correctly captures the desired semantics: we have no intention of passing around the pointers in the map, we just want the map to delete them on destruction. Use std::function instead of boost::function. (cherry picked from commit 7ba53ef82db5683756e296225f0c8b838420a26e) --- indra/llcommon/lleventdispatcher.cpp | 12 +++--------- indra/llcommon/lleventdispatcher.h | 21 +++++++-------------- 2 files changed, 10 insertions(+), 23 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index 0c3bb35cfe..3e45601429 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -561,9 +561,7 @@ void LLEventDispatcher::addArrayParamsDispatchEntry(const std::string& name, const invoker_function& invoker, LLSD::Integer arity) { - mDispatch.insert( - DispatchMap::value_type(name, DispatchMap::mapped_type( - new ArrayParamsDispatchEntry(desc, invoker, arity)))); + mDispatch.emplace(name, new ArrayParamsDispatchEntry(desc, invoker, arity)); } void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name, @@ -572,18 +570,14 @@ void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name, const LLSD& params, const LLSD& defaults) { - mDispatch.insert( - DispatchMap::value_type(name, DispatchMap::mapped_type( - new MapParamsDispatchEntry(name, desc, invoker, params, defaults)))); + mDispatch.emplace(name, new MapParamsDispatchEntry(name, desc, invoker, params, defaults)); } /// Register a callable by name void LLEventDispatcher::add(const std::string& name, const std::string& desc, const Callable& callable, const LLSD& required) { - mDispatch.insert( - DispatchMap::value_type(name, DispatchMap::mapped_type( - new LLSDDispatchEntry(desc, callable, required)))); + mDispatch.emplace(name, new LLSDDispatchEntry(desc, callable, required)); } /// Unregister a callable diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 6d1df86fea..cf88dced12 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -56,9 +56,6 @@ static const auto nil_(nil); static const auto& nil(nil_); #endif -#include -#include -#include #include #include #include @@ -73,6 +70,9 @@ static const auto& nil(nil_); #include #include #include +#include // std::function +#include // std::unique_ptr +#include #include #include "llevents.h" #include "llsdutil.h" @@ -96,7 +96,7 @@ public: /// Accept any C++ callable with the right signature, typically a /// boost::bind() expression - typedef boost::function Callable; + typedef std::function Callable; /** * Register a @a callable by @a name. The passed @a callable accepts a @@ -293,14 +293,7 @@ private: virtual void call(const std::string& desc, const LLSD& event) const = 0; virtual LLSD addMetadata(LLSD) const = 0; }; - // Tried using boost::ptr_map, but ptr_map<> - // wants its value type to be "clonable," even just to dereference an - // iterator. I don't want to clone entries -- if I have to copy an entry - // around, I want it to continue pointing to the same DispatchEntry - // subclass object. However, I definitely want DispatchMap to destroy - // DispatchEntry if no references are outstanding at the time an entry is - // removed. This looks like a job for boost::shared_ptr. - typedef std::map > DispatchMap; + typedef std::map > DispatchMap; public: /// We want the flexibility to redefine what data we store per name, @@ -363,10 +356,10 @@ private: struct invoker; // deliver LLSD arguments one at a time - typedef boost::function args_source; + typedef std::function args_source; // obtain args from an args_source to build param list and call target // function - typedef boost::function invoker_function; + typedef std::function invoker_function; template invoker_function make_invoker(Function f); -- cgit v1.2.3 From ee7fc1d14b1a9b677129bb20714b79343a3bad94 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 6 Apr 2022 17:34:28 -0400 Subject: DRTVWR-558: Change LLEventDispatcher error action (also LLEventAPI). Originally the LLEventAPI mechanism was primarily used for VITA testing. In that case it was okay for the viewer to crash with LL_ERRS if the test script passed a bad request. With puppetry, hopefully new LEAP scripts will be written to engage LLEventAPIs in all sorts of interesting ways. Change error handling from LL_ERRS to LL_WARNS. Furthermore, if the incoming request contains a "reply" key, send back an error response to the requester. Update lleventdispatcher_test.cpp accordingly. (cherry picked from commit de0539fcbe815ceec2041ecc9981e3adf59f2806) (cherry picked from commit 4b60941952e97691f11806062f4bc66dd5ac8dae) --- indra/llcommon/lleventdispatcher.h | 1 + 1 file changed, 1 insertion(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index cf88dced12..09b786b69e 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -325,6 +325,7 @@ private: CLASS* downcast = static_cast(this); add(name, desc, boost::bind(method, downcast, _1), required); } + void addFail(const std::string& name, const std::string& classname) const; std::string try_call_log(const std::string& key, const std::string& name, const LLSD& event) const; std::string try_call(const std::string& key, const std::string& name, -- cgit v1.2.3 From 1ef06b04a3b43581d44d41e417fe8b290a4cfde2 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 6 Apr 2022 21:49:03 -0400 Subject: DRTVWR-558: LLEventAPI allows all LLEventDispatcher add() overloads. Previously, LLEventAPI intentionally hid all but one of the many add() overloads supported by its LLEventDispatcher base class. The reason was that certain of the add() methods take an optional fourth parameter that's an LLSD::Map describing the expected parameter structure, while others take a fourth templated parameter that's an instance getter callable. This led to ambiguity, especially when passed an LLSDMap instance that's convertible to LLSD but isn't literally LLSD. At the time, it was simpler to constrain the add() methods inherited from LLEventDispatcher. But by adding new std::enable_if constraints to certain LLEventDispatcher add() methods, we've resolved the ambiguities, so LLEventAPI subclasses can now use any add() overload (as claimed on the relevant Confluence page). LLEventDispatcher comments have always loftily claimed that an instance getter callable may return either a pointer or a reference, doesn't matter. But it does when trying to pass the getter's result to boost::fusion::push_back(): a reference must be wrapped with std::ref() while a pointer cannot be. std::ref(pointer) produces errors. Introduce LLEventDispatcher::invoker:: bindable() overloads to Do The Right Thing whether passed a pointer or a reference. (cherry picked from commit 743f487c2e123171c9fc6d5b84d768f1d856d569) (cherry picked from commit 8618e41b3489e321ecd70eb65ec4d9ca7e2f75c6) --- indra/llcommon/lleventdispatcher.h | 132 +++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 09b786b69e..3f328dce9a 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -165,12 +165,27 @@ public: * When calling this name, pass an LLSD::Array. Each entry in turn will be * converted to the corresponding parameter type using LLSDParam. */ +<<<<<<< variant A // enable_if usage per https://stackoverflow.com/a/39913395/5533635 template::value >::type> void add(const std::string& name, const std::string& desc, Function f); +>>>>>>> variant B + template + typename std::enable_if< + boost::function_types::is_nonmember_callable_builtin::value + >::type add(const std::string& name, + const std::string& desc, + Function f); +####### Ancestor + template + typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin + >::type add(const std::string& name, + const std::string& desc, + Function f); +======= end /** * Register a nonstatic class method with arbitrary parameters. @@ -189,6 +204,7 @@ public: * When calling this name, pass an LLSD::Array. Each entry in turn will be * converted to the corresponding parameter type using LLSDParam. */ +<<<<<<< variant A template::value && @@ -196,6 +212,24 @@ public: >::type> void add(const std::string& name, const std::string& desc, Method f, const InstanceGetter& getter); +>>>>>>> variant B + template + typename std::enable_if< + boost::function_types::is_member_function_pointer::value && + ! std::is_same::value && + ! std::is_same::value + >::type add(const std::string& name, + const std::string& desc, + Method f, + const InstanceGetter& getter); +####### Ancestor + template + typename boost::enable_if< boost::function_types::is_member_function_pointer + >::type add(const std::string& name, + const std::string& desc, + Method f, + const InstanceGetter& getter); +======= end /** * Register a free function with arbitrary parameters. (This also works @@ -212,12 +246,31 @@ public: * an LLSD::Array using LLSDArgsMapper and then convert each entry in turn * to the corresponding parameter type using LLSDParam. */ +<<<<<<< variant A template::value >::type> void add(const std::string& name, const std::string& desc, Function f, const LLSD& params, const LLSD& defaults=LLSD()); +>>>>>>> variant B + template + typename std::enable_if< + boost::function_types::is_nonmember_callable_builtin::value + >::type add(const std::string& name, + const std::string& desc, + Function f, + const LLSD& params, + const LLSD& defaults=LLSD()); +####### Ancestor + template + typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin + >::type add(const std::string& name, + const std::string& desc, + Function f, + const LLSD& params, + const LLSD& defaults=LLSD()); +======= end /** * Register a nonstatic class method with arbitrary parameters. @@ -240,6 +293,7 @@ public: * an LLSD::Array using LLSDArgsMapper and then convert each entry in turn * to the corresponding parameter type using LLSDParam. */ +<<<<<<< variant A template::value && @@ -248,6 +302,28 @@ public: void add(const std::string& name, const std::string& desc, Method f, const InstanceGetter& getter, const LLSD& params, const LLSD& defaults=LLSD()); +>>>>>>> variant B + template + typename std::enable_if< + boost::function_types::is_member_function_pointer::value && + ! std::is_same::value && + ! std::is_same::value + >::type add(const std::string& name, + const std::string& desc, + Method f, + const InstanceGetter& getter, + const LLSD& params, + const LLSD& defaults=LLSD()); +####### Ancestor + template + typename boost::enable_if< boost::function_types::is_member_function_pointer + >::type add(const std::string& name, + const std::string& desc, + Method f, + const InstanceGetter& getter, + const LLSD& params, + const LLSD& defaults=LLSD()); +======= end //@} @@ -457,8 +533,18 @@ struct LLEventDispatcher::invoker } }; +<<<<<<< variant A template void LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f) +>>>>>>> variant B +template +typename std::enable_if< boost::function_types::is_nonmember_callable_builtin::value >::type +LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f) +####### Ancestor +template +typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin >::type +LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f) +======= end { // Construct an invoker_function, a callable accepting const args_source&. // Add to DispatchMap an ArrayParamsDispatchEntry that will handle the @@ -467,9 +553,25 @@ void LLEventDispatcher::add(const std::string& name, const std::string& desc, Fu boost::function_types::function_arity::value); } +<<<<<<< variant A template void LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, const InstanceGetter& getter) +>>>>>>> variant B +template +typename std::enable_if< + boost::function_types::is_member_function_pointer::value && + ! std::is_same::value && + ! std::is_same::value +>::type +LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, + const InstanceGetter& getter) +####### Ancestor +template +typename boost::enable_if< boost::function_types::is_member_function_pointer >::type +LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, + const InstanceGetter& getter) +======= end { // Subtract 1 from the compile-time arity because the getter takes care of // the first parameter. We only need (arity - 1) additional arguments. @@ -477,18 +579,48 @@ void LLEventDispatcher::add(const std::string& name, const std::string& desc, Me boost::function_types::function_arity::value - 1); } +<<<<<<< variant A template void LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f, const LLSD& params, const LLSD& defaults) +>>>>>>> variant B +template +typename std::enable_if< boost::function_types::is_nonmember_callable_builtin::value >::type +LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f, + const LLSD& params, const LLSD& defaults) +####### Ancestor +template +typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin >::type +LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f, + const LLSD& params, const LLSD& defaults) +======= end { // See comments for previous is_nonmember_callable_builtin add(). addMapParamsDispatchEntry(name, desc, make_invoker(f), params, defaults); } +<<<<<<< variant A template void LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, const InstanceGetter& getter, const LLSD& params, const LLSD& defaults) +>>>>>>> variant B +template +typename std::enable_if< + boost::function_types::is_member_function_pointer::value && + ! std::is_same::value && + ! std::is_same::value +>::type +LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, + const InstanceGetter& getter, + const LLSD& params, const LLSD& defaults) +####### Ancestor +template +typename boost::enable_if< boost::function_types::is_member_function_pointer >::type +LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, + const InstanceGetter& getter, + const LLSD& params, const LLSD& defaults) +======= end { addMapParamsDispatchEntry(name, desc, make_invoker(f, getter), params, defaults); } -- cgit v1.2.3 From b6bbd86e5e6a23ef8bc81fa2374a82f05b4d18ed Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 6 Apr 2022 22:19:10 -0400 Subject: DRTVWR-558: Generalize LLEventDispatcher::add() constraints. Instead of checking whether an add() parameter is exactly LLSD or LLSDMap, check whether it's convertible to LLSD -- which handles those cases and more. (cherry picked from commit fa168c11f64771dadc5df86d14ca2f07eba3b8ba) (cherry picked from commit 6b5bfc1cf674fc568d86d7ed623fd7bb3ee2f646) --- indra/llcommon/lleventdispatcher.h | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 3f328dce9a..99f03fe0bc 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -216,8 +216,7 @@ public: template typename std::enable_if< boost::function_types::is_member_function_pointer::value && - ! std::is_same::value && - ! std::is_same::value + ! std::is_convertible::value >::type add(const std::string& name, const std::string& desc, Method f, @@ -306,8 +305,7 @@ public: template typename std::enable_if< boost::function_types::is_member_function_pointer::value && - ! std::is_same::value && - ! std::is_same::value + ! std::is_convertible::value >::type add(const std::string& name, const std::string& desc, Method f, @@ -561,8 +559,7 @@ void LLEventDispatcher::add(const std::string& name, const std::string& desc, Me template typename std::enable_if< boost::function_types::is_member_function_pointer::value && - ! std::is_same::value && - ! std::is_same::value + ! std::is_convertible::value >::type LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, const InstanceGetter& getter) @@ -608,8 +605,7 @@ void LLEventDispatcher::add(const std::string& name, const std::string& desc, Me template typename std::enable_if< boost::function_types::is_member_function_pointer::value && - ! std::is_same::value && - ! std::is_same::value + ! std::is_convertible::value >::type LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, const InstanceGetter& getter, -- cgit v1.2.3 From 45464ee2d2b83b750d45b860e6117a4b74242ead Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 20 Dec 2022 13:09:42 -0500 Subject: DRTVWR-558: Pull in LLEventDispatcher / LLDispatchListener fixes. For LLEventDispatcher::add(), use simpler std::enable_if construct that avoids the need to restate the whole conditional. Derive LLDispatchListener from LLEventStream, instead of containing an instance. This sets up for LazyEventAPI. Don't allow tweaking an LLDispatchListener (or subclass LLEventAPI) name. (cherry-picked from af4fbc1f8a9 on the lazy-eventpump branch) (cherry picked from commit 419e7a4230ae662b035ae771af8e7d8bceb2c8b1) --- indra/llcommon/lleventdispatcher.h | 128 ------------------------------------- 1 file changed, 128 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 99f03fe0bc..09b786b69e 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -165,27 +165,12 @@ public: * When calling this name, pass an LLSD::Array. Each entry in turn will be * converted to the corresponding parameter type using LLSDParam. */ -<<<<<<< variant A // enable_if usage per https://stackoverflow.com/a/39913395/5533635 template::value >::type> void add(const std::string& name, const std::string& desc, Function f); ->>>>>>> variant B - template - typename std::enable_if< - boost::function_types::is_nonmember_callable_builtin::value - >::type add(const std::string& name, - const std::string& desc, - Function f); -####### Ancestor - template - typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin - >::type add(const std::string& name, - const std::string& desc, - Function f); -======= end /** * Register a nonstatic class method with arbitrary parameters. @@ -204,7 +189,6 @@ public: * When calling this name, pass an LLSD::Array. Each entry in turn will be * converted to the corresponding parameter type using LLSDParam. */ -<<<<<<< variant A template::value && @@ -212,23 +196,6 @@ public: >::type> void add(const std::string& name, const std::string& desc, Method f, const InstanceGetter& getter); ->>>>>>> variant B - template - typename std::enable_if< - boost::function_types::is_member_function_pointer::value && - ! std::is_convertible::value - >::type add(const std::string& name, - const std::string& desc, - Method f, - const InstanceGetter& getter); -####### Ancestor - template - typename boost::enable_if< boost::function_types::is_member_function_pointer - >::type add(const std::string& name, - const std::string& desc, - Method f, - const InstanceGetter& getter); -======= end /** * Register a free function with arbitrary parameters. (This also works @@ -245,31 +212,12 @@ public: * an LLSD::Array using LLSDArgsMapper and then convert each entry in turn * to the corresponding parameter type using LLSDParam. */ -<<<<<<< variant A template::value >::type> void add(const std::string& name, const std::string& desc, Function f, const LLSD& params, const LLSD& defaults=LLSD()); ->>>>>>> variant B - template - typename std::enable_if< - boost::function_types::is_nonmember_callable_builtin::value - >::type add(const std::string& name, - const std::string& desc, - Function f, - const LLSD& params, - const LLSD& defaults=LLSD()); -####### Ancestor - template - typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin - >::type add(const std::string& name, - const std::string& desc, - Function f, - const LLSD& params, - const LLSD& defaults=LLSD()); -======= end /** * Register a nonstatic class method with arbitrary parameters. @@ -292,7 +240,6 @@ public: * an LLSD::Array using LLSDArgsMapper and then convert each entry in turn * to the corresponding parameter type using LLSDParam. */ -<<<<<<< variant A template::value && @@ -301,27 +248,6 @@ public: void add(const std::string& name, const std::string& desc, Method f, const InstanceGetter& getter, const LLSD& params, const LLSD& defaults=LLSD()); ->>>>>>> variant B - template - typename std::enable_if< - boost::function_types::is_member_function_pointer::value && - ! std::is_convertible::value - >::type add(const std::string& name, - const std::string& desc, - Method f, - const InstanceGetter& getter, - const LLSD& params, - const LLSD& defaults=LLSD()); -####### Ancestor - template - typename boost::enable_if< boost::function_types::is_member_function_pointer - >::type add(const std::string& name, - const std::string& desc, - Method f, - const InstanceGetter& getter, - const LLSD& params, - const LLSD& defaults=LLSD()); -======= end //@} @@ -531,18 +457,8 @@ struct LLEventDispatcher::invoker } }; -<<<<<<< variant A template void LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f) ->>>>>>> variant B -template -typename std::enable_if< boost::function_types::is_nonmember_callable_builtin::value >::type -LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f) -####### Ancestor -template -typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin >::type -LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f) -======= end { // Construct an invoker_function, a callable accepting const args_source&. // Add to DispatchMap an ArrayParamsDispatchEntry that will handle the @@ -551,24 +467,9 @@ LLEventDispatcher::add(const std::string& name, const std::string& desc, Functio boost::function_types::function_arity::value); } -<<<<<<< variant A template void LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, const InstanceGetter& getter) ->>>>>>> variant B -template -typename std::enable_if< - boost::function_types::is_member_function_pointer::value && - ! std::is_convertible::value ->::type -LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, - const InstanceGetter& getter) -####### Ancestor -template -typename boost::enable_if< boost::function_types::is_member_function_pointer >::type -LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, - const InstanceGetter& getter) -======= end { // Subtract 1 from the compile-time arity because the getter takes care of // the first parameter. We only need (arity - 1) additional arguments. @@ -576,47 +477,18 @@ LLEventDispatcher::add(const std::string& name, const std::string& desc, Method boost::function_types::function_arity::value - 1); } -<<<<<<< variant A template void LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f, const LLSD& params, const LLSD& defaults) ->>>>>>> variant B -template -typename std::enable_if< boost::function_types::is_nonmember_callable_builtin::value >::type -LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f, - const LLSD& params, const LLSD& defaults) -####### Ancestor -template -typename boost::enable_if< boost::function_types::is_nonmember_callable_builtin >::type -LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f, - const LLSD& params, const LLSD& defaults) -======= end { // See comments for previous is_nonmember_callable_builtin add(). addMapParamsDispatchEntry(name, desc, make_invoker(f), params, defaults); } -<<<<<<< variant A template void LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, const InstanceGetter& getter, const LLSD& params, const LLSD& defaults) ->>>>>>> variant B -template -typename std::enable_if< - boost::function_types::is_member_function_pointer::value && - ! std::is_convertible::value ->::type -LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, - const InstanceGetter& getter, - const LLSD& params, const LLSD& defaults) -####### Ancestor -template -typename boost::enable_if< boost::function_types::is_member_function_pointer >::type -LLEventDispatcher::add(const std::string& name, const std::string& desc, Method f, - const InstanceGetter& getter, - const LLSD& params, const LLSD& defaults) -======= end { addMapParamsDispatchEntry(name, desc, make_invoker(f, getter), params, defaults); } -- cgit v1.2.3 From 07c5645f5f9130a7fc338df0bc2bb791d43bd702 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 22 Dec 2022 14:53:29 -0500 Subject: DRTVWR-558: LLEventDispatcher uses LL::apply(), not boost::fusion. While calling a C++ function with arguments taken from a runtime-variable data structure necessarily involves a bit of hocus-pocus, the best you can say for the boost::fusion based implementation is that it worked. Sadly, template recursion limited its applicability to a handful of function arguments. Now that we have LL::apply(), use that instead. This implementation is much more straightforward. In particular, the LLSDArgsSource class, whose job was to dole out elements of an LLSD array one at a time for the template recursion, goes away entirely. Make virtual LLEventDispatcher::DispatchEntry::call() return LLSD instead of void. All LLEventDispatcher target functions so far have been void; any function that wants to respond to its invoker must do so explicitly by calling sendReply() or constructing an LLEventAPI::Response instance. Supporting non- void functions permits LLEventDispatcher to respond implicitly with the returned value. Of course this requires a wrapper for void target functions that returns LLSD::isUndefined(). Break out LLEventDispatcher::reply() from callFail(), so we can reply with success as well as failure. Make LLEventDispatcher::try_call_log() prepend the actual leaf class name and description to any error returned by three-arg try_call(). That try_call() overload reported "LLEventDispatcher(desc): " for a couple specific errors, but no others. Hoist to try_call_log() to apply uniformly. Introduce new try_call_one() method to diagnose name-not-found errors and catch internal DispatchError and LL::apply_error exceptions. try_call_one() returns a std::pair, containing either an error message or an LLSD value. Make try_call_log() and three-arg try_call() accept LLSD 'name' instead of plain std::string, allowing for the possibility of an array or map. That lets us extend three-arg try_call() to break out new cases for the function selector LLSD: isUndefined(), isArray(), isMap() and (current case) scalar String. If try_call_one() reports an error, log it and try to send reply, as now. If it returns LLSD::isUndefined(), e.g. from a void target function wrapper, do nothing. But if it returns an LLSD map, try to send that back to the invoker. And if it returns an LLSD scalar or array, wrap it in a map with key "data" to respond to the invoker. Allowing a target function to return its result rather than explicitly sending it opens the possibility of batched requests (aggregate 'name') returning batched responses. Almost every place that constructs LLEventDispatcher's internal DispatchError exception called stringize() to format the what() string. Simplify calls by making DispatchError accept variadic arguments and forward to stringize(). Add LL::invoke() to apply.h. Like LL::apply(), this is a (limited) C++14 foreshadowing of std::invoke(), with preprocessor conditionals to switch to std::invoke() when that's available. Introduce LL::invoke() to handle a callable that's actually a pointer to method. Now our C++14 apply() implementation can accept pointer to method, using invoke() to generalize the actual function call. Also anticipate std::bind_front() with LL::bind_front(). For apply(func, std::array) and our extensions apply(func, std::vector) and apply(func, LLSD), we can't pass a pointer to method as the func unless the second argument happens to be an array or vector of pointers (or references) to instances of exactly the right class -- and of course LLSD can't store such at all. It's tempting to pass std::bind(std::mem_fn(ptr_to_method), instance), but that won't work: std::bind() requires a value or placeholder for each argument to pass to the bound function. The bind() expression above would only work for a nullary method. std::bind_front() would work, but that doesn't arrive until C++20. Again, once we get there we'll defer to the std:: implementation. Instead of the generic __cplusplus, check the appropriate feature-test macro for availability of each of std::invoke(), std::apply() and std::bind_front(). Change apply() error handling from assert() to new LL::apply_error exception. LLEventDispatcher must be able to intercept apply() errors. Move validation and synthesis of the relevant error message to new apply.cpp source file. Add to llptrto.h new LL::get_ref() and LL::get_ptr() template functions to unify the cases of a calling template accepting either a pointer or a reference. Wrapping the parameter in either get_ref() or get_ptr() allows dereferencing the parameter as desired. Move LL::apply(function, LLSD) argument validation/manipulation to a non- template function in llsdutil.cpp: no need to replicate that logic in the template for every CALLABLE specialization. The trouble with passing bind_front(std::mem_fn(ptr_to_method), instance) to apply() is that since bind_front() accepts and forwards variadic additional arguments, apply() can't infer the arity of the bound ptr_to_method. Address that by introducing apply_n(function, LLSD), permitting a caller to infer the arity of ptr_to_method and explicitly pass it to apply_n(). Polish up lleventdispatcher_test.cpp accordingly. Wrong LLSD type and wrong number of arguments now produce different (somewhat more informative) error messages. Moreover, passing too many entries in an LLSD array used to work: the extra arguments used to be ignored. Now we require that the size of the array match the arity of the target function. Change the too-many-arguments tests from success testing to error testing. Replace 'foreach' aka BOOST_FOREACH macro invocations with range 'for'. Replace STRINGIZE(item0 << item1 << ...) with stringize(item0, item1, ...). (cherry picked from commit 9c049563b5480bb7e8ed87d9313822595b479c3b) --- indra/llcommon/CMakeLists.txt | 1 + indra/llcommon/apply.cpp | 29 +++ indra/llcommon/apply.h | 100 +++++++++- indra/llcommon/lleventdispatcher.cpp | 224 ++++++++++++--------- indra/llcommon/lleventdispatcher.h | 255 ++++++++---------------- indra/llcommon/llptrto.h | 88 +++++++- indra/llcommon/llsdutil.cpp | 35 ++++ indra/llcommon/llsdutil.h | 49 ++--- indra/llcommon/tests/lleventdispatcher_test.cpp | 141 ++++++------- 9 files changed, 543 insertions(+), 379 deletions(-) create mode 100644 indra/llcommon/apply.cpp (limited to 'indra/llcommon') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 941d2d7baf..33e8301e12 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -16,6 +16,7 @@ include(Tracy) set(llcommon_SOURCE_FILES + apply.cpp indra_constants.cpp lazyeventapi.cpp llallocator.cpp diff --git a/indra/llcommon/apply.cpp b/indra/llcommon/apply.cpp new file mode 100644 index 0000000000..417e23d3b4 --- /dev/null +++ b/indra/llcommon/apply.cpp @@ -0,0 +1,29 @@ +/** + * @file apply.cpp + * @author Nat Goodspeed + * @date 2022-12-21 + * @brief Implementation for apply. + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Copyright (c) 2022, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "apply.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "stringize.h" + +void LL::apply_validate_size(size_t size, size_t arity) +{ + if (size != arity) + { + LLTHROW(apply_error(stringize("LL::apply(func(", arity, " args), " + "std::vector(", size, " elements))"))); + } +} diff --git a/indra/llcommon/apply.h b/indra/llcommon/apply.h index 9f4c268895..357887dfd7 100644 --- a/indra/llcommon/apply.h +++ b/indra/llcommon/apply.h @@ -12,9 +12,11 @@ #if ! defined(LL_APPLY_H) #define LL_APPLY_H +#include "llexception.h" #include -#include +#include // std::mem_fn() #include +#include // std::is_member_pointer namespace LL { @@ -56,9 +58,39 @@ namespace LL (ARGS)) /***************************************************************************** -* apply(function, tuple) +* invoke() *****************************************************************************/ -#if __cplusplus >= 201703L +#if __cpp_lib_invoke >= 201411L + +// C++17 implementation +using std::invoke; + +#else // no std::invoke + +// Use invoke() to handle pointer-to-method: +// derived from https://stackoverflow.com/a/38288251 +template::type>::value, + int>::type = 0 > +auto invoke(Fn&& f, Args&&... args) +{ + return std::mem_fn(f)(std::forward(args)...); +} + +template::type>::value, + int>::type = 0 > +auto invoke(Fn&& f, Args&&... args) +{ + return std::forward(f)(std::forward(args)...); +} + +#endif // no std::invoke + +/***************************************************************************** +* apply(function, tuple); apply(function, array) +*****************************************************************************/ +#if __cpp_lib_apply >= 201603L // C++17 implementation using std::apply; @@ -71,7 +103,7 @@ template auto apply_impl(CALLABLE&& func, const std::tuple& args, std::index_sequence) { // call func(unpacked args) - return std::forward(func)(std::move(std::get(args))...); + return invoke(std::forward(func), std::get(args)...); } template @@ -85,11 +117,6 @@ auto apply(CALLABLE&& func, const std::tuple& args) std::index_sequence_for{}); } -#endif // C++14 - -/***************************************************************************** -* apply(function, std::array) -*****************************************************************************/ // per https://stackoverflow.com/a/57510428/5533635 template auto apply(CALLABLE&& func, const std::array& args) @@ -97,6 +124,50 @@ auto apply(CALLABLE&& func, const std::array& args) return apply(std::forward(func), std::tuple_cat(args)); } +#endif // C++14 + +/***************************************************************************** +* bind_front() +*****************************************************************************/ +// To invoke a non-static member function with a tuple, you need a callable +// that binds your member function with an instance pointer or reference. +// std::bind_front() is perfect: std::bind_front(&cls::method, instance). +// Unfortunately bind_front() only enters the standard library in C++20. +#if __cpp_lib_bind_front >= 201907L + +// C++20 implementation +using std::bind_front; + +#else // no std::bind_front() + +template::type>::value, + int>::type = 0 > +auto bind_front(Fn&& f, Args&&... args) +{ + // Don't use perfect forwarding for f or args: we must bind them for later. + return [f, pfx_args=std::make_tuple(args...)] + (auto&&... sfx_args) + { + // Use perfect forwarding for sfx_args because we use them as soon as + // we receive them. + return apply( + f, + std::tuple_cat(pfx_args, + std::make_tuple(std::forward(sfx_args)...))); + }; +} + +template::type>::value, + int>::type = 0 > +auto bind_front(Fn&& f, Args&&... args) +{ + return bind_front(std::mem_fn(std::forward(f)), std::forward(args)...); +} + +#endif // C++20 with std::bind_front() + /***************************************************************************** * apply(function, std::vector) *****************************************************************************/ @@ -108,6 +179,15 @@ auto apply_impl(CALLABLE&& func, const std::vector& args, std::index_sequence std::make_tuple(args[I]...)); } +// produce suitable error if apply(func, vector) is the wrong size for func() +void apply_validate_size(size_t size, size_t arity); + +/// possible exception from apply() validation +struct apply_error: public LLException +{ + apply_error(const std::string& what): LLException(what) {} +}; + /** * apply(function, std::vector) goes beyond C++17 std::apply(). For this case * @a function @emph cannot be variadic: the compiler must know at compile @@ -117,7 +197,7 @@ template auto apply(CALLABLE&& func, const std::vector& args) { constexpr auto arity = boost::function_traits::arity; - assert(args.size() == arity); + apply_validate_size(args.size(), arity); return apply_impl(std::forward(func), args, std::make_index_sequence()); diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index 3e45601429..e7e73125a7 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -50,68 +50,13 @@ *****************************************************************************/ struct DispatchError: public LLException { - DispatchError(const std::string& what): LLException(what) {} + // template constructor involving strings passes all arguments to + // stringize() to construct LLException's what() string + template + DispatchError(const std::string& arg0, ARGS&&... args): + LLException(stringize(arg0, std::forward(args)...)) {} }; -/***************************************************************************** -* LLSDArgsSource -*****************************************************************************/ -/** - * Store an LLSD array, producing its elements one at a time. It is an error - * if the consumer requests more elements than the array contains. - */ -class LL_COMMON_API LLSDArgsSource -{ -public: - LLSDArgsSource(const std::string function, const LLSD& args); - ~LLSDArgsSource(); - - LLSD next(); - - void done() const; - -private: - std::string _function; - LLSD _args; - LLSD::Integer _index; -}; - -LLSDArgsSource::LLSDArgsSource(const std::string function, const LLSD& args): - _function(function), - _args(args), - _index(0) -{ - if (! (_args.isUndefined() || _args.isArray())) - { - LLTHROW(DispatchError(stringize(_function, " needs an args array instead of ", _args))); - } -} - -LLSDArgsSource::~LLSDArgsSource() -{ - done(); -} - -LLSD LLSDArgsSource::next() -{ - if (_index >= _args.size()) - { - LLTHROW(DispatchError(stringize(_function, " requires more arguments than the ", - _args.size(), " provided: ", _args))); - } - return _args[_index++]; -} - -void LLSDArgsSource::done() const -{ - if (_index < _args.size()) - { - LL_WARNS("LLSDArgsSource") << _function << " only consumed " << _index - << " of the " << _args.size() << " arguments provided: " - << _args << LL_ENDL; - } -} - /***************************************************************************** * LLSDArgsMapper *****************************************************************************/ @@ -204,7 +149,7 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function, { if (! (_names.isUndefined() || _names.isArray())) { - LLTHROW(DispatchError(stringize(function, " names must be an array, not ", names))); + LLTHROW(DispatchError(function, " names must be an array, not ", names)); } auto nparams(_names.size()); // From _names generate _indexes. @@ -227,8 +172,8 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function, // defaults is a (possibly empty) array. Right-align it with names. if (ndefaults > nparams) { - LLTHROW(DispatchError(stringize(function, " names array ", names, - " shorter than defaults array ", defaults))); + LLTHROW(DispatchError(function, " names array ", names, + " shorter than defaults array ", defaults)); } // Offset by which we slide defaults array right to right-align with @@ -265,14 +210,14 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function, } if (bogus.size()) { - LLTHROW(DispatchError(stringize(function, " defaults specified for nonexistent params ", - formatlist(bogus)))); + LLTHROW(DispatchError(function, " defaults specified for nonexistent params ", + formatlist(bogus))); } } else { - LLTHROW(DispatchError(stringize(function, " defaults must be a map or an array, not ", - defaults))); + LLTHROW(DispatchError(function, " defaults must be a map or an array, not ", + defaults)); } } @@ -280,8 +225,8 @@ LLSD LLSDArgsMapper::map(const LLSD& argsmap) const { if (! (argsmap.isUndefined() || argsmap.isMap() || argsmap.isArray())) { - LLTHROW(DispatchError(stringize(_function, " map() needs a map or array, not ", - argsmap))); + LLTHROW(DispatchError(_function, " map() needs a map or array, not ", + argsmap)); } // Initialize the args array. Indexing a non-const LLSD array grows it // to appropriate size, but we don't want to resize this one on each @@ -378,8 +323,8 @@ LLSD LLSDArgsMapper::map(const LLSD& argsmap) const // by argsmap, that's a problem. if (unfilled.size()) { - LLTHROW(DispatchError(stringize(_function, " missing required arguments ", - formatlist(unfilled), " from ", argsmap))); + LLTHROW(DispatchError(_function, " missing required arguments ", + formatlist(unfilled), " from ", argsmap)); } // done @@ -399,6 +344,9 @@ std::string LLSDArgsMapper::formatlist(const LLSD& list) return out.str(); } +/***************************************************************************** +* LLEventDispatcher +*****************************************************************************/ LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key): mDesc(desc), mKey(key) @@ -409,6 +357,10 @@ LLEventDispatcher::~LLEventDispatcher() { } +LLEventDispatcher::DispatchEntry::DispatchEntry(const std::string& desc): + mDesc(desc) +{} + /** * DispatchEntry subclass used for callables accepting(const LLSD&) */ @@ -423,16 +375,17 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE Callable mFunc; LLSD mRequired; - virtual void call(const std::string& desc, const LLSD& event) const + virtual LLSD call(const std::string& desc, const LLSD& event) const { // Validate the syntax of the event itself. std::string mismatch(llsd_matches(mRequired, event)); if (! mismatch.empty()) { - LLTHROW(DispatchError(stringize(desc, ": bad request: ", mismatch))); + LLTHROW(DispatchError(desc, ": bad request: ", mismatch)); } // Event syntax looks good, go for it! mFunc(event); + return {}; } virtual LLSD addMetadata(LLSD meta) const @@ -455,10 +408,9 @@ struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::Dispatc invoker_function mInvoker; - virtual void call(const std::string& desc, const LLSD& event) const + virtual LLSD call(const std::string&, const LLSD& event) const { - LLSDArgsSource src(desc, event); - mInvoker(boost::bind(&LLSDArgsSource::next, boost::ref(src))); + return mInvoker(event); } }; @@ -541,11 +493,11 @@ struct LLEventDispatcher::MapParamsDispatchEntry: public LLEventDispatcher::Para LLSD mRequired; LLSD mOptional; - virtual void call(const std::string& desc, const LLSD& event) const + virtual LLSD call(const std::string& desc, const LLSD& event) const { // Just convert from LLSD::Map to LLSD::Array using mMapper, then pass // to base-class call() method. - ParamsDispatchEntry::call(desc, mMapper.map(event)); + return ParamsDispatchEntry::call(desc, mMapper.map(event)); } virtual LLSD addMetadata(LLSD meta) const @@ -616,13 +568,19 @@ void LLEventDispatcher::operator()(const LLSD& event) const } void LLEventDispatcher::callFail(const LLSD& event, const std::string& msg) const +{ + // pass back a response that includes an "error" key with the message. + reply(llsd::map("error", msg), event); +} + +void LLEventDispatcher::reply(const LLSD& response, const LLSD& event) const { static LLSD::String key{ "reply" }; if (event.has(key)) { - // Oh good, the incoming event specifies a reply pump -- pass back a - // response that includes an "error" key with the message. - sendReply(llsd::map("error", msg), event, key); + // Oh good, the incoming event specifies a reply pump -- pass back + // our response. + sendReply(response, event, key); } } @@ -631,17 +589,30 @@ bool LLEventDispatcher::try_call(const LLSD& event) const return try_call_log(mKey, event[mKey], event).empty(); } +/*==========================================================================*| + TODO: + +* When mInvoker returns result.isDefined(), sendReply(llsd::map("data", result)) +* When try_call finds name.isArray(), construct response array from + dispatching each call, sendReply() as above +* When try_call finds name.isMap(), construct response map from dispatching + each call, sendReply() as above -- note, caller can't care about order +* Possible future transactional behavior: look up all names before calling any + +|*==========================================================================*/ bool LLEventDispatcher::try_call(const std::string& name, const LLSD& event) const { return try_call_log(std::string(), name, event).empty(); } -std::string LLEventDispatcher::try_call_log(const std::string& key, const std::string& name, +std::string LLEventDispatcher::try_call_log(const std::string& key, const LLSD& name, const LLSD& event) const { std::string error{ try_call(key, name, event) }; if (! error.empty()) { + // If we're a subclass of LLEventDispatcher, e.g. LLEventAPI, report that. + error = stringize(LLError::Log::classname(this), "(", mDesc, "): ", error); LL_WARNS("LLEventDispatcher") << error << LL_ENDL; } return error; @@ -649,33 +620,100 @@ std::string LLEventDispatcher::try_call_log(const std::string& key, const std::s // This internal method returns empty string if the call succeeded, else // non-empty error message. -std::string LLEventDispatcher::try_call(const std::string& key, const std::string& name, +std::string LLEventDispatcher::try_call(const std::string& key, const LLSD& name, const LLSD& event) const +{ + if (name.isUndefined()) + { + if (key.empty()) + { + return "attempting to call with no name"; + } + else + { + return stringize("no ", key); + } + } + else if (name.isArray()) + { + return stringize(key, " array dispatch ", name, " not yet implemented"); + } + else if (name.isMap()) + { + return stringize(key, " map dispatch ", name, " not yet implemented"); + } + else if (! name.isString()) + { + return stringize(key, " bad type ", LLSD::typeString(name.type()), ' ', name, + " -- function names are String"); + } + else // name is an LLSD::String + { + auto success{ try_call_one(key, name, event) }; + // pretend to unpack + std::string& error{ success.first }; + LLSD& result{ success.second }; + // did try_call_one() report an error? + if (! error.empty()) + { + // if we have a reply key, respond to invoker + reply(llsd::map("error", error), event); + // now tell caller + return error; + } + // try_call_one() succeeded in calling the target function -- + // should we reply to invoker? + if (result.isUndefined()) + { + // We would get result.isUndefined() if the target function has + // void return. In any case, even if the target function returns + // LLSD, isUndefined() means "don't bother sending response." + return {}; + } + // result.isDefined(): the target function returned something. + // Respond to invoker if we have a "reply" key. + if (! result.isMap()) + { + // wrap result in a map to play well with sendReply() + result = llsd::map("data", result); + } + reply(result, event); + return {}; + } +} + +std::pair +LLEventDispatcher::try_call_one(const std::string& key, const std::string& name, + const LLSD& event) const { DispatchMap::const_iterator found = mDispatch.find(name); if (found == mDispatch.end()) { if (key.empty()) { - return stringize("LLEventDispatcher(", mDesc, "): '", name, "' not found"); + return { stringize("'", name, "' not found"), {} }; } else { - return stringize("LLEventDispatcher(", mDesc, "): bad ", key, " value '", name, "'"); + return { stringize("bad ", key, " value '", name, "'"), {} }; } } try { // Found the name, so it's plausible to even attempt the call. - found->second->call(stringize("LLEventDispatcher(", mDesc, ") calling '", name, "'"), - event); + return { {}, found->second->call(stringize("calling '", name, "'"), event) }; } catch (const DispatchError& err) { - return err.what(); + // trouble preparing arguments + return { err.what(), {} }; + } + catch (const LL::apply_error& err) + { + // could also hit runtime errors with LL::apply() + return { err.what(), {} }; } - return {}; // tell caller we were able to call } LLSD LLEventDispatcher::getMetadata(const std::string& name) const @@ -691,6 +729,9 @@ LLSD LLEventDispatcher::getMetadata(const std::string& name) const return found->second->addMetadata(meta); } +/***************************************************************************** +* LLDispatchListener +*****************************************************************************/ LLDispatchListener::LLDispatchListener(const std::string& pumpname, const std::string& key): LLEventDispatcher(pumpname, key), // Do NOT tweak the passed pumpname. In practice, when someone @@ -712,8 +753,3 @@ bool LLDispatchListener::process(const LLSD& event) (*this)(event); return false; } - -LLEventDispatcher::DispatchEntry::DispatchEntry(const std::string& desc): - mDesc(desc) -{} - diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 09b786b69e..cebce618df 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -27,54 +27,23 @@ * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ - * - * The invoker machinery that constructs a boost::fusion argument list for use - * with boost::fusion::invoke() is derived from - * http://www.boost.org/doc/libs/1_45_0/libs/function_types/example/interpreter.hpp - * whose license information is copied below: - * - * "(C) Copyright Tobias Schwinger - * - * Use modification and distribution are subject to the boost Software License, - * Version 1.0. (See http://www.boost.org/LICENSE_1_0.txt)." */ #if ! defined(LL_LLEVENTDISPATCHER_H) #define LL_LLEVENTDISPATCHER_H -// nil is too generic a term to be allowed to be a global macro. In -// particular, boost::fusion defines a 'class nil' (properly encapsulated in a -// namespace) that a global 'nil' macro breaks badly. -#if defined(nil) -// Capture the value of the macro 'nil', hoping int is an appropriate type. -static const auto nil_(nil); -// Now forget the macro. -#undef nil -// Finally, reintroduce 'nil' as a properly-scoped alias for the previously- -// defined const 'nil_'. Make it static since otherwise it produces duplicate- -// symbol link errors later. -static const auto& nil(nil_); -#endif - -#include #include +#include #include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include #include // std::function #include // std::unique_ptr #include #include +#include #include "llevents.h" +#include "llptrto.h" #include "llsdutil.h" class LLSD; @@ -94,8 +63,7 @@ public: /// @name Register functions accepting(const LLSD&) //@{ - /// Accept any C++ callable with the right signature, typically a - /// boost::bind() expression + /// Accept any C++ callable with the right signature typedef std::function Callable; /** @@ -126,7 +94,7 @@ public: /** * Special case: a subclass of this class can pass an unbound member * function pointer (of an LLEventDispatcher subclass) without explicitly - * specifying the boost::bind() expression. The passed @a method + * specifying a std::bind() expression. The passed @a method * accepts a single LLSD value, presumably containing other parameters. */ template @@ -158,10 +126,6 @@ public: * Register a free function with arbitrary parameters. (This also works * for static class methods.) * - * @note This supports functions with up to about 6 parameters -- after - * that you start getting dismaying compile errors in which - * boost::fusion::joint_view is mentioned a surprising number of times. - * * When calling this name, pass an LLSD::Array. Each entry in turn will be * converted to the corresponding parameter type using LLSDParam. */ @@ -175,10 +139,6 @@ public: /** * Register a nonstatic class method with arbitrary parameters. * - * @note This supports functions with up to about 6 parameters -- after - * that you start getting dismaying compile errors in which - * boost::fusion::joint_view is mentioned a surprising number of times. - * * To cover cases such as a method on an LLSingleton we don't yet want to * instantiate, instead of directly storing an instance pointer, accept a * nullary callable returning a pointer/reference to the desired class @@ -201,10 +161,6 @@ public: * Register a free function with arbitrary parameters. (This also works * for static class methods.) * - * @note This supports functions with up to about 6 parameters -- after - * that you start getting dismaying compile errors in which - * boost::fusion::joint_view is mentioned a surprising number of times. - * * Pass an LLSD::Array of parameter names, and optionally another * LLSD::Array of default parameter values, a la LLSDArgsMapper. * @@ -222,10 +178,6 @@ public: /** * Register a nonstatic class method with arbitrary parameters. * - * @note This supports functions with up to about 6 parameters -- after - * that you start getting dismaying compile errors in which - * boost::fusion::joint_view is mentioned a surprising number of times. - * * To cover cases such as a method on an LLSingleton we don't yet want to * instantiate, instead of directly storing an instance pointer, accept a * nullary callable returning a pointer/reference to the desired class @@ -290,7 +242,7 @@ private: std::string mDesc; - virtual void call(const std::string& desc, const LLSD& event) const = 0; + virtual LLSD call(const std::string& desc, const LLSD& event) const = 0; virtual LLSD addMetadata(LLSD) const = 0; }; typedef std::map > DispatchMap; @@ -322,17 +274,28 @@ private: void addMethod(const std::string& name, const std::string& desc, const METHOD& method, const LLSD& required) { - CLASS* downcast = static_cast(this); - add(name, desc, boost::bind(method, downcast, _1), required); + CLASS* downcast = dynamic_cast(this); + if (! downcast) + { + addFail(name, typeid(CLASS).name()); + } + else + { + add(name, desc, std::bind(method, downcast, std::placeholders::_1), required); + } } void addFail(const std::string& name, const std::string& classname) const; - std::string try_call_log(const std::string& key, const std::string& name, + std::string try_call_log(const std::string& key, const LLSD& name, const LLSD& event) const; - std::string try_call(const std::string& key, const std::string& name, + std::string try_call(const std::string& key, const LLSD& name, const LLSD& event) const; + // returns either (empty string, LLSD) or (error message, isUndefined) + std::pair + try_call_one(const std::string& key, const std::string& name, const LLSD& event) const; // Implement "it is an error" semantics for attempted call operations: if // the incoming event includes a "reply" key, log and send an error reply. void callFail(const LLSD& event, const std::string& msg) const; + void reply(const LLSD& response, const LLSD& event) const; std::string mDesc, mKey; DispatchMap mDispatch; @@ -347,20 +310,8 @@ private: struct ArrayParamsDispatchEntry; struct MapParamsDispatchEntry; - // Step 2 of parameter analysis. Instantiating invoker - // implicitly sets its From and To parameters to the (compile time) begin - // and end iterators over that function's parameter types. - template< typename Function - , class From = typename boost::mpl::begin< boost::function_types::parameter_types >::type - , class To = typename boost::mpl::end< boost::function_types::parameter_types >::type - > - struct invoker; - - // deliver LLSD arguments one at a time - typedef std::function args_source; - // obtain args from an args_source to build param list and call target - // function - typedef std::function invoker_function; + // call target function with args from LLSD array + typedef std::function invoker_function; template invoker_function make_invoker(Function f); @@ -375,92 +326,17 @@ private: const invoker_function& invoker, const LLSD& params, const LLSD& defaults); + template + struct ReturnLLSD; }; /***************************************************************************** * LLEventDispatcher template implementation details *****************************************************************************/ -// Step 3 of parameter analysis, the recursive case. -template -struct LLEventDispatcher::invoker -{ - template - struct remove_cv_ref - : boost::remove_cv< typename boost::remove_reference::type > - { }; - - // apply() accepts an arbitrary boost::fusion sequence as args. It - // examines the next parameter type in the parameter-types sequence - // bounded by From and To, obtains the next LLSD object from the passed - // args_source and constructs an LLSDParam of appropriate type to try - // to convert the value. It then recurs with the next parameter-types - // iterator, passing the args sequence thus far. - template - static inline - void apply(Function func, const args_source& argsrc, Args const & args) - { - typedef typename boost::mpl::deref::type arg_type; - typedef typename boost::mpl::next::type next_iter_type; - typedef typename remove_cv_ref::type plain_arg_type; - - invoker::apply - ( func, argsrc, boost::fusion::push_back(args, LLSDParam(argsrc()))); - } - - // Special treatment for instance (first) parameter of a non-static member - // function. Accept the instance-getter callable, calling that to produce - // the first args value. Since we know we're at the top of the recursion - // chain, we need not also require a partial args sequence from our caller. - template - static inline - void method_apply(Function func, const args_source& argsrc, const InstanceGetter& getter) - { - typedef typename boost::mpl::next::type next_iter_type; - - // Instead of grabbing the first item from argsrc and making an - // LLSDParam of it, call getter() and pass that as the instance param. - invoker::apply - ( func, argsrc, boost::fusion::push_back(boost::fusion::nil(), bindable(getter()))); - } - - template - static inline - auto bindable(T&& value, - typename std::enable_if::value, bool>::type=true) - { - // if passed a pointer, just return that pointer - return std::forward(value); - } - - template - static inline - auto bindable(T&& value, - typename std::enable_if::value, bool>::type=true) - { - // if passed a reference, wrap it for binding - return std::ref(std::forward(value)); - } -}; - -// Step 4 of parameter analysis, the leaf case. When the general -// invoker logic has advanced From until it matches To, -// the compiler will pick this template specialization. -template -struct LLEventDispatcher::invoker -{ - // the argument list is complete, now call the function - template - static inline - void apply(Function func, const args_source&, Args const & args) - { - boost::fusion::invoke(func, args); - } -}; - template void LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f) { - // Construct an invoker_function, a callable accepting const args_source&. + // Construct an invoker_function, a callable accepting const LLSD&. // Add to DispatchMap an ArrayParamsDispatchEntry that will handle the // caller's LLSD::Array. addArrayParamsDispatchEntry(name, desc, make_invoker(f), @@ -493,33 +369,76 @@ void LLEventDispatcher::add(const std::string& name, const std::string& desc, Me addMapParamsDispatchEntry(name, desc, make_invoker(f, getter), params, defaults); } +// general case, when f() has a non-void return type +template +struct LLEventDispatcher::ReturnLLSD +{ + template + LLSD operator()(Function f, const LLSD& args) + { + return { LL::apply(f, args) }; + } + + template + LLSD operator()(Method f, const InstanceGetter& getter, const LLSD& args) + { + constexpr auto arity = boost::function_types::function_arity< + typename std::remove_reference::type>::value - 1; + + // Use bind_front() to bind the method to (a pointer to) the object + // returned by getter(). It's okay to capture and bind a pointer + // because this bind_front() object will last only as long as this + // operator() call. + return { LL::apply_n(LL::bind_front(f, LL::get_ptr(getter())), args) }; + } +}; + +// specialize for void return type +template <> +struct LLEventDispatcher::ReturnLLSD +{ + template + LLSD operator()(Function f, const LLSD& args) + { + LL::apply(f, args); + return {}; + } + + template + LLSD operator()(Method f, const InstanceGetter& getter, const LLSD& args) + { + constexpr auto arity = boost::function_types::function_arity< + typename std::remove_reference::type>::value - 1; + + // Use bind_front() to bind the method to (a pointer to) the object + // returned by getter(). It's okay to capture and bind a pointer + // because this bind_front() object will last only as long as this + // operator() call. + LL::apply_n(LL::bind_front(f, LL::get_ptr(getter())), args); + return {}; + } +}; + template LLEventDispatcher::invoker_function LLEventDispatcher::make_invoker(Function f) { - // Step 1 of parameter analysis, the top of the recursion. Passing a - // suitable f (see add()'s enable_if condition) to this method causes it - // to infer the function type; specifying that function type to invoker<> - // causes it to fill in the begin/end MPL iterators over the function's - // list of parameter types. - // While normally invoker::apply() could infer its template type from the - // boost::fusion::nil parameter value, here we must be explicit since - // we're boost::bind()ing it rather than calling it directly. - return boost::bind(&invoker::template apply, - f, - _1, - boost::fusion::nil()); + return [f](const LLSD& args) + { + return ReturnLLSD::type>() + (f, args); + }; } template LLEventDispatcher::invoker_function LLEventDispatcher::make_invoker(Method f, const InstanceGetter& getter) { - // Use invoker::method_apply() to treat the instance (first) arg specially. - return boost::bind(&invoker::template method_apply, - f, - _1, - getter); + return [f, getter](const LLSD& args) + { + return ReturnLLSD::type>() + (f, getter, args); + }; } /***************************************************************************** diff --git a/indra/llcommon/llptrto.h b/indra/llcommon/llptrto.h index 4082e30de6..9ef279fdbf 100644 --- a/indra/llcommon/llptrto.h +++ b/indra/llcommon/llptrto.h @@ -33,9 +33,12 @@ #include "llpointer.h" #include "llrefcount.h" // LLRefCount +#include +#include #include #include -#include +#include // std::shared_ptr, std::unique_ptr +#include /** * LLPtrTo::type is either of two things: @@ -55,14 +58,14 @@ struct LLPtrTo /// specialize for subclasses of LLRefCount template -struct LLPtrTo >::type> +struct LLPtrTo::value >::type> { typedef LLPointer type; }; /// specialize for subclasses of LLThreadSafeRefCount template -struct LLPtrTo >::type> +struct LLPtrTo::value >::type> { typedef LLPointer type; }; @@ -83,4 +86,83 @@ struct LLRemovePointer< LLPointer > typedef SOMECLASS type; }; +namespace LL +{ + +/***************************************************************************** +* get_ref() +*****************************************************************************/ + template + struct GetRef + { + // return const ref or non-const ref, depending on whether we can bind + // a non-const lvalue ref to the argument + const auto& operator()(const T& obj) const { return obj; } + auto& operator()(T& obj) const { return obj; } + }; + + template + struct GetRef + { + const auto& operator()(const T* ptr) const { return *ptr; } + }; + + template + struct GetRef + { + auto& operator()(T* ptr) const { return *ptr; } + }; + + template + struct GetRef< LLPointer > + { + auto& operator()(LLPointer ptr) const { return *ptr; } + }; + + /// whether we're passed a pointer or a reference, return a reference + template + auto& get_ref(T& ptr_or_ref) + { + return GetRef::type>()(ptr_or_ref); + } + + template + const auto& get_ref(const T& ptr_or_ref) + { + return GetRef::type>()(ptr_or_ref); + } + +/***************************************************************************** +* get_ptr() +*****************************************************************************/ + // if T is any pointer type we recognize, return it unchanged + template + const T* get_ptr(const T* ptr) { return ptr; } + + template + T* get_ptr(T* ptr) { return ptr; } + + template + const std::shared_ptr& get_ptr(const std::shared_ptr& ptr) { return ptr; } + + template + const std::unique_ptr& get_ptr(const std::unique_ptr& ptr) { return ptr; } + + template + const boost::shared_ptr& get_ptr(const boost::shared_ptr& ptr) { return ptr; } + + template + const boost::intrusive_ptr& get_ptr(const boost::intrusive_ptr& ptr) { return ptr; } + + template + const LLPointer& get_ptr(const LLPointer& ptr) { return ptr; } + + // T is not any pointer type we recognize, take a pointer to the parameter + template + const T* get_ptr(const T& obj) { return &obj; } + + template + T* get_ptr(T& obj) { return &obj; } +} // namespace LL + #endif /* ! defined(LL_LLPTRTO_H) */ diff --git a/indra/llcommon/llsdutil.cpp b/indra/llcommon/llsdutil.cpp index f70bee9903..e98fc0285a 100644 --- a/indra/llcommon/llsdutil.cpp +++ b/indra/llcommon/llsdutil.cpp @@ -1046,3 +1046,38 @@ LLSD llsd_shallow(LLSD value, LLSD filter) return shallow; } + +LLSD LL::apply_llsd_fix(size_t arity, const LLSD& args) +{ + // LLSD supports a number of types, two of which are aggregates: Map and + // Array. We don't try to support Map: supporting Map would seem to + // promise that we could somehow match the string key to 'func's parameter + // names. Uh sorry, maybe in some future version of C++ with reflection. + if (args.isMap()) + { + LLTHROW(LL::apply_error("LL::apply(function, Map LLSD) unsupported")); + } + // We expect an LLSD array, but what the heck, treat isUndefined() as a + // zero-length array for calling a nullary 'func'. + if (args.isUndefined() || args.isArray()) + { + // this works because LLSD().size() == 0 + if (args.size() != arity) + { + LLTHROW(LL::apply_error(stringize("LL::apply(function(", arity, " args), ", + args.size(), "-entry LLSD array)"))); + } + return args; + } + + // args is one of the scalar types + // scalar_LLSD.size() == 0, so don't test that here. + // You can pass a scalar LLSD only to a unary 'func'. + if (arity != 1) + { + LLTHROW(LL::apply_error(stringize("LL::apply(function(", arity, " args), " + "LLSD ", LLSD::typeString(args.type()), ")"))); + } + // make an array of it + return llsd::array(args); +} diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h index eddaa64bd2..546e27930d 100644 --- a/indra/llcommon/llsdutil.h +++ b/indra/llcommon/llsdutil.h @@ -29,10 +29,12 @@ #ifndef LL_LLSDUTIL_H #define LL_LLSDUTIL_H +#include "apply.h" // LL::invoke() #include "llsd.h" #include -#include +#include #include +#include // U32 LL_COMMON_API LLSD ll_sd_from_U32(const U32); @@ -589,6 +591,10 @@ namespace LL /***************************************************************************** * apply(function, LLSD array) *****************************************************************************/ +// validate incoming LLSD blob, and return an LLSD array suitable to pass to +// apply_impl() +LLSD apply_llsd_fix(size_t arity, const LLSD& args); + // Derived from https://stackoverflow.com/a/20441189 // and https://en.cppreference.com/w/cpp/utility/apply . // We can't simply make a tuple from the LLSD array and then apply() that @@ -602,6 +608,16 @@ auto apply_impl(CALLABLE&& func, const LLSD& array, std::index_sequence) return std::forward(func)(LLSDParam(array[I])...); } +// use apply_n(function, LLSD) to call a specific arity of a variadic +// function with (that many) items from the passed LLSD array +template +auto apply_n(CALLABLE&& func, const LLSD& args) +{ + return apply_impl(std::forward(func), + apply_llsd_fix(ARITY, args), + std::make_index_sequence()); +} + /** * apply(function, LLSD) goes beyond C++17 std::apply(). For this case * @a function @emph cannot be variadic: the compiler must know at compile @@ -610,32 +626,11 @@ auto apply_impl(CALLABLE&& func, const LLSD& array, std::index_sequence) template auto apply(CALLABLE&& func, const LLSD& args) { - LLSD array; - constexpr auto arity = boost::function_traits::arity; - // LLSD supports a number of types, two of which are aggregates: Map and - // Array. We don't try to support Map: supporting Map would seem to - // promise that we could somehow match the string key to 'func's parameter - // names. Uh sorry, maybe in some future version of C++ with reflection. - assert(! args.isMap()); - // We expect an LLSD array, but what the heck, treat isUndefined() as a - // zero-length array for calling a nullary 'func'. - if (args.isUndefined() || args.isArray()) - { - // this works because LLSD().size() == 0 - assert(args.size() == arity); - array = args; - } - else // args is one of the scalar types - { - // scalar_LLSD.size() == 0, so don't test that here. - // You can pass a scalar LLSD only to a unary 'func'. - assert(arity == 1); - // make an array of it - array = llsd::array(args); - } - return apply_impl(std::forward(func), - array, - std::make_index_sequence()); + // infer arity from the definition of func + constexpr auto arity = boost::function_types::function_arity< + typename std::remove_reference::type>::value; + // now that we have a compile-time arity, apply_n() works + return apply_n(std::forward(func), args); } } // namespace LL diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index b38e47a773..f09dd63316 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -33,8 +33,6 @@ #include #include #include -#include -#define foreach BOOST_FOREACH #include @@ -206,7 +204,7 @@ struct Vars void methodnb(NPARAMSb) { std::ostringstream vbin; - foreach(U8 byte, bin) + for (U8 byte: bin) { vbin << std::hex << std::setfill('0') << std::setw(2) << unsigned(byte); } @@ -462,7 +460,7 @@ namespace tut debug("dft_array_full:\n", dft_array_full); // Partial defaults arrays. - foreach(LLSD::String a, ab) + for (LLSD::String a: ab) { LLSD::Integer partition(std::min(partial_offset, dft_array_full[a].size())); dft_array_partial[a] = @@ -472,7 +470,7 @@ namespace tut debug("dft_array_partial:\n", dft_array_partial); - foreach(LLSD::String a, ab) + for(LLSD::String a: ab) { // Generate full defaults maps by zipping (params, dft_array_full). dft_map_full[a] = zipmap(params[a], dft_array_full[a]); @@ -599,19 +597,14 @@ namespace tut { // Copy descs to a temp map of same type. DescMap forgotten(descs.begin(), descs.end()); - // LLEventDispatcher intentionally provides only const_iterator: - // since dereferencing that iterator generates values on the fly, - // it's meaningless to have a modifiable iterator. But since our - // 'work' object isn't const, by default BOOST_FOREACH() wants to - // use non-const iterators. Persuade it to use the const_iterator. - foreach(LLEventDispatcher::NameDesc nd, const_cast(work)) + for (LLEventDispatcher::NameDesc nd: work) { DescMap::iterator found = forgotten.find(nd.first); - ensure(STRINGIZE("LLEventDispatcher records function '" << nd.first - << "' we didn't enter"), + ensure(stringize("LLEventDispatcher records function '", nd.first, + "' we didn't enter"), found != forgotten.end()); - ensure_equals(STRINGIZE("LLEventDispatcher desc '" << nd.second << - "' doesn't match what we entered: '" << found->second << "'"), + ensure_equals(stringize("LLEventDispatcher desc '", nd.second, + "' doesn't match what we entered: '", found->second, "'"), nd.second, found->second); // found in our map the name from LLEventDispatcher, good, erase // our map entry @@ -622,26 +615,26 @@ namespace tut std::ostringstream out; out << "LLEventDispatcher failed to report"; const char* delim = ": "; - foreach(const DescMap::value_type& fme, forgotten) + for (const DescMap::value_type& fme: forgotten) { out << delim << fme.first; delim = ", "; } - ensure(out.str(), false); + throw failure(out.str()); } } Vars* varsfor(const std::string& name) { VarsMap::const_iterator found = funcvars.find(name); - ensure(STRINGIZE("No Vars* for " << name), found != funcvars.end()); - ensure(STRINGIZE("NULL Vars* for " << name), found->second); + ensure(stringize("No Vars* for ", name), found != funcvars.end()); + ensure(stringize("NULL Vars* for ", name), found->second); return found->second; } void ensure_has(const std::string& outer, const std::string& inner) { - ensure(STRINGIZE("'" << outer << "' does not contain '" << inner << "'").c_str(), + ensure(stringize("'", outer, "' does not contain '", inner, "'").c_str(), outer.find(inner) != std::string::npos); } @@ -689,7 +682,7 @@ namespace tut LLSD getMetadata(const std::string& name) { LLSD meta(work.getMetadata(name)); - ensure(STRINGIZE("No metadata for " << name), meta.isDefined()); + ensure(stringize("No metadata for ", name), meta.isDefined()); return meta; } @@ -869,12 +862,12 @@ namespace tut LLSD req(LLSD::emptyArray()); if (arity) req[arity - 1] = LLSD(); - foreach(LLSD nm, inArray(names)) + for (LLSD nm: inArray(names)) { LLSD metadata(getMetadata(nm)); ensure_equals("name mismatch", metadata["name"], nm); ensure_equals(metadata["desc"].asString(), descs[nm]); - ensure_equals(STRINGIZE("mismatched required for " << nm.asString()), + ensure_equals(stringize("mismatched required for ", nm.asString()), metadata["required"], req); ensure("should not have optional", metadata["optional"].isUndefined()); } @@ -932,8 +925,8 @@ namespace tut ensure_equals("mdft name", mdft, mmeta["name"]); ameta.erase("name"); mmeta.erase("name"); - ensure_equals(STRINGIZE("metadata for " << adft.asString() - << " vs. " << mdft.asString()), + ensure_equals(stringize("metadata for ", adft.asString(), + " vs. ", mdft.asString()), ameta, mmeta); } } @@ -949,7 +942,7 @@ namespace tut // params are required. Also maps containing left requirements for // partial defaults arrays. Also defaults maps from defaults arrays. LLSD allreq, leftreq, rightdft; - foreach(LLSD::String a, ab) + for (LLSD::String a: ab) { // The map in which all params are required uses params[a] as // keys, with all isUndefined() as values. We can accomplish that @@ -977,9 +970,9 @@ namespace tut // Generate maps containing parameter names not provided by the // dft_map_partial maps. LLSD skipreq(allreq); - foreach(LLSD::String a, ab) + for (LLSD::String a: ab) { - foreach(const MapEntry& me, inMap(dft_map_partial[a])) + for (const MapEntry& me: inMap(dft_map_partial[a])) { skipreq[a].erase(me.first); } @@ -1024,7 +1017,7 @@ namespace tut (llsd::array("freenb_map_mdft", "smethodnb_map_mdft", "methodnb_map_mdft"), llsd::array(LLSD::emptyMap(), dft_map_full["b"])))); // required, optional - foreach(LLSD grp, inArray(groups)) + for (LLSD grp: inArray(groups)) { // Internal structure of each group in 'groups': LLSD names(grp[0]); @@ -1037,14 +1030,14 @@ namespace tut optional); // Loop through 'names' - foreach(LLSD nm, inArray(names)) + for (LLSD nm: inArray(names)) { LLSD metadata(getMetadata(nm)); ensure_equals("name mismatch", metadata["name"], nm); ensure_equals(nm.asString(), metadata["desc"].asString(), descs[nm]); - ensure_equals(STRINGIZE(nm << " required mismatch"), + ensure_equals(stringize(nm, " required mismatch"), metadata["required"], required); - ensure_equals(STRINGIZE(nm << " optional mismatch"), + ensure_equals(stringize(nm, " optional mismatch"), metadata["optional"], optional); } } @@ -1107,7 +1100,7 @@ namespace tut // LLSD value matching 'required' according to llsd_matches() rules. LLSD matching(LLSDMap("d", 3.14)("array", llsd::array("answer", true, answer))); // Okay, walk through 'tests'. - foreach(const CallablesTriple& tr, tests) + for (const CallablesTriple& tr: tests) { // Should be able to pass 'answer' to Callables registered // without 'required'. @@ -1129,14 +1122,17 @@ namespace tut set_test_name("passing wrong args to (map | array)-style registrations"); // Pass scalar/map to array-style functions, scalar/array to map-style - // functions. As that validation happens well before we engage the - // argument magic, it seems pointless to repeat this with every - // variation: (free function | non-static method), (no | arbitrary) - // args. We should only need to engage it for one map-style - // registration and one array-style registration. - std::string array_exc("needs an args array"); - call_logerr("free0_array", 17, array_exc); - call_logerr("free0_array", LLSDMap("pi", 3.14), array_exc); + // functions. It seems pointless to repeat this with every variation: + // (free function | non-static method), (no | arbitrary) args. We + // should only need to engage it for one map-style registration and + // one array-style registration. + // Now that LLEventDispatcher has been extended to treat an LLSD + // scalar as a single-entry array, the error we expect in this case is + // that apply() is trying to pass that non-empty array to a nullary + // function. + call_logerr("free0_array", 17, "LL::apply"); + // similarly, apply() doesn't accept an LLSD Map + call_logerr("free0_array", LLSDMap("pi", 3.14), "unsupported"); std::string map_exc("needs a map"); call_logerr("free0_map", 17, map_exc); @@ -1178,15 +1174,21 @@ namespace tut template<> template<> void object::test<19>() { - set_test_name("call array-style functions with too-short arrays"); - // Could have two different too-short arrays, one for *na and one for - // *nb, but since they both take 5 params... + set_test_name("call array-style functions with wrong-length arrays"); + // Could have different wrong-length arrays for *na and for *nb, but + // since they both take 5 params... LLSD tooshort(llsd::array("this", "array", "too", "short")); - foreach(const LLSD& funcsab, inArray(array_funcs)) + LLSD toolong (llsd::array("this", "array", "is", "one", "too", "long")); + LLSD badargs (llsd::array(tooshort, toolong)); + for (const LLSD& toosomething: inArray(badargs)) { - foreach(const llsd::MapEntry& e, inMap(funcsab)) + for (const LLSD& funcsab: inArray(array_funcs)) { - call_logerr(e.second, tooshort, "requires more arguments"); + for (const llsd::MapEntry& e: inMap(funcsab)) + { + // apply() complains about wrong number of array entries + call_logerr(e.second, toosomething, "LL::apply"); + } } } } @@ -1206,40 +1208,25 @@ namespace tut LLDate("2011-02-03T15:07:00Z"), LLURI("http://secondlife.com"), binary))); - LLSD argsplus(args); - argsplus["a"].append("bogus"); - argsplus["b"].append("bogus"); LLSD expect; - foreach(LLSD::String a, ab) + for (LLSD::String a: ab) { expect[a] = zipmap(params[a], args[a]); } // Adjust expect["a"]["cp"] for special Vars::cp treatment. - expect["a"]["cp"] = std::string("'") + expect["a"]["cp"].asString() + "'"; + expect["a"]["cp"] = stringize("'", expect["a"]["cp"].asString(), "'"); debug("expect: ", expect); - // Use substantially the same logic for args and argsplus - LLSD argsarrays(llsd::array(args, argsplus)); - // So i==0 selects 'args', i==1 selects argsplus - for (LLSD::Integer i(0), iend(argsarrays.size()); i < iend; ++i) + for (const LLSD& funcsab: inArray(array_funcs)) { - foreach(const LLSD& funcsab, inArray(array_funcs)) + for (LLSD::String a: ab) { - foreach(LLSD::String a, ab) - { - // Reset the Vars instance before each call - Vars* vars(varsfor(funcsab[a])); - *vars = Vars(); - work(funcsab[a], argsarrays[i][a]); - ensure_llsd(STRINGIZE(funcsab[a].asString() << - ": expect[\"" << a << "\"] mismatch"), - vars->inspect(), expect[a], 7); // 7 bits ~= 2 decimal digits - - // TODO: in the i==1 or argsplus case, intercept LL_WARNS - // output? Even without that, using argsplus verifies that - // passing too many args isn't fatal; it works -- but - // would be nice to notice the warning too. - } + // Reset the Vars instance before each call + Vars* vars(varsfor(funcsab[a])); + *vars = Vars(); + work(funcsab[a], args[a]); + ensure_llsd(stringize(funcsab[a].asString(), ": expect[\"", a, "\"] mismatch"), + vars->inspect(), expect[a], 7); // 7 bits ~= 2 decimal digits } } } @@ -1267,7 +1254,7 @@ namespace tut ("a", llsd::array(false, 255, 98.6, 1024.5, "pointer")) ("b", llsd::array("object", LLUUID::generateNewID(), LLDate::now(), LLURI("http://wiki.lindenlab.com/wiki"), LLSD::Binary(boost::begin(binary), boost::end(binary))))); LLSD array_overfull(array_full); - foreach(LLSD::String a, ab) + for (LLSD::String a: ab) { array_overfull[a].append("bogus"); } @@ -1281,7 +1268,7 @@ namespace tut ensure_not_equals("UUID collision", array_full["b"][1].asUUID(), dft_array_full["b"][1].asUUID()); LLSD map_full, map_overfull; - foreach(LLSD::String a, ab) + for (LLSD::String a: ab) { map_full[a] = zipmap(params[a], array_full[a]); map_overfull[a] = map_full[a]; @@ -1324,15 +1311,15 @@ namespace tut LLSD argssets(llsd::array(array_full, array_overfull, map_full, map_overfull)); foreach(const LLSD& args, inArray(argssets)) { - foreach(LLSD::String a, ab) + for (LLSD::String a: ab) { - foreach(LLSD::String name, inArray(names[a])) + for (LLSD::String name: inArray(names[a])) { // Reset the Vars instance Vars* vars(varsfor(name)); *vars = Vars(); work(name, args[a]); - ensure_llsd(STRINGIZE(name << ": expect[\"" << a << "\"] mismatch"), + ensure_llsd(stringize(name, ": expect[\"", a, "\"] mismatch"), vars->inspect(), expect[a], 7); // 7 bits, 2 decimal digits // intercept LL_WARNS for the two overfull cases? } -- cgit v1.2.3 From 8ac35b626072bd94178861dbbaf7d835354c9765 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 22 Dec 2022 15:51:56 -0500 Subject: DRTVWR-558: Add apply_n(function, std::vector) for variadics. apply_n(function, LLSD array) has been useful, so for completeness, add the corresponding function for std::vector. Add a reference to apply_n() in comments for both apply() functions. (cherry picked from commit dfb63a92e0e9a419931caf5112e1f590924e0867) --- indra/llcommon/apply.h | 19 ++++++++++++++----- indra/llcommon/llsdutil.h | 3 ++- 2 files changed, 16 insertions(+), 6 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/apply.h b/indra/llcommon/apply.h index 357887dfd7..123813bcec 100644 --- a/indra/llcommon/apply.h +++ b/indra/llcommon/apply.h @@ -188,19 +188,28 @@ struct apply_error: public LLException apply_error(const std::string& what): LLException(what) {} }; +template +auto apply_n(CALLABLE&& func, const std::vector& args) +{ + apply_validate_size(args.size(), ARITY); + return apply_impl(std::forward(func), + args, + std::make_index_sequence()); +} + /** * apply(function, std::vector) goes beyond C++17 std::apply(). For this case * @a function @emph cannot be variadic: the compiler must know at compile - * time how many arguments to pass. This isn't Python. + * time how many arguments to pass. This isn't Python. (But see apply_n() to + * pass a specific number of args to a variadic function.) */ template auto apply(CALLABLE&& func, const std::vector& args) { + // infer arity from the definition of func constexpr auto arity = boost::function_traits::arity; - apply_validate_size(args.size(), arity); - return apply_impl(std::forward(func), - args, - std::make_index_sequence()); + // now that we have a compile-time arity, apply_n() works + return apply_n(std::forward(func), args); } } // namespace LL diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h index 546e27930d..baf4400768 100644 --- a/indra/llcommon/llsdutil.h +++ b/indra/llcommon/llsdutil.h @@ -621,7 +621,8 @@ auto apply_n(CALLABLE&& func, const LLSD& args) /** * apply(function, LLSD) goes beyond C++17 std::apply(). For this case * @a function @emph cannot be variadic: the compiler must know at compile - * time how many arguments to pass. This isn't Python. + * time how many arguments to pass. This isn't Python. (But see apply_n() to + * pass a specific number of args to a variadic function.) */ template auto apply(CALLABLE&& func, const LLSD& args) -- cgit v1.2.3 From a02ccbf1b81549cd44ff5088c94dbb5fc756fea0 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 6 Jan 2023 17:04:12 -0500 Subject: DRTVWR-558: Support LLStoreListener, add LLCaptureListener. LLStoreListener didn't work because of an ambiguity problem. Resolve that by introducing an internal storeTarget() method. Introduce LLCaptureListener that's both an LLStoreListener and the variable into which to capture the expected result. Introduce LLVarHolder to contain the variable, so we can guarantee the actual data member will be fully constructed by the time we want to pass it to the LLStoreListener base class. (cherry picked from commit a894703188a7755bb9acb897d6c31ae1af6efce0) --- indra/llcommon/lleventfilter.h | 47 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventfilter.h b/indra/llcommon/lleventfilter.h index 7613850fb2..1fb41e0297 100644 --- a/indra/llcommon/lleventfilter.h +++ b/indra/llcommon/lleventfilter.h @@ -435,16 +435,61 @@ public: // generic type-appropriate store through mTarget, construct an // LLSDParam and store that, thus engaging LLSDParam's custom // conversions. - mTarget = LLSDParam(llsd::drill(event, mPath)); + storeTarget(LLSDParam(llsd::drill(event, mPath))); return mConsume; } private: + // This method disambiguates LLStoreListener. Directly assigning + // some_LLSD_var = LLSDParam(some_LLSD_value); + // is problematic because the compiler has too many choices: LLSD has + // multiple assignment operator overloads, and LLSDParam has a + // templated conversion operator. But LLSDParam can convert to a + // (const LLSD&) parameter, and LLSD::operator=(const LLSD&) works. + void storeTarget(const T& value) + { + mTarget = value; + } + T& mTarget; const LLSD mPath; const bool mConsume; }; +/** + * LLVarHolder bundles a target variable of the specified type. We use it as a + * base class so the target variable will be fully constructed by the time a + * subclass constructor tries to pass a reference to some other base class. + */ +template +struct LLVarHolder +{ + T mVar; +}; + +/** + * LLCaptureListener isa LLStoreListener that bundles the target variable of + * interest. + */ +template +class LLCaptureListener: public LLVarHolder, + public LLStoreListener +{ +private: + using holder = LLVarHolder; + using super = LLStoreListener; + +public: + LLCaptureListener(const LLSD& path=LLSD(), bool consume=false): + super(*this, holder::mVar, path, consume) + {} + + void set(T&& newval=T()) { holder::mVar = std::forward(newval); } + + const T& get() const { return holder::mVar; } + operator const T&() { return holder::mVar; } +}; + /***************************************************************************** * LLEventLogProxy *****************************************************************************/ -- cgit v1.2.3 From 8e61a8f7c0ebcc72a1c348e790eeb73b9388ccde Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 6 Jan 2023 17:06:39 -0500 Subject: DRTVWR-558: Allow directly streaming test helper class CaptureLog. (cherry picked from commit 374eb409b98795158b36e232f670d1302f31b9ff) --- indra/llcommon/tests/wrapllerrs.h | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/wrapllerrs.h b/indra/llcommon/tests/wrapllerrs.h index 3779fb41bc..d657b329bb 100644 --- a/indra/llcommon/tests/wrapllerrs.h +++ b/indra/llcommon/tests/wrapllerrs.h @@ -226,6 +226,11 @@ public: return boost::dynamic_pointer_cast(mRecorder)->streamto(out); } + friend inline std::ostream& operator<<(std::ostream& out, const CaptureLog& self) + { + return self.streamto(out); + } + private: LLError::FatalFunction mFatalFunction; LLError::SettingsStoragePtr mOldSettings; -- cgit v1.2.3 From b2205bde52acf82575757f74a642c40b7433bf6b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 6 Jan 2023 19:58:12 -0500 Subject: DRTVWR-558: Clean up LLEventDispatcher argument and result handling. Add a new LLEventDispatcher constructor accepting not only the map key to extract a requested function name, but a second map key to extract the arguments -- when required. In Doxygen comments, clarify the difference between the two constructors. Move interaction with the LLEventPump subsystem to LLDispatchListener. LLEventDispatcher is intended to be directly called. On error, instead of looking for a "reply" key in the invocation LLSD, throw DispatchError. Publish DispatchError, formerly an implementation detail, and its new subclass DispatchMissing. Make both LLEventDispatcher::operator()() overloads return LLSD, leveraging the new internal ReturnLLSD logic that returns a degenerate LLSD blob for a void target callable and, for compatible types, converts the returned value to LLSD. Notably, the public try_call() overloads still return bool; any value returned by the target callable is discarded. Clarify the operator() and try_call() argument requirements for target callables registered to accept an LLSD array, in Doxygen comments and in code. In particular, the 'event' passed to (event) overloads (vs. the (name, event) overloads) must be an LLSD map, so it must contain an "args" key (or the new arguments map key specified to the constructor) containing the LLSD args array. Since the use of the new args key depends on whether the target callable is registered to accept an array or a map, pass it into DispatchEntry::call() (and all subclass overrides), along with a bool to disambiguate whether we reached that method from an LLEventDispatcher (event) invocation method or a (name, event) invocation method. Allow streaming an LLEventDispatcher instance to std::ostream, primarily to facilitate construction of proper error messages. Revert the 'name' argument of internal try_call(key, name, event) to std::string. Ditch try_call_log(), try_call_one() and reply(). Fold try_call_one() logic into three-argument try_call(). Refactor callFail() as a template method accepting both the exception to throw and arbitrary stringize() arguments from which to construct the exception message. Non-static callFail() implicitly prepends the instance and a colon to the rest of the arguments, and calls static sCallFail(). The latter constructs the exception message, logs it and throws the specified exception. This obviates try_call_log(). Make implementation detail helper class LLSDArgsMapper a private member of LLEventDispatcher so it can access sCallFail(): we now want all error handling to go through that method. Add LLSDArgsMapper::callFail() resembling LLSDEventDispatcher::callFail(), but without having to specify the exception: only LLEventDispatcher will throw anything but generic DispatchError. Give LLEventDispatcher::ParamsDispatchEntry and its subclasses ArrayParamsDispatchEntry and MapParamsDispatchEntry a new 'name' argument to identify error messages. Store it and use it implicitly in new callFail() method, very like LLSDArgsMapper::callFail(). Make LLEventDispatcher:: addArrayParamsDispatchEntry() and addMapParamsDispatchEntry() pass a 'name' that includes the LLEventDispatcher instance name as well as the name of the specific registered callable. This way we need not intercept a low-level error and annotate it with contextual data: we can just let the exception propagate. Make ParamsDispatchEntry::call() override catch LL::apply_error thrown by an invoker_function, and pass its message to callFail(), i.e. rethrow as LLEventDispatcher::DispatchError. Introduce ArrayParamsDispatchEntry::call() override for the special logic to extract an arguments array from a passed LLSD map -- but only under the circumstances described in the Doxygen comment. Add similar logic to MapParamsDispatchEntry::call(), but with both argskey itself and a value for argskey optional in the passed LLSD map. Because LLEventDispatcher now has two constructor overloads, allow subclass constructor LLDispatchListener() to accept zero or more trailing arguments. This is different than giving LLDispatchListener's constructor a default final argument, in that the subclass doesn't need to specify its default value: that's up to the base-class constructor. But it does require that the subclass constructor move to the header file. Move private LLEventDispatcher::reply() method to LLDispatchListener. Extend LLDispatchListener::process() to handle DispatchError by attempting to reply with a map containing an "error" key, per convention. (In other words, move that logic from LLEventDispatcher to LLDispatchListener.) Also, for a map LLSD result, attempt to reply with that result; for other defined LLSD types, attempt to reply with a map containing a "data" key. This is backwards compatible with previous behavior because all previous LLDispatchListener subclass methods returned void, which now produces an undefined LLSD blob, which we don't bother trying to send in reply. In lleventdispatcher_test.cpp, rework tut::lleventdispatcher_data::call_exc() yet again to catch DispatchError instead of listening for an LLEventPump reply event. Similarly, make call_logerr() catch DispatchError. Since the exception should also be logged, we ignore it and focus on the log, as before. Add tests <23> to <27>, exercising calls to new class DispatchResult methods returning string, int, LLSD map, LLSD array and void. (cherry picked from commit 2f9c915dd3d5137b5b2b1a57f0179e1f7a090f8c) --- indra/llcommon/lleventdispatcher.cpp | 428 +++++++++++++----------- indra/llcommon/lleventdispatcher.h | 166 +++++++-- indra/llcommon/tests/lleventdispatcher_test.cpp | 126 +++++-- 3 files changed, 463 insertions(+), 257 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index e7e73125a7..7e5723c503 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -43,20 +43,9 @@ #include "llexception.h" #include "llsdutil.h" #include "stringize.h" +#include // std::quoted() #include // std::auto_ptr -/***************************************************************************** -* DispatchError -*****************************************************************************/ -struct DispatchError: public LLException -{ - // template constructor involving strings passes all arguments to - // stringize() to construct LLException's what() string - template - DispatchError(const std::string& arg0, ARGS&&... args): - LLException(stringize(arg0, std::forward(args)...)) {} -}; - /***************************************************************************** * LLSDArgsMapper *****************************************************************************/ @@ -109,7 +98,7 @@ struct DispatchError: public LLException * - Holes are filled with the default values. * - Any remaining holes constitute an error. */ -class LL_COMMON_API LLSDArgsMapper +class LL_COMMON_API LLEventDispatcher::LLSDArgsMapper { public: /// Accept description of function: function name, param names, param @@ -122,6 +111,8 @@ public: private: static std::string formatlist(const LLSD&); + template + void callFail(ARGS&&... args) const; // The function-name string is purely descriptive. We want error messages // to be able to indicate which function's LLSDArgsMapper has the problem. @@ -141,15 +132,16 @@ private: FilledVector _has_dft; }; -LLSDArgsMapper::LLSDArgsMapper(const std::string& function, - const LLSD& names, const LLSD& defaults): +LLEventDispatcher::LLSDArgsMapper::LLSDArgsMapper(const std::string& function, + const LLSD& names, + const LLSD& defaults): _function(function), _names(names), _has_dft(names.size()) { if (! (_names.isUndefined() || _names.isArray())) { - LLTHROW(DispatchError(function, " names must be an array, not ", names)); + callFail(" names must be an array, not ", names); } auto nparams(_names.size()); // From _names generate _indexes. @@ -172,8 +164,7 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function, // defaults is a (possibly empty) array. Right-align it with names. if (ndefaults > nparams) { - LLTHROW(DispatchError(function, " names array ", names, - " shorter than defaults array ", defaults)); + callFail(" names array ", names, " shorter than defaults array ", defaults); } // Offset by which we slide defaults array right to right-align with @@ -210,23 +201,20 @@ LLSDArgsMapper::LLSDArgsMapper(const std::string& function, } if (bogus.size()) { - LLTHROW(DispatchError(function, " defaults specified for nonexistent params ", - formatlist(bogus))); + callFail(" defaults specified for nonexistent params ", formatlist(bogus)); } } else { - LLTHROW(DispatchError(function, " defaults must be a map or an array, not ", - defaults)); + callFail(" defaults must be a map or an array, not ", defaults); } } -LLSD LLSDArgsMapper::map(const LLSD& argsmap) const +LLSD LLEventDispatcher::LLSDArgsMapper::map(const LLSD& argsmap) const { if (! (argsmap.isUndefined() || argsmap.isMap() || argsmap.isArray())) { - LLTHROW(DispatchError(_function, " map() needs a map or array, not ", - argsmap)); + callFail(" map() needs a map or array, not ", argsmap); } // Initialize the args array. Indexing a non-const LLSD array grows it // to appropriate size, but we don't want to resize this one on each @@ -323,15 +311,14 @@ LLSD LLSDArgsMapper::map(const LLSD& argsmap) const // by argsmap, that's a problem. if (unfilled.size()) { - LLTHROW(DispatchError(_function, " missing required arguments ", - formatlist(unfilled), " from ", argsmap)); + callFail(" missing required arguments ", formatlist(unfilled), " from ", argsmap); } // done return args; } -std::string LLSDArgsMapper::formatlist(const LLSD& list) +std::string LLEventDispatcher::LLSDArgsMapper::formatlist(const LLSD& list) { std::ostringstream out; const char* delim = ""; @@ -344,14 +331,26 @@ std::string LLSDArgsMapper::formatlist(const LLSD& list) return out.str(); } +template +void LLEventDispatcher::LLSDArgsMapper::callFail(ARGS&&... args) const +{ + LLEventDispatcher::sCallFail + (_function, std::forward(args)...); +} + /***************************************************************************** * LLEventDispatcher *****************************************************************************/ LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key): + LLEventDispatcher(desc, key, "args") +{} + +LLEventDispatcher::LLEventDispatcher(const std::string& desc, const std::string& key, + const std::string& argskey): mDesc(desc), - mKey(key) -{ -} + mKey(key), + mArgskey(argskey) +{} LLEventDispatcher::~LLEventDispatcher() { @@ -375,20 +374,21 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE Callable mFunc; LLSD mRequired; - virtual LLSD call(const std::string& desc, const LLSD& event) const + LLSD call(const std::string& desc, const LLSD& event, bool, const std::string&) const override { // Validate the syntax of the event itself. std::string mismatch(llsd_matches(mRequired, event)); if (! mismatch.empty()) { - LLTHROW(DispatchError(desc, ": bad request: ", mismatch)); + LLEventDispatcher::sCallFail + (desc, ": bad request: ", mismatch); } // Event syntax looks good, go for it! mFunc(event); return {}; } - virtual LLSD addMetadata(LLSD meta) const + LLSD addMetadata(LLSD meta) const override { meta["required"] = mRequired; return meta; @@ -401,16 +401,35 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE */ struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::DispatchEntry { - ParamsDispatchEntry(const std::string& desc, const invoker_function& func): + ParamsDispatchEntry(const std::string& name, const std::string& desc, + const invoker_function& func): DispatchEntry(desc), + mName(name), mInvoker(func) {} + std::string mName; invoker_function mInvoker; - virtual LLSD call(const std::string&, const LLSD& event) const + LLSD call(const std::string&, const LLSD& event, bool, const std::string&) const override + { + try + { + return mInvoker(event); + } + catch (const LL::apply_error& err) + { + // could hit runtime errors with LL::apply() + return callFail(err.what()); + } + } + + template + LLSD callFail(ARGS&&... args) const { - return mInvoker(event); + LLEventDispatcher::sCallFail(mName, ": ", std::forward(args)...); + // pacify the compiler + return {}; } }; @@ -420,15 +439,48 @@ struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::Dispatc */ struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry { - ArrayParamsDispatchEntry(const std::string& desc, const invoker_function& func, - LLSD::Integer arity): - ParamsDispatchEntry(desc, func), + ArrayParamsDispatchEntry(const std::string& name, const std::string& desc, + const invoker_function& func, LLSD::Integer arity): + ParamsDispatchEntry(name, desc, func), mArity(arity) {} LLSD::Integer mArity; - virtual LLSD addMetadata(LLSD meta) const + LLSD call(const std::string& desc, const LLSD& event, bool fromMap, const std::string& argskey) const override + { +// std::string context { stringize(desc, "(", event, ") with argskey ", std::quoted(argskey), ": ") }; + // Whether we try to extract arguments from 'event' depends on whether + // the LLEventDispatcher consumer called one of the (name, event) + // methods (! fromMap) or one of the (event) methods (fromMap). If we + // were called with (name, event), the passed event must itself be + // suitable to pass to the registered callable, no args extraction + // required or even attempted. Only if called with plain (event) do we + // consider extracting args from that event. Initially assume 'event' + // itself contains the arguments. + LLSD args{ event }; + if (fromMap) + { + if (mArity) + { + // We only require/retrieve argskey if the target function + // isn't nullary. For all others, since we require an LLSD + // array, we must have an argskey. + if (argskey.empty()) + { + return callFail("LLEventDispatcher has no args key"); + } + if ((! event.has(argskey))) + { + return callFail("missing required key ", std::quoted(argskey)); + } + args = event[argskey]; + } + } + return ParamsDispatchEntry::call(desc, args, fromMap, argskey); + } + + LLSD addMetadata(LLSD meta) const override { LLSD array(LLSD::emptyArray()); // Resize to number of arguments required @@ -449,7 +501,7 @@ struct LLEventDispatcher::MapParamsDispatchEntry: public LLEventDispatcher::Para MapParamsDispatchEntry(const std::string& name, const std::string& desc, const invoker_function& func, const LLSD& params, const LLSD& defaults): - ParamsDispatchEntry(desc, func), + ParamsDispatchEntry(name, desc, func), mMapper(name, params, defaults), mRequired(LLSD::emptyMap()) { @@ -493,14 +545,25 @@ struct LLEventDispatcher::MapParamsDispatchEntry: public LLEventDispatcher::Para LLSD mRequired; LLSD mOptional; - virtual LLSD call(const std::string& desc, const LLSD& event) const + LLSD call(const std::string& desc, const LLSD& event, bool fromMap, const std::string& argskey) const override { - // Just convert from LLSD::Map to LLSD::Array using mMapper, then pass - // to base-class call() method. - return ParamsDispatchEntry::call(desc, mMapper.map(event)); + // by default, pass the whole event as the arguments map + LLSD args{ event }; + // Were we called by one of the (event) methods (instead of the (name, + // event) methods), do we have an argskey, and does the incoming event + // have that key? + if (fromMap && (! argskey.empty()) && event.has(argskey)) + { + // if so, extract the value of argskey from the incoming event, + // and use that as the arguments map + args = event[argskey]; + } + // Now convert args from LLSD map to LLSD array using mMapper, then + // pass to base-class call() method. + return ParamsDispatchEntry::call(desc, mMapper.map(args), fromMap, argskey); } - virtual LLSD addMetadata(LLSD meta) const + LLSD addMetadata(LLSD meta) const override { meta["required"] = mRequired; meta["optional"] = mOptional; @@ -513,7 +576,11 @@ void LLEventDispatcher::addArrayParamsDispatchEntry(const std::string& name, const invoker_function& invoker, LLSD::Integer arity) { - mDispatch.emplace(name, new ArrayParamsDispatchEntry(desc, invoker, arity)); + // The first parameter to ArrayParamsDispatchEntry is solely for error + // messages. Identify our instance and this entry. + mDispatch.emplace( + name, + new ArrayParamsDispatchEntry(stringize(*this, '[', name, ']'), desc, invoker, arity)); } void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name, @@ -522,7 +589,11 @@ void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name, const LLSD& params, const LLSD& defaults) { - mDispatch.emplace(name, new MapParamsDispatchEntry(name, desc, invoker, params, defaults)); + // Pass instance info as well as this entry name for error messages. + mDispatch.emplace( + name, + new MapParamsDispatchEntry(stringize(*this, '[', name, ']'), + desc, invoker, params, defaults)); } /// Register a callable by name @@ -546,174 +617,101 @@ bool LLEventDispatcher::remove(const std::string& name) /// Call a registered callable with an explicitly-specified name. It is an /// error if no such callable exists. -void LLEventDispatcher::operator()(const std::string& name, const LLSD& event) const +LLSD LLEventDispatcher::operator()(const std::string& name, const LLSD& event) const { - std::string error{ try_call_log(std::string(), name, event) }; - if (! error.empty()) - { - callFail(event, error); - } + return try_call(std::string(), name, event); } -/// Extract the @a key value from the incoming @a event, and call the callable -/// whose name is specified by that map @a key. It is an error if no such -/// callable exists. -void LLEventDispatcher::operator()(const LLSD& event) const +bool LLEventDispatcher::try_call(const std::string& name, const LLSD& event) const { - std::string error{ try_call_log(mKey, event[mKey], event) }; - if (! error.empty()) + try { - callFail(event, error); + try_call(std::string(), name, event); + return true; } -} - -void LLEventDispatcher::callFail(const LLSD& event, const std::string& msg) const -{ - // pass back a response that includes an "error" key with the message. - reply(llsd::map("error", msg), event); -} - -void LLEventDispatcher::reply(const LLSD& response, const LLSD& event) const -{ - static LLSD::String key{ "reply" }; - if (event.has(key)) + // Note that we don't catch the generic DispatchError, only the specific + // DispatchMissing. try_call() only promises to return false if the + // specified callable name isn't found -- not for general errors. + catch (const DispatchMissing&) { - // Oh good, the incoming event specifies a reply pump -- pass back - // our response. - sendReply(response, event, key); + return false; } } -bool LLEventDispatcher::try_call(const LLSD& event) const -{ - return try_call_log(mKey, event[mKey], event).empty(); -} - -/*==========================================================================*| - TODO: - -* When mInvoker returns result.isDefined(), sendReply(llsd::map("data", result)) -* When try_call finds name.isArray(), construct response array from - dispatching each call, sendReply() as above -* When try_call finds name.isMap(), construct response map from dispatching - each call, sendReply() as above -- note, caller can't care about order -* Possible future transactional behavior: look up all names before calling any - -|*==========================================================================*/ -bool LLEventDispatcher::try_call(const std::string& name, const LLSD& event) const +/// Extract the @a key value from the incoming @a event, and call the callable +/// whose name is specified by that map @a key. It is an error if no such +/// callable exists. +LLSD LLEventDispatcher::operator()(const LLSD& event) const { - return try_call_log(std::string(), name, event).empty(); + return try_call(mKey, event[mKey], event); } -std::string LLEventDispatcher::try_call_log(const std::string& key, const LLSD& name, - const LLSD& event) const +bool LLEventDispatcher::try_call(const LLSD& event) const { - std::string error{ try_call(key, name, event) }; - if (! error.empty()) + try { - // If we're a subclass of LLEventDispatcher, e.g. LLEventAPI, report that. - error = stringize(LLError::Log::classname(this), "(", mDesc, "): ", error); - LL_WARNS("LLEventDispatcher") << error << LL_ENDL; + try_call(mKey, event[mKey], event); + return true; + } + catch (const DispatchMissing&) + { + return false; } - return error; } -// This internal method returns empty string if the call succeeded, else -// non-empty error message. -std::string LLEventDispatcher::try_call(const std::string& key, const LLSD& name, - const LLSD& event) const +LLSD LLEventDispatcher::try_call(const std::string& key, const std::string& name, + const LLSD& event) const { - if (name.isUndefined()) + if (name.empty()) { if (key.empty()) { - return "attempting to call with no name"; + callFail("attempting to call with no name"); } else { - return stringize("no ", key); - } - } - else if (name.isArray()) - { - return stringize(key, " array dispatch ", name, " not yet implemented"); - } - else if (name.isMap()) - { - return stringize(key, " map dispatch ", name, " not yet implemented"); - } - else if (! name.isString()) - { - return stringize(key, " bad type ", LLSD::typeString(name.type()), ' ', name, - " -- function names are String"); - } - else // name is an LLSD::String - { - auto success{ try_call_one(key, name, event) }; - // pretend to unpack - std::string& error{ success.first }; - LLSD& result{ success.second }; - // did try_call_one() report an error? - if (! error.empty()) - { - // if we have a reply key, respond to invoker - reply(llsd::map("error", error), event); - // now tell caller - return error; - } - // try_call_one() succeeded in calling the target function -- - // should we reply to invoker? - if (result.isUndefined()) - { - // We would get result.isUndefined() if the target function has - // void return. In any case, even if the target function returns - // LLSD, isUndefined() means "don't bother sending response." - return {}; - } - // result.isDefined(): the target function returned something. - // Respond to invoker if we have a "reply" key. - if (! result.isMap()) - { - // wrap result in a map to play well with sendReply() - result = llsd::map("data", result); + callFail("no ", key); } - reply(result, event); - return {}; } -} -std::pair -LLEventDispatcher::try_call_one(const std::string& key, const std::string& name, - const LLSD& event) const -{ DispatchMap::const_iterator found = mDispatch.find(name); if (found == mDispatch.end()) { + // Here we were passed a valid name, but there's no registered + // callable with that name. This is the one case in which we throw + // DispatchMissing instead of the generic DispatchError. + // Distinguish the public method by which our caller reached here: + // key.empty() means the name was passed explicitly, non-empty means + // we extracted the name from the incoming event using that key. if (key.empty()) { - return { stringize("'", name, "' not found"), {} }; + callFail(std::quoted(name), " not found"); } else { - return { stringize("bad ", key, " value '", name, "'"), {} }; + callFail("bad ", key, " value ", std::quoted(name)); } } - try - { - // Found the name, so it's plausible to even attempt the call. - return { {}, found->second->call(stringize("calling '", name, "'"), event) }; - } - catch (const DispatchError& err) - { - // trouble preparing arguments - return { err.what(), {} }; - } - catch (const LL::apply_error& err) - { - // could also hit runtime errors with LL::apply() - return { err.what(), {} }; - } + // Found the name, so it's plausible to even attempt the call. + return found->second->call(stringize(*this, " calling ", std::quoted(name)), + event, (! key.empty()), mArgskey); +} + +template +//static +void LLEventDispatcher::sCallFail(ARGS&&... args) +{ + auto error = stringize(std::forward(args)...); + LL_WARNS("LLEventDispatcher") << error << LL_ENDL; + LLTHROW(EXCEPTION(error)); +} + +template +void LLEventDispatcher::callFail(ARGS&&... args) const +{ + // Describe this instance in addition to the error itself. + sCallFail(*this, ": ", std::forward(args)...); } LLSD LLEventDispatcher::getMetadata(const std::string& name) const @@ -729,27 +727,69 @@ LLSD LLEventDispatcher::getMetadata(const std::string& name) const return found->second->addMetadata(meta); } +std::ostream& operator<<(std::ostream& out, const LLEventDispatcher& self) +{ + // If we're a subclass of LLEventDispatcher, e.g. LLEventAPI, report that. + return out << LLError::Log::classname(self) << '(' << self.mDesc << ')'; +} + /***************************************************************************** * LLDispatchListener *****************************************************************************/ -LLDispatchListener::LLDispatchListener(const std::string& pumpname, const std::string& key): - LLEventDispatcher(pumpname, key), - // Do NOT tweak the passed pumpname. In practice, when someone - // instantiates a subclass of our LLEventAPI subclass, they intend to - // claim that LLEventPump name in the global LLEventPumps namespace. It - // would be mysterious and distressing if we allowed name tweaking, and - // someone else claimed pumpname first for a completely unrelated - // LLEventPump. Posted events would never reach our subclass listener - // because we would have silently changed its name; meanwhile listeners - // (if any) on that other LLEventPump would be confused by the events - // intended for our subclass. - LLEventStream(pumpname, false), - mBoundListener(listen("self", [this](const LLSD& event){ return process(event); })) +/*==========================================================================*| + TODO: +* When process() finds name.isArray(), construct response array from + dispatching each call -- args must also be (array of args structures) + (could also construct response map, IF array contains unique names) +* When process() finds name.isMap(), construct response map from dispatching + each call -- value of each key is its args struct -- argskey ignored -- + note, caller can't care about order +* Possible future transactional behavior: look up all names before calling any +|*==========================================================================*/ +std::string LLDispatchListener::mReplyKey{ "reply" }; + +bool LLDispatchListener::process(const LLSD& event) const { + LLSD result; + try + { + result = (*this)(event); + } + catch (const DispatchError& err) + { + // If the incoming event contains a "reply" key, we'll respond to the + // invoker with an error message (below). But if not -- silently + // ignoring an invocation request would be confusing at best. Escalate. + if (! event.has(mReplyKey)) + { + throw; + } + + // reply with a map containing an "error" key explaining the problem + reply(llsd::map("error", err.what()), event); + return false; + } + + // We seem to have gotten a valid result. But we don't know whether the + // registered callable is void or non-void. If it's void, + // LLEventDispatcher will return isUndefined(). Otherwise, try to send it + // back to our invoker. + if (result.isDefined()) + { + if (! result.isMap()) + { + // wrap the result in a map as the "data" key + result = llsd::map("data", result); + } + reply(result, event); + } + return false; } -bool LLDispatchListener::process(const LLSD& event) +void LLDispatchListener::reply(const LLSD& reply, const LLSD& request) const { - (*this)(event); - return false; + // Call sendReply() unconditionally: sendReply() itself tests whether the + // specified reply key is present in the incoming request, and does + // nothing if there's no such key. + sendReply(reply, request, mReplyKey); } diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index cebce618df..494fc6a366 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -57,7 +57,20 @@ class LLSD; class LL_COMMON_API LLEventDispatcher { public: + /** + * Pass description and the LLSD key used by try_call(const LLSD&) and + * operator()(const LLSD&) to extract the name of the registered callable + * to invoke. + */ LLEventDispatcher(const std::string& desc, const std::string& key); + /** + * Pass description, the LLSD key used by try_call(const LLSD&) and + * operator()(const LLSD&) to extract the name of the registered callable + * to invoke, and the LLSD key used by try_call(const LLSD&) and + * operator()(const LLSD&) to extract arguments LLSD. + */ + LLEventDispatcher(const std::string& desc, const std::string& key, + const std::string& argskey); virtual ~LLEventDispatcher(); /// @name Register functions accepting(const LLSD&) @@ -146,6 +159,8 @@ public: * boost::lambda::var(instance) or boost::lambda::constant(instance_ptr) * produce suitable callables. * + * TODO: variant accepting a method of the containing class, no getter. + * * When calling this name, pass an LLSD::Array. Each entry in turn will be * converted to the corresponding parameter type using LLSDParam. */ @@ -185,6 +200,8 @@ public: * boost::lambda::var(instance) or boost::lambda::constant(instance_ptr) * produce suitable callables. * + * TODO: variant accepting a method of the containing class, no getter. + * * Pass an LLSD::Array of parameter names, and optionally another * LLSD::Array of default parameter values, a la LLSDArgsMapper. * @@ -206,28 +223,82 @@ public: /// Unregister a callable bool remove(const std::string& name); - /// Call a registered callable with an explicitly-specified name. It is an - /// error if no such callable exists. It is an error if the @a event fails - /// to match the @a required prototype specified at add() time. - void operator()(const std::string& name, const LLSD& event) const; + /// Exception if an attempted call fails for any reason + struct DispatchError: public LLException + { + DispatchError(const std::string& what): LLException(what) {} + }; + + /// Specific exception for an attempt to call a nonexistent name + struct DispatchMissing: public DispatchError + { + DispatchMissing(const std::string& what): DispatchError(what) {} + }; + + /** + * Call a registered callable with an explicitly-specified name, + * converting its return value to LLSD (undefined for a void callable). + * It is an error if no such callable exists. It is an error if the @a + * event fails to match the @a required prototype specified at add() + * time. + * + * @a event must be an LLSD array for a callable registered to accept its + * arguments from such an array. It must be an LLSD map for a callable + * registered to accept its arguments from such a map. + */ + LLSD operator()(const std::string& name, const LLSD& event) const; - /// Call a registered callable with an explicitly-specified name and - /// return true. If no such callable exists, return - /// false. It is an error if the @a event fails to match the @a - /// required prototype specified at add() time. + /** + * Call a registered callable with an explicitly-specified name and + * return true. If no such callable exists, return + * false. It is an error if the @a event fails to match the @a + * required prototype specified at add() time. + * + * @a event must be an LLSD array for a callable registered to accept its + * arguments from such an array. It must be an LLSD map for a callable + * registered to accept its arguments from such a map. + */ bool try_call(const std::string& name, const LLSD& event) const; - /// Extract the @a key value from the incoming @a event, and call the - /// callable whose name is specified by that map @a key. It is an error if - /// no such callable exists. It is an error if the @a event fails to match - /// the @a required prototype specified at add() time. - void operator()(const LLSD& event) const; - - /// Extract the @a key value from the incoming @a event, call the callable - /// whose name is specified by that map @a key and return true. - /// If no such callable exists, return false. It is an error if - /// the @a event fails to match the @a required prototype specified at - /// add() time. + /** + * Extract the @a key specified to our constructor from the incoming LLSD + * map @a event, and call the callable whose name is specified by that @a + * key's value, converting its return value to LLSD (undefined for a void + * callable). It is an error if no such callable exists. It is an error if + * the @a event fails to match the @a required prototype specified at + * add() time. + * + * For a (non-nullary) callable registered to accept its arguments from an + * LLSD array, the @a event map must contain the key @a argskey specified to + * our constructor. The value of the @a argskey key must be an LLSD array + * containing the arguments to pass to the callable named by @a key. + * + * For a callable registered to accept its arguments from an LLSD map, if + * the @a event map contains the key @a argskey specified our constructor, + * extract the value of the @a argskey key and use it as the arguments map. + * If @a event contains no @a argskey key, use the whole @a event as the + * arguments map. + */ + LLSD operator()(const LLSD& event) const; + + /** + * Extract the @a key specified to our constructor from the incoming LLSD + * map @a event, call the callable whose name is specified by that @a + * key's value and return true. If no such callable exists, + * return false. It is an error if the @a event fails to match + * the @a required prototype specified at add() time. + * + * For a (non-nullary) callable registered to accept its arguments from an + * LLSD array, the @a event map must contain the key @a argskey specified to + * our constructor. The value of the @a argskey key must be an LLSD array + * containing the arguments to pass to the callable named by @a key. + * + * For a callable registered to accept its arguments from an LLSD map, if + * the @a event map contains the key @a argskey specified our constructor, + * extract the value of the @a argskey key and use it as the arguments map. + * If @a event contains no @a argskey key, use the whole @a event as the + * arguments map. + */ bool try_call(const LLSD& event) const; /// @name Iterate over defined names @@ -242,7 +313,8 @@ private: std::string mDesc; - virtual LLSD call(const std::string& desc, const LLSD& event) const = 0; + virtual LLSD call(const std::string& desc, const LLSD& event, + bool fromMap, const std::string& argskey) const = 0; virtual LLSD addMetadata(LLSD) const = 0; }; typedef std::map > DispatchMap; @@ -269,6 +341,9 @@ public: /// Retrieve the LLSD key we use for one-arg operator() method std::string getDispatchKey() const { return mKey; } + /// description of this instance's leaf class and description + friend std::ostream& operator<<(std::ostream&, const LLEventDispatcher&); + private: template void addMethod(const std::string& name, const std::string& desc, @@ -285,19 +360,16 @@ private: } } void addFail(const std::string& name, const std::string& classname) const; - std::string try_call_log(const std::string& key, const LLSD& name, - const LLSD& event) const; - std::string try_call(const std::string& key, const LLSD& name, - const LLSD& event) const; - // returns either (empty string, LLSD) or (error message, isUndefined) - std::pair - try_call_one(const std::string& key, const std::string& name, const LLSD& event) const; - // Implement "it is an error" semantics for attempted call operations: if - // the incoming event includes a "reply" key, log and send an error reply. - void callFail(const LLSD& event, const std::string& msg) const; - void reply(const LLSD& response, const LLSD& event) const; - - std::string mDesc, mKey; + LLSD try_call(const std::string& key, const std::string& name, + const LLSD& event) const; + // raise specified EXCEPTION with specified stringize(ARGS) + template + void callFail(ARGS&&... args) const; + template + static + void sCallFail(ARGS&&... args); + + std::string mDesc, mKey, mArgskey; DispatchMap mDispatch; static NameDesc makeNameDesc(const DispatchMap::value_type& item) @@ -305,6 +377,7 @@ private: return NameDesc(item.first, item.second->mDesc); } + class LLSDArgsMapper; struct LLSDDispatchEntry; struct ParamsDispatchEntry; struct ArrayParamsDispatchEntry; @@ -459,13 +532,36 @@ class LL_COMMON_API LLDispatchListener: public LLEventStream { public: - LLDispatchListener(const std::string& pumpname, const std::string& key); + template + LLDispatchListener(const std::string& pumpname, const std::string& key, + ARGS&&... args); virtual ~LLDispatchListener() {} private: - bool process(const LLSD& event); + bool process(const LLSD& event) const; + void reply(const LLSD& reply, const LLSD& request) const; LLTempBoundListener mBoundListener; + static std::string mReplyKey; }; +template +LLDispatchListener::LLDispatchListener(const std::string& pumpname, const std::string& key, + ARGS&&... args): + // pass through any additional arguments to LLEventDispatcher ctor + LLEventDispatcher(pumpname, key, std::forward(args)...), + // Do NOT tweak the passed pumpname. In practice, when someone + // instantiates a subclass of our LLEventAPI subclass, they intend to + // claim that LLEventPump name in the global LLEventPumps namespace. It + // would be mysterious and distressing if we allowed name tweaking, and + // someone else claimed pumpname first for a completely unrelated + // LLEventPump. Posted events would never reach our subclass listener + // because we would have silently changed its name; meanwhile listeners + // (if any) on that other LLEventPump would be confused by the events + // intended for our subclass. + LLEventStream(pumpname, false), + mBoundListener(listen("self", [this](const LLSD& event){ return process(event); })) +{ +} + #endif /* ! defined(LL_LLEVENTDISPATCHER_H) */ diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index f09dd63316..00bdff89e5 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -18,6 +18,7 @@ // external library headers // other Linden headers #include "../test/lltut.h" +#include "lleventfilter.h" #include "llsd.h" #include "llsdutil.h" #include "llevents.h" @@ -640,42 +641,38 @@ namespace tut std::string call_exc(const std::string& func, const LLSD& args, const std::string& exc_frag) { - // This method was written when LLEventDispatcher responded to - // name or argument errors with LL_ERRS, hence the name: we used - // to have to intercept LL_ERRS by making it throw. Now we set up - // to catch an error response instead. But -- for that we need to - // be able to sneak a "reply" key into args, which must be a Map. - if (! (args.isUndefined() or args.isMap())) - fail(stringize("can't test call_exc() with ", args)); - LLEventStream replypump("reply"); - LLSD reply; - LLTempBoundListener bound{ - replypump.listen( - "listener", - [&reply](const LLSD& event) - { - reply = event; - return false; - }) }; - LLSD modargs{ args }; - modargs["reply"] = replypump.getName(); - if (func.empty()) + std::string what; + try { - work(modargs); + if (func.empty()) + { + work(args); + } + else + { + work(func, args); + } } - else + catch (const LLEventDispatcher::DispatchError& err) { - work(func, modargs); + what = err.what(); } - ensure("no error response", reply.has("error")); - ensure_has(reply["error"], exc_frag); - return reply["error"]; + ensure_has(what, exc_frag); + return what; } void call_logerr(const std::string& func, const LLSD& args, const std::string& frag) { CaptureLog capture; - work(func, args); + try + { + work(func, args); + } + catch (const LLEventDispatcher::DispatchError& err) + { + // the error should also have been logged; we just need to + // stop the exception propagating + } capture.messageWith(frag); } @@ -1017,6 +1014,10 @@ namespace tut (llsd::array("freenb_map_mdft", "smethodnb_map_mdft", "methodnb_map_mdft"), llsd::array(LLSD::emptyMap(), dft_map_full["b"])))); // required, optional + llsd::array // group + (llsd::array("freenb_map_mdft", "smethodnb_map_mdft", "methodnb_map_mdft"), + llsd::array(LLSD::emptyMap(), dft_map_full["b"])))); // required, optional + for (LLSD grp: inArray(groups)) { // Internal structure of each group in 'groups': @@ -1196,7 +1197,7 @@ namespace tut template<> template<> void object::test<20>() { - set_test_name("call array-style functions with (just right | too long) arrays"); + set_test_name("call array-style functions with right-size arrays"); std::vector binary; for (size_t h(0x01), i(0); i < 5; h+= 0x22, ++i) { @@ -1326,4 +1327,73 @@ namespace tut } } } + + struct DispatchResult: public LLEventDispatcher + { + using DR = DispatchResult; + + DispatchResult(): LLEventDispatcher("expect result", "op") + { + // As of 2022-12-22, LLEventDispatcher's shorthand add() methods + // for pointer-to-method of same instance only support methods + // with signature void(const LLSD&). The generic add(pointer-to- + // method) requires an instance getter. + add("strfunc", "return string", &DR::strfunc, [this](){ return this; }); + add("voidfunc", "void function", &DR::voidfunc, [this](){ return this; }); + add("intfunc", "return Integer LLSD", &DR::intfunc, [this](){ return this; }); + add("mapfunc", "return map LLSD", &DR::mapfunc, [this](){ return this; }); + add("arrayfunc", "return array LLSD", &DR::arrayfunc, [this](){ return this; }); + } + + std::string strfunc(const LLSD&) const { return "a string"; } + void voidfunc() const {} + int intfunc(const LLSD&) const { return 17; } + LLSD mapfunc(const LLSD&) const { return llsd::map("key", "value"); } + LLSD arrayfunc(const LLSD&) const { return llsd::array("a", "b", "c"); } + }; + + template<> template<> + void object::test<23>() + { + set_test_name("string result"); + DispatchResult service; + LLSD result{ service("strfunc", "ignored") }; + ensure_equals("strfunc() mismatch", result.asString(), "a string"); + } + + template<> template<> + void object::test<24>() + { + set_test_name("void result"); + DispatchResult service; + LLSD result{ service("voidfunc", LLSD()) }; + ensure("voidfunc() returned defined", result.isUndefined()); + } + + template<> template<> + void object::test<25>() + { + set_test_name("Integer result"); + DispatchResult service; + LLSD result{ service("intfunc", "ignored") }; + ensure_equals("intfunc() mismatch", result.asInteger(), 17); + } + + template<> template<> + void object::test<26>() + { + set_test_name("map LLSD result"); + DispatchResult service; + LLSD result{ service("mapfunc", "ignored") }; + ensure_equals("mapfunc() mismatch", result, llsd::map("key", "value")); + } + + template<> template<> + void object::test<27>() + { + set_test_name("array LLSD result"); + DispatchResult service; + LLSD result{ service("arrayfunc", "ignored") }; + ensure_equals("arrayfunc() mismatch", result, llsd::array("a", "b", "c")); + } } // namespace tut -- cgit v1.2.3 From 9553965ad661b2753d13fa9b414f529ad440000f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 10 Jan 2023 17:38:01 -0500 Subject: DRTVWR-558: Add tests for LLDispatchListener functionality. Refine the special case of calling a nullary target function from an (event) method, notably via LLDispatchListener. (cherry picked from commit edcc52a9f60b1ec9b8f53603d6e2676558d41294) --- indra/llcommon/lleventdispatcher.cpp | 8 +- indra/llcommon/tests/lleventdispatcher_test.cpp | 152 ++++++++++++++++++------ 2 files changed, 123 insertions(+), 37 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index 7e5723c503..7abdc8f57a 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -461,7 +461,13 @@ struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::Pa LLSD args{ event }; if (fromMap) { - if (mArity) + if (! mArity) + { + // When the target function is nullary, and we're called from + // an (event) method, just ignore the rest of the map entries. + args.clear(); + } + else { // We only require/retrieve argskey if the target function // isn't nullary. For all others, since we require an LLSD diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 00bdff89e5..179fab9fad 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -23,6 +23,7 @@ #include "llsdutil.h" #include "llevents.h" #include "stringize.h" +#include "StringVec.h" #include "tests/wrapllerrs.h" #include "../test/catch_and_store_what_in.h" #include "../test/debug.h" @@ -315,6 +316,31 @@ void freenb(NPARAMSb) *****************************************************************************/ namespace tut { + void ensure_has(const std::string& outer, const std::string& inner) + { + ensure(stringize("'", outer, "' does not contain '", inner, "'"), + outer.find(inner) != std::string::npos); + } + + template + std::string call_exc(CALLABLE&& func, const std::string& exc_frag) + { + std::string what = + catch_what(std::forward(func)); + ensure_has(what, exc_frag); + return what; + } + + template + void call_logerr(CALLABLE&& func, const std::string& frag) + { + CaptureLog capture; + // the error should be logged; we just need to stop the exception + // propagating + catch_what(std::forward(func)); + capture.messageWith(frag); + } + struct lleventdispatcher_data { Debug debug{"test"}; @@ -633,47 +659,26 @@ namespace tut return found->second; } - void ensure_has(const std::string& outer, const std::string& inner) - { - ensure(stringize("'", outer, "' does not contain '", inner, "'").c_str(), - outer.find(inner) != std::string::npos); - } - std::string call_exc(const std::string& func, const LLSD& args, const std::string& exc_frag) { - std::string what; - try - { - if (func.empty()) + return tut::call_exc( + [this, func, args]() { - work(args); - } - else - { - work(func, args); - } - } - catch (const LLEventDispatcher::DispatchError& err) - { - what = err.what(); - } - ensure_has(what, exc_frag); - return what; + if (func.empty()) + { + work(args); + } + else + { + work(func, args); + } + }, + exc_frag); } void call_logerr(const std::string& func, const LLSD& args, const std::string& frag) { - CaptureLog capture; - try - { - work(func, args); - } - catch (const LLEventDispatcher::DispatchError& err) - { - // the error should also have been logged; we just need to - // stop the exception propagating - } - capture.messageWith(frag); + tut::call_logerr([this, func, args](){ work(func, args); }, frag); } LLSD getMetadata(const std::string& name) @@ -1328,11 +1333,11 @@ namespace tut } } - struct DispatchResult: public LLEventDispatcher + struct DispatchResult: public LLDispatchListener { using DR = DispatchResult; - DispatchResult(): LLEventDispatcher("expect result", "op") + DispatchResult(): LLDispatchListener("results", "op") { // As of 2022-12-22, LLEventDispatcher's shorthand add() methods // for pointer-to-method of same instance only support methods @@ -1340,6 +1345,7 @@ namespace tut // method) requires an instance getter. add("strfunc", "return string", &DR::strfunc, [this](){ return this; }); add("voidfunc", "void function", &DR::voidfunc, [this](){ return this; }); + add("emptyfunc", "return empty LLSD", &DR::emptyfunc, [this](){ return this; }); add("intfunc", "return Integer LLSD", &DR::intfunc, [this](){ return this; }); add("mapfunc", "return map LLSD", &DR::mapfunc, [this](){ return this; }); add("arrayfunc", "return array LLSD", &DR::arrayfunc, [this](){ return this; }); @@ -1347,6 +1353,7 @@ namespace tut std::string strfunc(const LLSD&) const { return "a string"; } void voidfunc() const {} + LLSD emptyfunc() const { return {}; } int intfunc(const LLSD&) const { return 17; } LLSD mapfunc(const LLSD&) const { return llsd::map("key", "value"); } LLSD arrayfunc(const LLSD&) const { return llsd::array("a", "b", "c"); } @@ -1396,4 +1403,77 @@ namespace tut LLSD result{ service("arrayfunc", "ignored") }; ensure_equals("arrayfunc() mismatch", result, llsd::array("a", "b", "c")); } + + template<> template<> + void object::test<28>() + { + set_test_name("listener error, no reply"); + DispatchResult service; + tut::call_exc( + [&service]() + { service.post(llsd::map("op", "nosuchfunc", "reqid", 17)); }, + "nosuchfunc"); + } + + template<> template<> + void object::test<29>() + { + set_test_name("listener error with reply"); + DispatchResult service; + LLCaptureListener result; + service.post(llsd::map("op", "nosuchfunc", "reqid", 17, "reply", result.getName())); + LLSD reply{ result.get() }; + ensure("no reply", reply.isDefined()); + ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17); + ensure_has(reply["error"].asString(), "nosuchfunc"); + } + + template<> template<> + void object::test<30>() + { + set_test_name("listener call to void function"); + DispatchResult service; + LLCaptureListener result; + result.set("non-empty"); + for (const auto& func: StringVec{ "voidfunc", "emptyfunc" }) + { + service.post(llsd::map( + "op", func, + "reqid", 17, + "reply", result.getName())); + ensure_equals("reply from " + func, result.get().asString(), "non-empty"); + } + } + + template<> template<> + void object::test<31>() + { + set_test_name("listener call to string function"); + DispatchResult service; + LLCaptureListener result; + service.post(llsd::map( + "op", "strfunc", + "args", llsd::array(LLSD()), + "reqid", 17, + "reply", result.getName())); + LLSD reply{ result.get() }; + ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17); + ensure_equals("bad reply from strfunc", reply["data"].asString(), "a string"); + } + + template<> template<> + void object::test<32>() + { + set_test_name("listener call to map function"); + DispatchResult service; + LLCaptureListener result; + service.post(llsd::map( + "op", "mapfunc", + "args", llsd::array(LLSD()), + "reqid", 17, + "reply", result.getName())); + LLSD reply{ result.get() }; + ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17); + ensure_equals("bad reply from mapfunc", reply["key"], "value"); + } } // namespace tut -- cgit v1.2.3 From 27f19826b6ef9b4af5db8613c44988b5c973618b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 12 Jan 2023 10:49:01 -0500 Subject: DRTVWR-558: Break out new LLDispatchListener::call() method. This captures logic we intend to reuse for forthcoming LLDispatchListener batched request support. (cherry picked from commit 3cb6d374cb76e4b00dc121255e8f5aa4e777fa27) --- indra/llcommon/lleventdispatcher.cpp | 69 +++++++++++++++++++++++++----------- indra/llcommon/lleventdispatcher.h | 36 +++++++++++++++++++ 2 files changed, 85 insertions(+), 20 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index 7abdc8f57a..8e20d7184a 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -742,6 +742,8 @@ std::ostream& operator<<(std::ostream& out, const LLEventDispatcher& self) /***************************************************************************** * LLDispatchListener *****************************************************************************/ +std::string LLDispatchListener::mReplyKey{ "reply" }; + /*==========================================================================*| TODO: * When process() finds name.isArray(), construct response array from @@ -752,33 +754,27 @@ std::ostream& operator<<(std::ostream& out, const LLEventDispatcher& self) note, caller can't care about order * Possible future transactional behavior: look up all names before calling any |*==========================================================================*/ -std::string LLDispatchListener::mReplyKey{ "reply" }; - bool LLDispatchListener::process(const LLSD& event) const { - LLSD result; - try - { - result = (*this)(event); - } - catch (const DispatchError& err) - { - // If the incoming event contains a "reply" key, we'll respond to the - // invoker with an error message (below). But if not -- silently - // ignoring an invocation request would be confusing at best. Escalate. - if (! event.has(mReplyKey)) - { - throw; - } - - // reply with a map containing an "error" key explaining the problem - reply(llsd::map("error", err.what()), event); + // Collecting errors is only meaningful with a reply key. Without one, if + // an error occurs, let the exception propagate. + auto returned = call("", event, (! event.has(mReplyKey))); + std::string& error{ returned.first }; + LLSD& result{ returned.second }; + + if (! error.empty()) + { + // Here there was an error and the incoming event has mReplyKey -- + // else DispatchError would already have propagated out of the call() + // above. Reply with a map containing an "error" key explaining the + // problem. + reply(llsd::map("error", error), event); return false; } // We seem to have gotten a valid result. But we don't know whether the // registered callable is void or non-void. If it's void, - // LLEventDispatcher will return isUndefined(). Otherwise, try to send it + // LLEventDispatcher returned isUndefined(). Otherwise, try to send it // back to our invoker. if (result.isDefined()) { @@ -792,6 +788,39 @@ bool LLDispatchListener::process(const LLSD& event) const return false; } +// Pass empty name to call LLEventDispatcher::operator()(const LLSD&), +// non-empty name to call operator()(const std::string&, const LLSD&). +// Returns (empty string, return value) on successful call. +// Returns (error message, undefined) if error and 'exception' is false. +// Throws DispatchError if error and 'exception' is true. +std::pair LLDispatchListener::call(const std::string& name, const LLSD& event, + bool exception) const +{ + try + { + if (name.empty()) + { + // unless this throws, return (empty string, real return value) + return { {}, (*this)(event) }; + } + else + { + // unless this throws, return (empty string, real return value) + return { {}, (*this)(name, event) }; + } + } + catch (const DispatchError& err) + { + if (exception) + { + // Caller asked for an exception on error. Oblige. + throw; + } + // Caller does NOT want an exception: return (error message, undefined) + return { err.what(), LLSD() }; + } +} + void LLDispatchListener::reply(const LLSD& reply, const LLSD& request) const { // Call sendReply() unconditionally: sendReply() itself tests whether the diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 494fc6a366..a348a6660f 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -42,6 +42,7 @@ #include #include #include +#include // std::pair #include "llevents.h" #include "llptrto.h" #include "llsdutil.h" @@ -522,6 +523,33 @@ LLEventDispatcher::make_invoker(Method f, const InstanceGetter& getter) * that contains (or derives from) LLDispatchListener need only specify the * LLEventPump name and dispatch key, and add() its methods. Incoming events * will automatically be dispatched. + * + * If the incoming event contains a "reply" key specifying the LLSD::String + * name of an LLEventPump to which to respond, LLDispatchListener will attempt + * to send a response to that LLEventPump. + * + * If some error occurs (e.g. nonexistent callable name, wrong params) and + * "reply" is present, LLDispatchListener will send a response map to the + * specified LLEventPump containing an "error" key whose value is the relevant + * error message. If "reply" is not present, the DispatchError exception will + * propagate. + * + * If LLDispatchListener successfully calls the target callable, but no + * "reply" key is present, any value returned by that callable is discarded. + * If a "reply" key is present, but the target callable is void -- or it + * returns LLSD::isUndefined() -- no response is sent. If a void callable + * wants to send a response, it must do so explicitly. + * + * If the target callable returns a type convertible to LLSD (and, if it + * directly returns LLSD, the return value isDefined()), and if a "reply" key + * is present in the incoming event, LLDispatchListener will post the returned + * value to the "reply" LLEventPump. If the returned value is an LLSD map, it + * will merge the echoed "reqid" key into the map and send that. Otherwise, it + * will send an LLSD map containing "reqid" and a "data" key whose value is + * the value returned by the target callable. + * + * (It is inadvisable for a target callable to return an LLSD map containing + * keys "reqid" or "error", as that will confuse the invoker.) */ // Instead of containing an LLEventStream, LLDispatchListener derives from it. // This allows an LLEventPumps::PumpFactory to return a pointer to an @@ -532,6 +560,7 @@ class LL_COMMON_API LLDispatchListener: public LLEventStream { public: + /// LLEventPump name, dispatch key [, arguments key (see LLEventDispatcher)] template LLDispatchListener(const std::string& pumpname, const std::string& key, ARGS&&... args); @@ -539,6 +568,13 @@ public: private: bool process(const LLSD& event) const; + // Pass empty name to call LLEventDispatcher::operator()(const LLSD&), + // non-empty name to call operator()(const std::string&, const LLSD&). + // Returns (empty string, return value) on successful call. + // Returns (error message, undefined) if error and 'exception' is false. + // Throws DispatchError if error and 'exception' is true. + std::pair call(const std::string& name, const LLSD& event, + bool exception) const; void reply(const LLSD& reply, const LLSD& request) const; LLTempBoundListener mBoundListener; -- cgit v1.2.3 From d637229beeb3b7fa2bc7adb850ce9337b119037c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 13 Jan 2023 00:10:23 -0500 Subject: DRTVWR-558: Introduce LLDispatchListener batched requests. Now the value of the incoming event's dispatch key may be an LLSD::String (as before), a map or an array, as documented in the augmented Doxygen class comments. LLDispatchListener will attempt multiple calls before sending a reply. (cherry picked from commit 7671b37285c6cdf1afcddb0019311a822c8a4dc5) --- indra/llcommon/lleventdispatcher.cpp | 183 +++++++++++++++++++++++++++-------- indra/llcommon/lleventdispatcher.h | 93 ++++++++++++++---- 2 files changed, 216 insertions(+), 60 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index 8e20d7184a..d10cf16b88 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -744,32 +744,44 @@ std::ostream& operator<<(std::ostream& out, const LLEventDispatcher& self) *****************************************************************************/ std::string LLDispatchListener::mReplyKey{ "reply" }; -/*==========================================================================*| - TODO: -* When process() finds name.isArray(), construct response array from - dispatching each call -- args must also be (array of args structures) - (could also construct response map, IF array contains unique names) -* When process() finds name.isMap(), construct response map from dispatching - each call -- value of each key is its args struct -- argskey ignored -- - note, caller can't care about order -* Possible future transactional behavior: look up all names before calling any -|*==========================================================================*/ bool LLDispatchListener::process(const LLSD& event) const { - // Collecting errors is only meaningful with a reply key. Without one, if - // an error occurs, let the exception propagate. - auto returned = call("", event, (! event.has(mReplyKey))); - std::string& error{ returned.first }; - LLSD& result{ returned.second }; + // Decide what to do based on the incoming value of the specified dispatch + // key. + LLSD name{ event[getDispatchKey()] }; + if (name.isMap()) + { + call_map(name, event); + } + else if (name.isArray()) + { + call_array(name, event); + } + else + { + call_one(name, event); + } + return false; +} - if (! error.empty()) +void LLDispatchListener::call_one(const LLSD& name, const LLSD& event) const +{ + LLSD result; + try { - // Here there was an error and the incoming event has mReplyKey -- - // else DispatchError would already have propagated out of the call() - // above. Reply with a map containing an "error" key explaining the - // problem. - reply(llsd::map("error", error), event); - return false; + result = (*this)(event); + } + catch (const DispatchError& err) + { + if (! event.has(mReplyKey)) + { + // Without a reply key, let the exception propagate. + throw; + } + + // Here there was an error and the incoming event has mReplyKey. Reply + // with a map containing an "error" key explaining the problem. + return reply(llsd::map("error", err.what()), event); } // We seem to have gotten a valid result. But we don't know whether the @@ -785,40 +797,127 @@ bool LLDispatchListener::process(const LLSD& event) const } reply(result, event); } - return false; } -// Pass empty name to call LLEventDispatcher::operator()(const LLSD&), -// non-empty name to call operator()(const std::string&, const LLSD&). -// Returns (empty string, return value) on successful call. -// Returns (error message, undefined) if error and 'exception' is false. -// Throws DispatchError if error and 'exception' is true. -std::pair LLDispatchListener::call(const std::string& name, const LLSD& event, - bool exception) const +void LLDispatchListener::call_map(const LLSD& reqmap, const LLSD& event) const { - try + // LLSD map containing returned values + LLSD result; + // collect any error messages here + std::ostringstream errors; + const char* delim = ""; + + for (const auto& pair : llsd::inMap(reqmap)) + { + const LLSD::String& name{ pair.first }; + const LLSD& args{ pair.second }; + try + { + // With this form, capture return value even if undefined: + // presence of the key in the response map can be used to detect + // which request keys succeeded. + result[name] = (*this)(name, args); + } + catch (const DispatchError& err) + { + // collect message in 'errors', with hint as to which request map + // entry failed + errors << delim << err.what() << " (" << getDispatchKey() + << '[' << name << "])"; + delim = "\n"; + } + } + + // so, were there any errors? + std::string error = errors.str(); + if (! error.empty()) { - if (name.empty()) + if (! event.has(mReplyKey)) { - // unless this throws, return (empty string, real return value) - return { {}, (*this)(event) }; + // can't send reply, throw + sCallFail(error); } else { - // unless this throws, return (empty string, real return value) - return { {}, (*this)(name, event) }; + // reply key present + result["error"] = error; } } - catch (const DispatchError& err) + + reply(result, event); +} + +void LLDispatchListener::call_array(const LLSD& reqarray, const LLSD& event) const +{ + // LLSD array containing returned values + LLSD results; + // arguments array, if present -- const because, if it's shorter than + // reqarray, we don't want to grow it + const LLSD argsarray{ event[getArgsKey()] }; + // error message, if any + std::string error; + + // classic index loop because we need the index + for (size_t i = 0, size = reqarray.size(); i < size; ++i) + { + const auto& reqentry{ reqarray[i] }; + std::string name; + LLSD args; + if (reqentry.isString()) + { + name = reqentry.asString(); + args = argsarray[i]; + } + else if (reqentry.isArray() && reqentry.size() == 2 && reqentry[0].isString()) + { + name = reqentry[0].asString(); + args = reqentry[1]; + } + else + { + // reqentry isn't in either of the documented forms + error = stringize(*this, ": ", getDispatchKey(), '[', i, "] ", + reqentry, " unsupported"); + break; + } + + // reqentry is one of the valid forms, got name and args + try + { + // With this form, capture return value even if undefined + results.append((*this)(name, args)); + } + catch (const DispatchError& err) + { + // append hint as to which requentry produced the error + error = stringize(err.what(), " (", getDispatchKey(), '[', i, ']'); + break; + } + } + + LLSD result; + // was there an error? + if (! error.empty()) { - if (exception) + if (! event.has(mReplyKey)) { - // Caller asked for an exception on error. Oblige. - throw; + // can't send reply, throw + sCallFail(error); + } + else + { + // reply key present + result["error"] = error; } - // Caller does NOT want an exception: return (error message, undefined) - return { err.what(), LLSD() }; } + + // wrap the results array as response map "data" key, as promised + if (results.isDefined()) + { + result["data"] = results; + } + + reply(result, event); } void LLDispatchListener::reply(const LLSD& reply, const LLSD& request) const diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index a348a6660f..5ab860b6dd 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -341,6 +341,8 @@ public: /// Retrieve the LLSD key we use for one-arg operator() method std::string getDispatchKey() const { return mKey; } + /// Retrieve the LLSD key we use for non-map arguments + std::string getArgsKey() const { return mArgskey; } /// description of this instance's leaf class and description friend std::ostream& operator<<(std::ostream&, const LLEventDispatcher&); @@ -363,6 +365,8 @@ private: void addFail(const std::string& name, const std::string& classname) const; LLSD try_call(const std::string& key, const std::string& name, const LLSD& event) const; + +protected: // raise specified EXCEPTION with specified stringize(ARGS) template void callFail(ARGS&&... args) const; @@ -370,6 +374,7 @@ private: static void sCallFail(ARGS&&... args); +private: std::string mDesc, mKey, mArgskey; DispatchMap mDispatch; @@ -521,12 +526,12 @@ LLEventDispatcher::make_invoker(Method f, const InstanceGetter& getter) /** * Bundle an LLEventPump and a listener with an LLEventDispatcher. A class * that contains (or derives from) LLDispatchListener need only specify the - * LLEventPump name and dispatch key, and add() its methods. Incoming events - * will automatically be dispatched. + * LLEventPump name and dispatch key, and add() its methods. Each incoming + * event ("request") will automatically be dispatched. * - * If the incoming event contains a "reply" key specifying the LLSD::String - * name of an LLEventPump to which to respond, LLDispatchListener will attempt - * to send a response to that LLEventPump. + * If the request contains a "reply" key specifying the LLSD::String name of + * an LLEventPump to which to respond, LLDispatchListener will attempt to send + * a response to that LLEventPump. * * If some error occurs (e.g. nonexistent callable name, wrong params) and * "reply" is present, LLDispatchListener will send a response map to the @@ -542,14 +547,70 @@ LLEventDispatcher::make_invoker(Method f, const InstanceGetter& getter) * * If the target callable returns a type convertible to LLSD (and, if it * directly returns LLSD, the return value isDefined()), and if a "reply" key - * is present in the incoming event, LLDispatchListener will post the returned - * value to the "reply" LLEventPump. If the returned value is an LLSD map, it - * will merge the echoed "reqid" key into the map and send that. Otherwise, it - * will send an LLSD map containing "reqid" and a "data" key whose value is - * the value returned by the target callable. + * is present in the request, LLDispatchListener will post the returned value + * to the "reply" LLEventPump. If the returned value is an LLSD map, it will + * merge the echoed "reqid" key into the map and send that. Otherwise, it will + * send an LLSD map containing "reqid" and a "data" key whose value is the + * value returned by the target callable. * * (It is inadvisable for a target callable to return an LLSD map containing - * keys "reqid" or "error", as that will confuse the invoker.) + * keys "data", "reqid" or "error", as that will confuse the invoker.) + * + * Normally the request will specify the value of the dispatch key as an + * LLSD::String naming the target callable. Alternatively, several such calls + * may be "batched" as described below. + * + * If the value of the dispatch key is itself an LLSD map (a "request map"), + * each map key must name a target callable, and the value of that key must + * contain the parameters to pass to that callable. If a "reply" key is + * present in the request, the response map will contain a key for each of the + * keys in the request map. The value of every such key is the value returned + * by the target callable. + * + * (Avoid naming any target callable in the LLDispatchListener "data", "reqid" + * or "error" to avoid confusion.) + * + * Since LLDispatchListener calls the target callables specified by a request + * map in arbitrary order, this form assumes that the batched operations are + * independent of each other. LLDispatchListener will attempt every call, even + * if some attempts produce errors. If any keys in the request map produce + * errors, LLDispatchListener builds a composite error message string + * collecting the relevant messages. The corresponding keys will be missing + * from the response map. As in the single-callable case, absent a "reply" key + * in the request, this error message will be thrown as a DispatchError. With + * a "reply" key, it will be returned as the value of the "error" key. This + * form can indicate partial success: some request keys might have + * return-value keys in the response, others might have message text in the + * "error" key. + * + * If a specific call sequence is required, the value of the dispatch key may + * instead be an LLSD array (a "request array"). Each entry in the request + * array ("request entry") names a target callable, to be called in + * array-index sequence. Arguments for that callable may be specified in + * either of two ways. + * + * The request entry may itself be a two-element array, whose [0] is an + * LLSD::String naming the target callable and whose [1] contains the + * arguments to pass to that callable. + * + * Alternatively, the request entry may be an LLSD::String naming the target + * callable, in which case the request must contain an arguments key (optional + * third constructor argument) whose value is an array matching the request + * array. The arguments for the request entry's target callable are found at + * the same index in the arguments key array. + * + * If a "reply" key is present in the request, the response map will contain a + * "data" key whose value is an array. Each entry in that response array will + * contain the result from the corresponding request entry. + * + * This form assumes that any of the batched operations might depend on the + * success of a previous operation in the same batch. The @emph first error + * encountered will terminate the sequence. The error message might either be + * thrown as DispatchError or, given a "reply" key, returned as the "error" + * key in the response map. This form can indicate partial success: the first + * few request entries might have return-value entries in the "data" response + * array, along with an "error" key whose value is the error message that + * stopped the sequence. */ // Instead of containing an LLEventStream, LLDispatchListener derives from it. // This allows an LLEventPumps::PumpFactory to return a pointer to an @@ -568,13 +629,9 @@ public: private: bool process(const LLSD& event) const; - // Pass empty name to call LLEventDispatcher::operator()(const LLSD&), - // non-empty name to call operator()(const std::string&, const LLSD&). - // Returns (empty string, return value) on successful call. - // Returns (error message, undefined) if error and 'exception' is false. - // Throws DispatchError if error and 'exception' is true. - std::pair call(const std::string& name, const LLSD& event, - bool exception) const; + void call_one(const LLSD& name, const LLSD& event) const; + void call_map(const LLSD& reqmap, const LLSD& event) const; + void call_array(const LLSD& reqarray, const LLSD& event) const; void reply(const LLSD& reply, const LLSD& request) const; LLTempBoundListener mBoundListener; -- cgit v1.2.3 From 98793b8d44b32604033bd1b280f37023c86055bb Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 13 Jan 2023 21:53:18 -0500 Subject: DRTVWR-558: Make DispatchResult methods use their arguments. Fix lleventdispatcher_test.cpp's test class DispatchResult::strfunc(), intfunc(), mapfunc() and arrayfunc() to return values derived from (not identical to) their arguments, so we can reuse these functions for further testing of passing arguments to a named callable. Adjust existing tests accordingly. (cherry picked from commit 07e09a8daea008d28b97399920db60a147cf75c0) --- indra/llcommon/tests/lleventdispatcher_test.cpp | 37 +++++++++++++++---------- 1 file changed, 22 insertions(+), 15 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 179fab9fad..0254d23a0b 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -1351,12 +1351,18 @@ namespace tut add("arrayfunc", "return array LLSD", &DR::arrayfunc, [this](){ return this; }); } - std::string strfunc(const LLSD&) const { return "a string"; } + std::string strfunc(const std::string& str) const { return "got " + str; } void voidfunc() const {} LLSD emptyfunc() const { return {}; } - int intfunc(const LLSD&) const { return 17; } - LLSD mapfunc(const LLSD&) const { return llsd::map("key", "value"); } - LLSD arrayfunc(const LLSD&) const { return llsd::array("a", "b", "c"); } + int intfunc(int i) const { return -i; } + LLSD mapfunc(int i, const std::string& str) const + { + return llsd::map("i", intfunc(i), "str", strfunc(str)); + } + LLSD arrayfunc(int i, const std::string& str) const + { + return llsd::array(intfunc(i), strfunc(str)); + } }; template<> template<> @@ -1364,8 +1370,8 @@ namespace tut { set_test_name("string result"); DispatchResult service; - LLSD result{ service("strfunc", "ignored") }; - ensure_equals("strfunc() mismatch", result.asString(), "a string"); + LLSD result{ service("strfunc", "a string") }; + ensure_equals("strfunc() mismatch", result.asString(), "got a string"); } template<> template<> @@ -1382,7 +1388,7 @@ namespace tut { set_test_name("Integer result"); DispatchResult service; - LLSD result{ service("intfunc", "ignored") }; + LLSD result{ service("intfunc", -17) }; ensure_equals("intfunc() mismatch", result.asInteger(), 17); } @@ -1391,8 +1397,8 @@ namespace tut { set_test_name("map LLSD result"); DispatchResult service; - LLSD result{ service("mapfunc", "ignored") }; - ensure_equals("mapfunc() mismatch", result, llsd::map("key", "value")); + LLSD result{ service("mapfunc", llsd::array(-12, "value")) }; + ensure_equals("mapfunc() mismatch", result, llsd::map("i", 12, "str", "got value")); } template<> template<> @@ -1400,8 +1406,8 @@ namespace tut { set_test_name("array LLSD result"); DispatchResult service; - LLSD result{ service("arrayfunc", "ignored") }; - ensure_equals("arrayfunc() mismatch", result, llsd::array("a", "b", "c")); + LLSD result{ service("arrayfunc", llsd::array(-8, "word")) }; + ensure_equals("arrayfunc() mismatch", result, llsd::array(8, "got word")); } template<> template<> @@ -1453,12 +1459,12 @@ namespace tut LLCaptureListener result; service.post(llsd::map( "op", "strfunc", - "args", llsd::array(LLSD()), + "args", llsd::array("a string"), "reqid", 17, "reply", result.getName())); LLSD reply{ result.get() }; ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17); - ensure_equals("bad reply from strfunc", reply["data"].asString(), "a string"); + ensure_equals("bad reply from strfunc", reply["data"].asString(), "got a string"); } template<> template<> @@ -1469,11 +1475,12 @@ namespace tut LLCaptureListener result; service.post(llsd::map( "op", "mapfunc", - "args", llsd::array(LLSD()), + "args", llsd::array(-7, "value"), "reqid", 17, "reply", result.getName())); LLSD reply{ result.get() }; ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17); - ensure_equals("bad reply from mapfunc", reply["key"], "value"); + ensure_equals("bad i from mapfunc", reply["i"].asInteger(), 7); + ensure_equals("bad str from mapfunc", reply["str"], "got value"); } } // namespace tut -- cgit v1.2.3 From b36deb79c2e99bfa600b5895c277ffb78c61957f Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 13 Jan 2023 23:07:24 -0500 Subject: DRTVWR-558: Add tests for batched LLDispatchListener operations. Specifically, add tests for: - successful map batch - map batch with some errors and a reply pump - map batch with some errors and no reply - successful array batch - array batch with some errors and a reply pump - array batch with some errors and no reply (cherry picked from commit 078f0f5c9fb5075a8ad01cac417e1d7ee2b6a919) --- indra/llcommon/tests/lleventdispatcher_test.cpp | 175 ++++++++++++++++++++++++ 1 file changed, 175 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 0254d23a0b..8e84a9e038 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -1483,4 +1483,179 @@ namespace tut ensure_equals("bad i from mapfunc", reply["i"].asInteger(), 7); ensure_equals("bad str from mapfunc", reply["str"], "got value"); } + + template<> template<> + void object::test<33>() + { + set_test_name("batched map success"); + DispatchResult service; + LLCaptureListener result; + service.post(llsd::map( + "op", llsd::map( + "strfunc", "some string", + "intfunc", 2, + "voidfunc", LLSD(), + "arrayfunc", llsd::array(-5, "other string")), + "reqid", 17, + "reply", result.getName())); + LLSD reply{ result.get() }; + ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17); + reply.erase("reqid"); + ensure_equals( + "bad map batch", + reply, + llsd::map( + "strfunc", "got some string", + "intfunc", -2, + "voidfunc", LLSD(), + "arrayfunc", llsd::array(5, "got other string"))); + } + + template<> template<> + void object::test<34>() + { + set_test_name("batched map error"); + DispatchResult service; + LLCaptureListener result; + service.post(llsd::map( + "op", llsd::map( + "badfunc", 34, // ! + "strfunc", "some string", + "intfunc", 2, + "missing", LLSD(), // ! + "voidfunc", LLSD(), + "arrayfunc", llsd::array(-5, "other string")), + "reqid", 17, + "reply", result.getName())); + LLSD reply{ result.get() }; + ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17); + reply.erase("reqid"); + auto error{ reply["error"].asString() }; + reply.erase("error"); + ensure_has(error, "badfunc"); + ensure_has(error, "missing"); + ensure_equals( + "bad partial batch", + reply, + llsd::map( + "strfunc", "got some string", + "intfunc", -2, + "voidfunc", LLSD(), + "arrayfunc", llsd::array(5, "got other string"))); + } + + template<> template<> + void object::test<35>() + { + set_test_name("batched map exception"); + DispatchResult service; + auto error = tut::call_exc( + [&service]() + { + service.post(llsd::map( + "op", llsd::map( + "badfunc", 34, // ! + "strfunc", "some string", + "intfunc", 2, + "missing", LLSD(), // ! + "voidfunc", LLSD(), + "arrayfunc", llsd::array(-5, "other string")), + "reqid", 17)); + // no "reply" + }, + "badfunc"); + ensure_has(error, "missing"); + } + + template<> template<> + void object::test<36>() + { + set_test_name("batched array success"); + DispatchResult service; + LLCaptureListener result; + service.post(llsd::map( + "op", llsd::array( + llsd::array("strfunc", "some string"), + llsd::array("intfunc", 2), + "arrayfunc", + "voidfunc"), + "args", llsd::array( + LLSD(), + LLSD(), + llsd::array(-5, "other string")), + // args array deliberately short, since the default + // [3] is undefined, which should work for voidfunc + "reqid", 17, + "reply", result.getName())); + LLSD reply{ result.get() }; + ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17); + reply.erase("reqid"); + ensure_equals( + "bad array batch", + reply, + llsd::map( + "data", llsd::array( + "got some string", + -2, + llsd::array(5, "got other string"), + LLSD()))); + } + + template<> template<> + void object::test<37>() + { + set_test_name("batched array error"); + DispatchResult service; + LLCaptureListener result; + service.post(llsd::map( + "op", llsd::array( + llsd::array("strfunc", "some string"), + llsd::array("intfunc", 2, "whoops"), // bad form + "arrayfunc", + "voidfunc"), + "args", llsd::array( + LLSD(), + LLSD(), + llsd::array(-5, "other string")), + // args array deliberately short, since the default + // [3] is undefined, which should work for voidfunc + "reqid", 17, + "reply", result.getName())); + LLSD reply{ result.get() }; + ensure_equals("reqid not echoed", reply["reqid"].asInteger(), 17); + reply.erase("reqid"); + auto error{ reply["error"] }; + reply.erase("error"); + ensure_has(error, "[1]"); + ensure_has(error, "unsupported"); + ensure_equals("bad array batch", reply, + llsd::map("data", llsd::array("got some string"))); + } + + template<> template<> + void object::test<38>() + { + set_test_name("batched array exception"); + DispatchResult service; + auto error = tut::call_exc( + [&service]() + { + service.post(llsd::map( + "op", llsd::array( + llsd::array("strfunc", "some string"), + llsd::array("intfunc", 2, "whoops"), // bad form + "arrayfunc", + "voidfunc"), + "args", llsd::array( + LLSD(), + LLSD(), + llsd::array(-5, "other string")), + // args array deliberately short, since the default + // [3] is undefined, which should work for voidfunc + "reqid", 17)); + // no "reply" + }, + "[1]"); + ensure_has(error, "unsupported"); + } } // namespace tut -- cgit v1.2.3 From 2c894ecb25de044f5cb9c408c5264e5234b73983 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 20 Jan 2023 22:34:31 -0500 Subject: DRTVWR-558: Extend LLEventDispatcher::add() overloads. Add LL::always_return(), which takes a callable and variadic arguments. It calls the callable with those arguments and, if the returned type is convertible to T, converts it and returns it. Otherwise it returns T(). always_return() is generalized from, and supersedes, LLEventDispatcher::ReturnLLSD. Add LL::function_arity, which extends boost::function_types::function_arity by reporting results for both std::function and boost::function. Use for LL::apply(function, LLSD array) as well as for LLEventDispatcher. Make LLEventDispatcher::add() overloads uniformly distinguish between a callable (whether non-static member function or otherwise) that accepts a single LLSD parameter, versus any other signature. Accepting exactly one LLSD parameter signals that the callable will accept the composite arguments LLSD blob, instead of asking LLEventDispatcher to unpack the arguments blob into individual arguments. Support add(subclass method) overloads for arbitrary-parameters methods as well as for (const LLSD&) methods. Update tests accordingly: we need no longer pass the boilerplate lambda instance getter that binds and returns 'this'. Extract to the two LLEventDispatcher::make_invoker() overloads the LL::apply() logic formerly found in ReturnLLSD. Change lleventdispatcher_test.cpp tests from boost::bind(), which accepts variadic arguments (even though it only passes a fixed set to the target callable), to fixed-signature lambdas. This is because the revamped add() overloads care about signature. Add a test for a non-static method that accepts (const LLSD&), in other words the composite arguments LLSD blob, and likewise returns LLSD. (cherry picked from commit 95b787f7d7226ee9de79dfc9816f33c8bf199aad) --- indra/llcommon/CMakeLists.txt | 2 + indra/llcommon/always_return.h | 124 +++++++++ indra/llcommon/function_types.h | 49 ++++ indra/llcommon/lleventdispatcher.cpp | 7 +- indra/llcommon/lleventdispatcher.h | 326 ++++++++++++++++++------ indra/llcommon/llsdutil.h | 4 +- indra/llcommon/tests/lleventdispatcher_test.cpp | 62 +++-- 7 files changed, 460 insertions(+), 114 deletions(-) create mode 100644 indra/llcommon/always_return.h create mode 100644 indra/llcommon/function_types.h (limited to 'indra/llcommon') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 33e8301e12..64751926d0 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -117,11 +117,13 @@ set(llcommon_SOURCE_FILES set(llcommon_HEADER_FILES CMakeLists.txt + always_return.h apply.h chrono.h classic_callback.h ctype_workaround.h fix_macros.h + function_types.h indra_constants.h lazyeventapi.h linden_common.h diff --git a/indra/llcommon/always_return.h b/indra/llcommon/always_return.h new file mode 100644 index 0000000000..6b9f1fdeaf --- /dev/null +++ b/indra/llcommon/always_return.h @@ -0,0 +1,124 @@ +/** + * @file always_return.h + * @author Nat Goodspeed + * @date 2023-01-20 + * @brief Call specified callable with arbitrary arguments, but always return + * specified type. + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Copyright (c) 2023, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_ALWAYS_RETURN_H) +#define LL_ALWAYS_RETURN_H + +#include // std::enable_if, std::is_convertible + +namespace LL +{ + +#if __cpp_lib_is_invocable >= 201703L // C++17 + template + using invoke_result = std::invoke_result; +#else // C++14 + template + using invoke_result = std::result_of; +#endif // C++14 + + /** + * AlwaysReturn()(some_function, some_args...) calls + * some_function(some_args...). It is guaranteed to return a value of type + * T, regardless of the return type of some_function(). If some_function() + * returns a type convertible to T, it will convert and return that value. + * Otherwise (notably if some_function() is void), AlwaysReturn returns + * T(). + * + * When some_function() returns a type not convertible to T, if + * you want AlwaysReturn to return some T value other than + * default-constructed T(), pass that value to AlwaysReturn's constructor. + */ + template + class AlwaysReturn + { + public: + /// pass explicit default value if other than default-constructed type + AlwaysReturn(const DESIRED& dft=DESIRED()): mDefault(dft) {} + + // callable returns a type not convertible to DESIRED, return default + template ::type, + DESIRED + >::value, + bool + >::type=true> + DESIRED operator()(CALLABLE&& callable, ARGS&&... args) + { + // discard whatever callable(args) returns + std::forward(callable)(std::forward(args)...); + return mDefault; + } + + // callable returns a type convertible to DESIRED + template ::type, + DESIRED + >::value, + bool + >::type=true> + DESIRED operator()(CALLABLE&& callable, ARGS&&... args) + { + return { std::forward(callable)(std::forward(args)...) }; + } + + private: + DESIRED mDefault; + }; + + /** + * always_return(some_function, some_args...) calls + * some_function(some_args...). It is guaranteed to return a value of type + * T, regardless of the return type of some_function(). If some_function() + * returns a type convertible to T, it will convert and return that value. + * Otherwise (notably if some_function() is void), always_return() returns + * T(). + */ + template + DESIRED always_return(CALLABLE&& callable, ARGS&&... args) + { + return AlwaysReturn()(std::forward(callable), + std::forward(args)...); + } + + /** + * make_always_return(some_function) returns a callable which, when + * called with appropriate some_function() arguments, always returns a + * value of type T, regardless of the return type of some_function(). If + * some_function() returns a type convertible to T, the returned callable + * will convert and return that value. Otherwise (notably if + * some_function() is void), the returned callable returns T(). + * + * When some_function() returns a type not convertible to T, if + * you want the returned callable to return some T value other than + * default-constructed T(), pass that value to make_always_return() as its + * optional second argument. + */ + template + auto make_always_return(CALLABLE&& callable, const DESIRED& dft=DESIRED()) + { + return + [dft, callable = std::forward(callable)] + (auto&&... args) + { + return AlwaysReturn(dft)(callable, + std::forward(args)...); + }; + } + +} // namespace LL + +#endif /* ! defined(LL_ALWAYS_RETURN_H) */ diff --git a/indra/llcommon/function_types.h b/indra/llcommon/function_types.h new file mode 100644 index 0000000000..3f42f6d640 --- /dev/null +++ b/indra/llcommon/function_types.h @@ -0,0 +1,49 @@ +/** + * @file function_types.h + * @author Nat Goodspeed + * @date 2023-01-20 + * @brief Extend boost::function_types to examine boost::function and + * std::function + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Copyright (c) 2023, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_FUNCTION_TYPES_H) +#define LL_FUNCTION_TYPES_H + +#include +#include +#include + +namespace LL +{ + + template + struct function_arity_impl + { + static constexpr auto value = boost::function_types::function_arity::value; + }; + + template + struct function_arity_impl> + { + static constexpr auto value = function_arity_impl::value; + }; + + template + struct function_arity_impl> + { + static constexpr auto value = function_arity_impl::value; + }; + + template + struct function_arity + { + static constexpr auto value = function_arity_impl::type>::value; + }; + +} // namespace LL + +#endif /* ! defined(LL_FUNCTION_TYPES_H) */ diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index d10cf16b88..caff854753 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -384,8 +384,7 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE (desc, ": bad request: ", mismatch); } // Event syntax looks good, go for it! - mFunc(event); - return {}; + return mFunc(event); } LLSD addMetadata(LLSD meta) const override @@ -603,8 +602,8 @@ void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name, } /// Register a callable by name -void LLEventDispatcher::add(const std::string& name, const std::string& desc, - const Callable& callable, const LLSD& required) +void LLEventDispatcher::addLLSD(const std::string& name, const std::string& desc, + const Callable& callable, const LLSD& required) { mDispatch.emplace(name, new LLSDDispatchEntry(desc, callable, required)); } diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 5ab860b6dd..789a59459c 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -32,17 +32,18 @@ #if ! defined(LL_LLEVENTDISPATCHER_H) #define LL_LLEVENTDISPATCHER_H -#include #include #include -#include -#include +#include // until C++17, when we get std::is_invocable +#include #include // std::function #include // std::unique_ptr #include #include #include #include // std::pair +#include "always_return.h" +#include "function_types.h" // LL::function_arity #include "llevents.h" #include "llptrto.h" #include "llsdutil.h" @@ -78,7 +79,7 @@ public: //@{ /// Accept any C++ callable with the right signature - typedef std::function Callable; + typedef std::function Callable; /** * Register a @a callable by @a name. The passed @a callable accepts a @@ -90,19 +91,25 @@ public: void add(const std::string& name, const std::string& desc, const Callable& callable, - const LLSD& required=LLSD()); + const LLSD& required=LLSD()) + { + addLLSD(name, desc, callable, required); + } - /** - * The case of a free function (or static method) accepting(const LLSD&) - * could also be intercepted by the arbitrary-args overload below. Ensure - * that it's directed to the Callable overload above instead. - */ + template ::value + >::type> void add(const std::string& name, const std::string& desc, - void (*f)(const LLSD&), + CALLABLE&& callable, const LLSD& required=LLSD()) { - add(name, desc, Callable(f), required); + addLLSD( + name, + desc, + Callable(LL::make_always_return(std::forward(callable))), + required); } /** @@ -111,6 +118,27 @@ public: * specifying a std::bind() expression. The passed @a method * accepts a single LLSD value, presumably containing other parameters. */ + template + void add(const std::string& name, + const std::string& desc, + R (CLASS::*method)(const LLSD&), + const LLSD& required=LLSD()) + { + addMethod(name, desc, method, required); + } + + /// Overload for both const and non-const methods. The passed @a method + /// accepts a single LLSD value, presumably containing other parameters. + template + void add(const std::string& name, + const std::string& desc, + R (CLASS::*method)(const LLSD&) const, + const LLSD& required=LLSD()) + { + addMethod(name, desc, method, required); + } + + // because the compiler can't match a method returning void to the above template void add(const std::string& name, const std::string& desc, @@ -131,6 +159,128 @@ public: addMethod(name, desc, method, required); } + // non-const nullary method + template + void add(const std::string& name, + const std::string& desc, + R (CLASS::*method)()) + { + addVMethod(name, desc, method); + } + + // const nullary method + template + void add(const std::string& name, + const std::string& desc, + R (CLASS::*method)() const) + { + addVMethod(name, desc, method); + } + + // non-const nullary method returning void + template + void add(const std::string& name, + const std::string& desc, + void (CLASS::*method)()) + { + addVMethod(name, desc, method); + } + + // const nullary method returning void + template + void add(const std::string& name, + const std::string& desc, + void (CLASS::*method)() const) + { + addVMethod(name, desc, method); + } + + // non-const unary method (but method accepting LLSD should use the other add()) + // enable_if usage per https://stackoverflow.com/a/39913395/5533635 + template ::type, LLSD>::value + >::type> + void add(const std::string& name, + const std::string& desc, + R (CLASS::*method)(ARG)) + { + addVMethod(name, desc, method); + } + + // const unary method (but method accepting LLSD should use the other add()) + template ::type, LLSD>::value + >::type> + void add(const std::string& name, + const std::string& desc, + R (CLASS::*method)(ARG) const) + { + addVMethod(name, desc, method); + } + + // non-const unary method returning void + // enable_if usage per https://stackoverflow.com/a/39913395/5533635 + template ::type, LLSD>::value + >::type> + void add(const std::string& name, + const std::string& desc, + void (CLASS::*method)(ARG)) + { + addVMethod(name, desc, method); + } + + // const unary method returning void + template ::type, LLSD>::value + >::type> + void add(const std::string& name, + const std::string& desc, + void (CLASS::*method)(ARG) const) + { + addVMethod(name, desc, method); + } + + // non-const binary (or more) method + template + void add(const std::string& name, + const std::string& desc, + R (CLASS::*method)(ARG0, ARG1, ARGS...)) + { + addVMethod(name, desc, method); + } + + // const binary (or more) method + template + void add(const std::string& name, + const std::string& desc, + R (CLASS::*method)(ARG0, ARG1, ARGS...) const) + { + addVMethod(name, desc, method); + } + + // non-const binary (or more) method returning void + template + void add(const std::string& name, + const std::string& desc, + void (CLASS::*method)(ARG0, ARG1, ARGS...)) + { + addVMethod(name, desc, method); + } + + // const binary (or more) method returning void + template + void add(const std::string& name, + const std::string& desc, + void (CLASS::*method)(ARG0, ARG1, ARGS...) const) + { + addVMethod(name, desc, method); + } + //@} /// @name Register functions with arbitrary param lists @@ -143,12 +293,16 @@ public: * When calling this name, pass an LLSD::Array. Each entry in turn will be * converted to the corresponding parameter type using LLSDParam. */ - // enable_if usage per https://stackoverflow.com/a/39913395/5533635 - template::value + template () >::type> - void add(const std::string& name, const std::string& desc, Function f); + void add(const std::string& name, + const std::string& desc, + CALLABLE&& f) + { + addV(name, desc, f); + } /** * Register a nonstatic class method with arbitrary parameters. @@ -156,11 +310,7 @@ public: * To cover cases such as a method on an LLSingleton we don't yet want to * instantiate, instead of directly storing an instance pointer, accept a * nullary callable returning a pointer/reference to the desired class - * instance. If you already have an instance in hand, - * boost::lambda::var(instance) or boost::lambda::constant(instance_ptr) - * produce suitable callables. - * - * TODO: variant accepting a method of the containing class, no getter. + * instance. * * When calling this name, pass an LLSD::Array. Each entry in turn will be * converted to the corresponding parameter type using LLSDParam. @@ -186,7 +336,8 @@ public: */ template::value + boost::function_types::is_nonmember_callable_builtin::value && + ! boost::hof::is_invocable::value >::type> void add(const std::string& name, const std::string& desc, Function f, const LLSD& params, const LLSD& defaults=LLSD()); @@ -348,6 +499,11 @@ public: friend std::ostream& operator<<(std::ostream&, const LLEventDispatcher&); private: + void addLLSD(const std::string& name, + const std::string& desc, + const Callable& callable, + const LLSD& required); + template void addMethod(const std::string& name, const std::string& desc, const METHOD& method, const LLSD& required) @@ -359,9 +515,40 @@ private: } else { - add(name, desc, std::bind(method, downcast, std::placeholders::_1), required); + add(name, + desc, + Callable(LL::make_always_return( + [downcast, method] + (const LLSD& args) + { + return (downcast->*method)(args); + })), + required); } } + + template + void addVMethod(const std::string& name, const std::string& desc, + const METHOD& method) + { + CLASS* downcast = dynamic_cast(this); + if (! downcast) + { + addFail(name, typeid(CLASS).name()); + } + else + { + // add() arbitrary method plus InstanceGetter, where the + // InstanceGetter in this case returns 'this'. We don't need to + // worry about binding 'this' because, once this LLEventDispatcher + // is destroyed, the DispatchEntry goes away too. + add(name, desc, method, [downcast](){ return downcast; }); + } + } + + template + void addV(const std::string& name, const std::string& desc, Function f); + void addFail(const std::string& name, const std::string& classname) const; LLSD try_call(const std::string& key, const std::string& name, const LLSD& event) const; @@ -405,21 +592,19 @@ private: const invoker_function& invoker, const LLSD& params, const LLSD& defaults); - template - struct ReturnLLSD; }; /***************************************************************************** * LLEventDispatcher template implementation details *****************************************************************************/ -template -void LLEventDispatcher::add(const std::string& name, const std::string& desc, Function f) +template +void LLEventDispatcher::addV(const std::string& name, const std::string& desc, Function f) { // Construct an invoker_function, a callable accepting const LLSD&. // Add to DispatchMap an ArrayParamsDispatchEntry that will handle the // caller's LLSD::Array. addArrayParamsDispatchEntry(name, desc, make_invoker(f), - boost::function_types::function_arity::value); + LL::function_arity::value); } template @@ -429,7 +614,7 @@ void LLEventDispatcher::add(const std::string& name, const std::string& desc, Me // Subtract 1 from the compile-time arity because the getter takes care of // the first parameter. We only need (arity - 1) additional arguments. addArrayParamsDispatchEntry(name, desc, make_invoker(f, getter), - boost::function_types::function_arity::value - 1); + LL::function_arity::value - 1); } template @@ -448,64 +633,23 @@ void LLEventDispatcher::add(const std::string& name, const std::string& desc, Me addMapParamsDispatchEntry(name, desc, make_invoker(f, getter), params, defaults); } -// general case, when f() has a non-void return type -template -struct LLEventDispatcher::ReturnLLSD -{ - template - LLSD operator()(Function f, const LLSD& args) - { - return { LL::apply(f, args) }; - } - - template - LLSD operator()(Method f, const InstanceGetter& getter, const LLSD& args) - { - constexpr auto arity = boost::function_types::function_arity< - typename std::remove_reference::type>::value - 1; - - // Use bind_front() to bind the method to (a pointer to) the object - // returned by getter(). It's okay to capture and bind a pointer - // because this bind_front() object will last only as long as this - // operator() call. - return { LL::apply_n(LL::bind_front(f, LL::get_ptr(getter())), args) }; - } -}; - -// specialize for void return type -template <> -struct LLEventDispatcher::ReturnLLSD -{ - template - LLSD operator()(Function f, const LLSD& args) - { - LL::apply(f, args); - return {}; - } - - template - LLSD operator()(Method f, const InstanceGetter& getter, const LLSD& args) - { - constexpr auto arity = boost::function_types::function_arity< - typename std::remove_reference::type>::value - 1; - - // Use bind_front() to bind the method to (a pointer to) the object - // returned by getter(). It's okay to capture and bind a pointer - // because this bind_front() object will last only as long as this - // operator() call. - LL::apply_n(LL::bind_front(f, LL::get_ptr(getter())), args); - return {}; - } -}; - template LLEventDispatcher::invoker_function LLEventDispatcher::make_invoker(Function f) { + // Return an invoker_function that accepts (const LLSD& args). return [f](const LLSD& args) { - return ReturnLLSD::type>() - (f, args); + // When called, call always_return, directing it to call + // f(expanded args). always_return guarantees we'll get an LLSD + // value back, even if it's undefined because 'f' doesn't return a + // type convertible to LLSD. + return LL::always_return( + [f, args] + () + { + return LL::apply(f, args); + }); }; } @@ -513,10 +657,24 @@ template LLEventDispatcher::invoker_function LLEventDispatcher::make_invoker(Method f, const InstanceGetter& getter) { + // function_arity includes its implicit 'this' pointer + constexpr auto arity = LL::function_arity< + typename std::remove_reference::type>::value - 1; + return [f, getter](const LLSD& args) { - return ReturnLLSD::type>() - (f, getter, args); + // always_return() immediately calls the lambda we pass, and + // returns LLSD whether our passed lambda returns void or non-void. + return LL::always_return( + [f, getter, args] + () + { + // Use bind_front() to bind the method to (a pointer to) the object + // returned by getter(). It's okay to capture and bind a pointer + // because this bind_front() object will last only as long as this + // lambda call. + return LL::apply_n(LL::bind_front(f, LL::get_ptr(getter())), args); + }); }; } diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h index baf4400768..a6fd2fdac2 100644 --- a/indra/llcommon/llsdutil.h +++ b/indra/llcommon/llsdutil.h @@ -30,9 +30,9 @@ #define LL_LLSDUTIL_H #include "apply.h" // LL::invoke() +#include "function_types.h" // LL::function_arity #include "llsd.h" #include -#include #include #include @@ -628,7 +628,7 @@ template auto apply(CALLABLE&& func, const LLSD& args) { // infer arity from the definition of func - constexpr auto arity = boost::function_types::function_arity< + constexpr auto arity = function_arity< typename std::remove_reference::type>::value; // now that we have a compile-time arity, apply_n() works return apply_n(std::forward(func), args); diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 8e84a9e038..2a3644e2e1 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -423,9 +423,9 @@ namespace tut work.add(name, desc, &Dispatcher::cmethod1, required); // Non-subclass method with/out required params addf("method1", "method1", &v); - work.add(name, desc, boost::bind(&Vars::method1, boost::ref(v), _1)); + work.add(name, desc, [this](const LLSD& args){ return v.method1(args); }); addf("method1_req", "method1", &v); - work.add(name, desc, boost::bind(&Vars::method1, boost::ref(v), _1), required); + work.add(name, desc, [this](const LLSD& args){ return v.method1(args); }, required); /*--------------- Arbitrary params, array style ----------------*/ @@ -609,6 +609,7 @@ namespace tut void addf(const std::string& n, const std::string& d, Vars* v) { + debug("addf('", n, "', '", d, "')"); // This method is to capture in our own DescMap the name and // description of every registered function, for metadata query // testing. @@ -1339,22 +1340,25 @@ namespace tut DispatchResult(): LLDispatchListener("results", "op") { - // As of 2022-12-22, LLEventDispatcher's shorthand add() methods - // for pointer-to-method of same instance only support methods - // with signature void(const LLSD&). The generic add(pointer-to- - // method) requires an instance getter. - add("strfunc", "return string", &DR::strfunc, [this](){ return this; }); - add("voidfunc", "void function", &DR::voidfunc, [this](){ return this; }); - add("emptyfunc", "return empty LLSD", &DR::emptyfunc, [this](){ return this; }); - add("intfunc", "return Integer LLSD", &DR::intfunc, [this](){ return this; }); - add("mapfunc", "return map LLSD", &DR::mapfunc, [this](){ return this; }); - add("arrayfunc", "return array LLSD", &DR::arrayfunc, [this](){ return this; }); + add("strfunc", "return string", &DR::strfunc); + add("voidfunc", "void function", &DR::voidfunc); + add("emptyfunc", "return empty LLSD", &DR::emptyfunc); + add("intfunc", "return Integer LLSD", &DR::intfunc); + add("llsdfunc", "return passed LLSD", &DR::llsdfunc); + add("mapfunc", "return map LLSD", &DR::mapfunc); + add("arrayfunc", "return array LLSD", &DR::arrayfunc); } std::string strfunc(const std::string& str) const { return "got " + str; } void voidfunc() const {} LLSD emptyfunc() const { return {}; } int intfunc(int i) const { return -i; } + LLSD llsdfunc(const LLSD& event) const + { + LLSD result{ event }; + result["with"] = "string"; + return result; + } LLSD mapfunc(int i, const std::string& str) const { return llsd::map("i", intfunc(i), "str", strfunc(str)); @@ -1394,6 +1398,16 @@ namespace tut template<> template<> void object::test<26>() + { + set_test_name("LLSD echo"); + DispatchResult service; + LLSD result{ service("llsdfunc", llsd::map("op", "llsdfunc", "reqid", 17)) }; + ensure_equals("llsdfunc() mismatch", result, + llsd::map("op", "llsdfunc", "reqid", 17, "with", "string")); + } + + template<> template<> + void object::test<27>() { set_test_name("map LLSD result"); DispatchResult service; @@ -1402,7 +1416,7 @@ namespace tut } template<> template<> - void object::test<27>() + void object::test<28>() { set_test_name("array LLSD result"); DispatchResult service; @@ -1411,7 +1425,7 @@ namespace tut } template<> template<> - void object::test<28>() + void object::test<29>() { set_test_name("listener error, no reply"); DispatchResult service; @@ -1422,7 +1436,7 @@ namespace tut } template<> template<> - void object::test<29>() + void object::test<30>() { set_test_name("listener error with reply"); DispatchResult service; @@ -1435,7 +1449,7 @@ namespace tut } template<> template<> - void object::test<30>() + void object::test<31>() { set_test_name("listener call to void function"); DispatchResult service; @@ -1452,7 +1466,7 @@ namespace tut } template<> template<> - void object::test<31>() + void object::test<32>() { set_test_name("listener call to string function"); DispatchResult service; @@ -1468,7 +1482,7 @@ namespace tut } template<> template<> - void object::test<32>() + void object::test<33>() { set_test_name("listener call to map function"); DispatchResult service; @@ -1485,7 +1499,7 @@ namespace tut } template<> template<> - void object::test<33>() + void object::test<34>() { set_test_name("batched map success"); DispatchResult service; @@ -1512,7 +1526,7 @@ namespace tut } template<> template<> - void object::test<34>() + void object::test<35>() { set_test_name("batched map error"); DispatchResult service; @@ -1545,7 +1559,7 @@ namespace tut } template<> template<> - void object::test<35>() + void object::test<36>() { set_test_name("batched map exception"); DispatchResult service; @@ -1568,7 +1582,7 @@ namespace tut } template<> template<> - void object::test<36>() + void object::test<37>() { set_test_name("batched array success"); DispatchResult service; @@ -1602,7 +1616,7 @@ namespace tut } template<> template<> - void object::test<37>() + void object::test<38>() { set_test_name("batched array error"); DispatchResult service; @@ -1633,7 +1647,7 @@ namespace tut } template<> template<> - void object::test<38>() + void object::test<39>() { set_test_name("batched array exception"); DispatchResult service; -- cgit v1.2.3 From c747ff0925fb85147a96745bb55e66e7e8004fd8 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sat, 21 Jan 2023 11:13:04 -0500 Subject: DRTVWR-558: Enrich LLEventDispatcher::callFail() with current call. Now an LLEventAPI subclass method can call callFail(...) to report an error, and the error will be annotated with the leaf class name, the instance name and the way the method was reached. The enriched error message will be logged and either sent back to the invoker or propagated as an exception, depending on the invocation tactic. In other words, a business method can use callFail() to Do The Right Thing according to the LLEventDispatcher contract. Introduce a nested SetState RAII class to set and clear transient state. SetState's constructor accepts variadic stringize() arguments. The resulting message is passed to LLEventDispatcher::setState(), which requires a SetState reference because ONLY SetState should call setState(): state data really is intended to be transient. SetState guarantees it will be cleared every time it's set. setState() respects previously-set transient state. If a call from an inner function finds that transient state was already set by some ancestor, it ignores the call and informs the caller by returning false. This lets a given SetState instance recognize whether it is responsible for clearing the current transient state. operator<<(std::ostream&, const LLEventDispatcher&) now appends getState() to the data reported by streaming *this. Non-static LLEventDispatcher::callFail() already prepends *this to the reported error message. Transient state is managed by a fiber_specific_ptr, since different threads and even different fibers within a thread might be concurrently performing different operations on the same LLEventDispatcher. Introduce a back pointer to the parent LLEventDispatcher in DispatchEntry. Populate it with a new constructor parameter, propagated through every subclass constructor. Hoist ParamsDispatchEntry::callFail() up into its DispatchEntry base class. Make it call non-static LLEventDispatcher:: callFail(), which prepends the reported error with instance and transient state info. Use DispatchEntry::callFail() in LLSDDispatchEntry::call(), instead of redundantly calling LLEventDispatcher::callFail(). Similarly, introduce an LLEventDispatcher back pointer into LLSDArgsMapper for use by its own callFail() method. The above should (!) eliminate the need to replicate LLEventDispatcher instance info into every helper object's descriptive strings. In particular, since the previous info was stored in each object by its constructor, it couldn't report associated transient information about how the subject callable was actually reached. Traversing a back pointer to the live LLEventDispatcher instance gets us the most current state. Make the internal three-argument LLEventDispatcher::try_call() method, which implements each of the operator()() and public try_call() methods, use SetState to append "[name]" (for explicit operator()(name, event) calls) or "[key=name]" (for implicit operator()(event) calls) to streamed *this. In the new LLDispatchListener request array and request map operations, use SetState to indicate the current entry in the array or map, overriding the lower-level state set by three-argument LLEventDispatcher::try_call(). (cherry picked from commit 2f8d7d20f43ab411ea0fe8b756cb696954acfb3e) --- indra/llcommon/lleventdispatcher.cpp | 118 ++++++++++++++++++++++------------- indra/llcommon/lleventdispatcher.h | 54 +++++++++++++++- 2 files changed, 129 insertions(+), 43 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index caff854753..0079b9ce36 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -103,7 +103,8 @@ class LL_COMMON_API LLEventDispatcher::LLSDArgsMapper public: /// Accept description of function: function name, param names, param /// default values - LLSDArgsMapper(const std::string& function, const LLSD& names, const LLSD& defaults); + LLSDArgsMapper(LLEventDispatcher* parent, const std::string& function, + const LLSD& names, const LLSD& defaults); /// Given arguments map, return LLSD::Array of parameter values, or /// trigger error. @@ -114,6 +115,9 @@ private: template void callFail(ARGS&&... args) const; + // store a plain dumb back-pointer because we don't have to manage the + // parent LLEventDispatcher's lifespan + LLEventDispatcher* _parent; // The function-name string is purely descriptive. We want error messages // to be able to indicate which function's LLSDArgsMapper has the problem. std::string _function; @@ -132,9 +136,11 @@ private: FilledVector _has_dft; }; -LLEventDispatcher::LLSDArgsMapper::LLSDArgsMapper(const std::string& function, +LLEventDispatcher::LLSDArgsMapper::LLSDArgsMapper(LLEventDispatcher* parent, + const std::string& function, const LLSD& names, const LLSD& defaults): + _parent(parent), _function(function), _names(names), _has_dft(names.size()) @@ -334,7 +340,7 @@ std::string LLEventDispatcher::LLSDArgsMapper::formatlist(const LLSD& list) template void LLEventDispatcher::LLSDArgsMapper::callFail(ARGS&&... args) const { - LLEventDispatcher::sCallFail + _parent->callFail (_function, std::forward(args)...); } @@ -356,7 +362,8 @@ LLEventDispatcher::~LLEventDispatcher() { } -LLEventDispatcher::DispatchEntry::DispatchEntry(const std::string& desc): +LLEventDispatcher::DispatchEntry::DispatchEntry(LLEventDispatcher* parent, const std::string& desc): + mParent(parent), mDesc(desc) {} @@ -365,8 +372,9 @@ LLEventDispatcher::DispatchEntry::DispatchEntry(const std::string& desc): */ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchEntry { - LLSDDispatchEntry(const std::string& desc, const Callable& func, const LLSD& required): - DispatchEntry(desc), + LLSDDispatchEntry(LLEventDispatcher* parent, const std::string& desc, + const Callable& func, const LLSD& required): + DispatchEntry(parent, desc), mFunc(func), mRequired(required) {} @@ -380,8 +388,7 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE std::string mismatch(llsd_matches(mRequired, event)); if (! mismatch.empty()) { - LLEventDispatcher::sCallFail - (desc, ": bad request: ", mismatch); + return callFail(desc, ": bad request: ", mismatch); } // Event syntax looks good, go for it! return mFunc(event); @@ -400,9 +407,9 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE */ struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::DispatchEntry { - ParamsDispatchEntry(const std::string& name, const std::string& desc, - const invoker_function& func): - DispatchEntry(desc), + ParamsDispatchEntry(LLEventDispatcher* parent, const std::string& name, + const std::string& desc, const invoker_function& func): + DispatchEntry(parent, desc), mName(name), mInvoker(func) {} @@ -422,14 +429,6 @@ struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::Dispatc return callFail(err.what()); } } - - template - LLSD callFail(ARGS&&... args) const - { - LLEventDispatcher::sCallFail(mName, ": ", std::forward(args)...); - // pacify the compiler - return {}; - } }; /** @@ -438,9 +437,10 @@ struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::Dispatc */ struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry { - ArrayParamsDispatchEntry(const std::string& name, const std::string& desc, - const invoker_function& func, LLSD::Integer arity): - ParamsDispatchEntry(name, desc, func), + ArrayParamsDispatchEntry(LLEventDispatcher* parent, const std::string& name, + const std::string& desc, const invoker_function& func, + LLSD::Integer arity): + ParamsDispatchEntry(parent, name, desc, func), mArity(arity) {} @@ -503,11 +503,11 @@ struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::Pa */ struct LLEventDispatcher::MapParamsDispatchEntry: public LLEventDispatcher::ParamsDispatchEntry { - MapParamsDispatchEntry(const std::string& name, const std::string& desc, - const invoker_function& func, + MapParamsDispatchEntry(LLEventDispatcher* parent, const std::string& name, + const std::string& desc, const invoker_function& func, const LLSD& params, const LLSD& defaults): - ParamsDispatchEntry(name, desc, func), - mMapper(name, params, defaults), + ParamsDispatchEntry(parent, name, desc, func), + mMapper(parent, name, params, defaults), mRequired(LLSD::emptyMap()) { // Build the set of all param keys, then delete the ones that are @@ -581,11 +581,9 @@ void LLEventDispatcher::addArrayParamsDispatchEntry(const std::string& name, const invoker_function& invoker, LLSD::Integer arity) { - // The first parameter to ArrayParamsDispatchEntry is solely for error - // messages. Identify our instance and this entry. mDispatch.emplace( name, - new ArrayParamsDispatchEntry(stringize(*this, '[', name, ']'), desc, invoker, arity)); + new ArrayParamsDispatchEntry(this, "", desc, invoker, arity)); } void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name, @@ -597,15 +595,14 @@ void LLEventDispatcher::addMapParamsDispatchEntry(const std::string& name, // Pass instance info as well as this entry name for error messages. mDispatch.emplace( name, - new MapParamsDispatchEntry(stringize(*this, '[', name, ']'), - desc, invoker, params, defaults)); + new MapParamsDispatchEntry(this, "", desc, invoker, params, defaults)); } /// Register a callable by name void LLEventDispatcher::addLLSD(const std::string& name, const std::string& desc, const Callable& callable, const LLSD& required) { - mDispatch.emplace(name, new LLSDDispatchEntry(desc, callable, required)); + mDispatch.emplace(name, new LLSDDispatchEntry(this, desc, callable, required)); } /// Unregister a callable @@ -682,7 +679,7 @@ LLSD LLEventDispatcher::try_call(const std::string& key, const std::string& name DispatchMap::const_iterator found = mDispatch.find(name); if (found == mDispatch.end()) { - // Here we were passed a valid name, but there's no registered + // Here we were passed a non-empty name, but there's no registered // callable with that name. This is the one case in which we throw // DispatchMissing instead of the generic DispatchError. // Distinguish the public method by which our caller reached here: @@ -699,8 +696,10 @@ LLSD LLEventDispatcher::try_call(const std::string& key, const std::string& name } // Found the name, so it's plausible to even attempt the call. - return found->second->call(stringize(*this, " calling ", std::quoted(name)), - event, (! key.empty()), mArgskey); + const char* delim = (key.empty()? "" : "="); + // append either "[key=name]" or just "[name]" + SetState transient(this, '[', key, delim, name, ']'); + return found->second->call("", event, (! key.empty()), mArgskey); } template @@ -735,7 +734,34 @@ LLSD LLEventDispatcher::getMetadata(const std::string& name) const std::ostream& operator<<(std::ostream& out, const LLEventDispatcher& self) { // If we're a subclass of LLEventDispatcher, e.g. LLEventAPI, report that. - return out << LLError::Log::classname(self) << '(' << self.mDesc << ')'; + // Also report whatever transient state is active. + return out << LLError::Log::classname(self) << '(' << self.mDesc << ')' + << self.getState(); +} + +std::string LLEventDispatcher::getState() const +{ + // default value of fiber_specific_ptr is nullptr, and ~SetState() reverts + // to that; infer empty string + if (! mState.get()) + return {}; + else + return *mState; +} + +bool LLEventDispatcher::setState(SetState&, const std::string& state) const +{ + // If SetState is instantiated at multiple levels of function call, ignore + // the lower-level call because the outer call presumably provides more + // context. + if (mState.get()) + return false; + + // Pass us empty string (a la ~SetState()) to reset to nullptr, else take + // a heap copy of the passed state string so we can delete it on + // subsequent reset(). + mState.reset(state.empty()? nullptr : new std::string(state)); + return true; } /***************************************************************************** @@ -802,6 +828,8 @@ void LLDispatchListener::call_map(const LLSD& reqmap, const LLSD& event) const { // LLSD map containing returned values LLSD result; + // cache dispatch key + std::string key{ getDispatchKey() }; // collect any error messages here std::ostringstream errors; const char* delim = ""; @@ -812,6 +840,9 @@ void LLDispatchListener::call_map(const LLSD& reqmap, const LLSD& event) const const LLSD& args{ pair.second }; try { + // in case of errors, tell user the dispatch key, the fact that + // we're processing a request map and the current key in that map + SetState(this, '[', key, '[', name, "]]"); // With this form, capture return value even if undefined: // presence of the key in the response map can be used to detect // which request keys succeeded. @@ -819,10 +850,8 @@ void LLDispatchListener::call_map(const LLSD& reqmap, const LLSD& event) const } catch (const DispatchError& err) { - // collect message in 'errors', with hint as to which request map - // entry failed - errors << delim << err.what() << " (" << getDispatchKey() - << '[' << name << "])"; + // collect message in 'errors' + errors << delim << err.what(); delim = "\n"; } } @@ -850,6 +879,8 @@ void LLDispatchListener::call_array(const LLSD& reqarray, const LLSD& event) con { // LLSD array containing returned values LLSD results; + // cache the dispatch key + std::string key{ getDispatchKey() }; // arguments array, if present -- const because, if it's shorter than // reqarray, we don't want to grow it const LLSD argsarray{ event[getArgsKey()] }; @@ -883,13 +914,16 @@ void LLDispatchListener::call_array(const LLSD& reqarray, const LLSD& event) con // reqentry is one of the valid forms, got name and args try { + // in case of errors, tell user the dispatch key, the fact that + // we're processing a request array, the current entry in that + // array and the corresponding callable name + SetState(this, '[', key, '[', i, "]=", name, ']'); // With this form, capture return value even if undefined results.append((*this)(name, args)); } catch (const DispatchError& err) { - // append hint as to which requentry produced the error - error = stringize(err.what(), " (", getDispatchKey(), '[', i, ']'); + error = err.what(); break; } } diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 789a59459c..2c5c62dd50 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -32,6 +32,7 @@ #if ! defined(LL_LLEVENTDISPATCHER_H) #define LL_LLEVENTDISPATCHER_H +#include #include #include #include // until C++17, when we get std::is_invocable @@ -460,14 +461,26 @@ public: private: struct DispatchEntry { - DispatchEntry(const std::string& desc); + DispatchEntry(LLEventDispatcher* parent, const std::string& desc); virtual ~DispatchEntry() {} // suppress MSVC warning, sigh + // store a plain dumb back-pointer because the parent + // LLEventDispatcher manages the lifespan of each DispatchEntry + // subclass instance -- not the other way around + LLEventDispatcher* mParent; std::string mDesc; virtual LLSD call(const std::string& desc, const LLSD& event, bool fromMap, const std::string& argskey) const = 0; virtual LLSD addMetadata(LLSD) const = 0; + + template + LLSD callFail(ARGS&&... args) const + { + mParent->callFail(std::forward(args)...); + // pacify the compiler + return {}; + } }; typedef std::map > DispatchMap; @@ -561,9 +574,48 @@ protected: static void sCallFail(ARGS&&... args); + // Manage transient state, e.g. which registered callable we're attempting + // to call, for error reporting + class SetState + { + public: + template + SetState(const LLEventDispatcher* self, ARGS&&... args): + mSelf(self) + { + mSet = mSelf->setState(*this, stringize(std::forward(args)...)); + } + // RAII class: forbid both copy and move + SetState(const SetState&) = delete; + SetState(SetState&&) = delete; + SetState& operator=(const SetState&) = delete; + SetState& operator=(SetState&&) = delete; + virtual ~SetState() + { + // if we're the ones who succeeded in setting state, clear it + if (mSet) + { + mSelf->setState(*this, {}); + } + } + + private: + const LLEventDispatcher* mSelf; + bool mSet; + }; + private: std::string mDesc, mKey, mArgskey; DispatchMap mDispatch; + // transient state: must be fiber_specific since multiple threads and/or + // multiple fibers may be calling concurrently. Make it mutable so we can + // use SetState even within const methods. + mutable boost::fibers::fiber_specific_ptr mState; + + std::string getState() const; + // setState() requires SetState& because only the SetState class should + // call it. Make it const so we can use SetState even within const methods. + bool setState(SetState&, const std::string& state) const; static NameDesc makeNameDesc(const DispatchMap::value_type& item) { -- cgit v1.2.3 From 2eb0ea9593d0e299445d2e1dde711bfe5072542e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 23 Jan 2023 10:51:33 -0500 Subject: DRTVWR-558: Nail down LLDispatchListener exception handling for exceptions other than those thrown by base-class LLEventDispatcher. Explain in LLDispatchListener Doxygen comments that for a request lacking a "reply" key, any exception is allowed to propagate because it's likely to reach the post() call that triggered the exception in the first place. For batch LLDispatchListener operations, catch not only LLEventDispatcher:: DispatchError exceptions but any std::exception, so we can collect them to report to the invoker. "Gotta catch 'em all!" Make LLLeap catch any std::exception thrown by processing a request from the plugin child process, log it and send a reply to the plugin. No plugin should be allowed to crash the viewer. (cherry picked from commit 94e10fd039b79f71ed8d7e10807b6e4eebd1928c) --- indra/llcommon/lleventdispatcher.cpp | 15 ++++++++++----- indra/llcommon/lleventdispatcher.h | 5 ++++- indra/llcommon/llleap.cpp | 27 ++++++++++++++++++++++----- 3 files changed, 36 insertions(+), 11 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index 0079b9ce36..a4c0ed3766 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -848,10 +848,12 @@ void LLDispatchListener::call_map(const LLSD& reqmap, const LLSD& event) const // which request keys succeeded. result[name] = (*this)(name, args); } - catch (const DispatchError& err) + catch (const std::exception& err) { - // collect message in 'errors' - errors << delim << err.what(); + // Catch not only DispatchError, but any C++ exception thrown by + // the target callable. Collect exception name and message in + // 'errors'. + errors << delim << LLError::Log::classname(err) << ": " << err.what(); delim = "\n"; } } @@ -921,9 +923,12 @@ void LLDispatchListener::call_array(const LLSD& reqarray, const LLSD& event) con // With this form, capture return value even if undefined results.append((*this)(name, args)); } - catch (const DispatchError& err) + catch (const std::exception& err) { - error = err.what(); + // Catch not only DispatchError, but any C++ exception thrown by + // the target callable. Report the exception class as well as the + // error string. + error = stringize(LLError::Log::classname(err), ": ", err.what()); break; } } diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 2c5c62dd50..4ca8a7c735 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -747,7 +747,10 @@ LLEventDispatcher::make_invoker(Method f, const InstanceGetter& getter) * "reply" is present, LLDispatchListener will send a response map to the * specified LLEventPump containing an "error" key whose value is the relevant * error message. If "reply" is not present, the DispatchError exception will - * propagate. + * propagate. Since LLDispatchListener bundles an LLEventStream, which + * attempts the call immediately on receiving the post() call, there's a + * reasonable chance that the exception will highlight the post() call that + * triggered the error. * * If LLDispatchListener successfully calls the target callable, but no * "reply" key is present, any value returned by that callable is discarded. diff --git a/indra/llcommon/llleap.cpp b/indra/llcommon/llleap.cpp index c87c0758fe..abbc4185c6 100644 --- a/indra/llcommon/llleap.cpp +++ b/indra/llcommon/llleap.cpp @@ -327,11 +327,28 @@ public: } else { - // The LLSD object we got from our stream contains the keys we - // need. - LLEventPumps::instance().obtain(data["pump"]).post(data["data"]); - // Block calls to this method; resetting mBlocker unblocks calls - // to the other method. + try + { + // The LLSD object we got from our stream contains the + // keys we need. + LLEventPumps::instance().obtain(data["pump"]).post(data["data"]); + } + catch (const std::exception& err) + { + // No plugin should be allowed to crash the viewer by + // driving an exception -- intentionally or not. + LOG_UNHANDLED_EXCEPTION(stringize("handling request ", data)); + // Whether or not the plugin added a "reply" key to the + // request, send a reply. We happen to know who originated + // this request, and the reply LLEventPump of interest. + // Not our problem if the plugin ignores the reply event. + data["reply"] = mReplyPump.getName(); + sendReply(llsd::map("error", + stringize(LLError::Log::classname(err), ": ", err.what())), + data); + } + // Block calls to this method; resetting mBlocker unblocks + // calls to the other method. mBlocker.reset(new LLEventPump::Blocker(mStdoutDataConnection)); // Go check for any more pending events in the buffer. if (childout.size()) -- cgit v1.2.3 From 7c79d7a7d4d5cb1e39293cdc98fd972be5bd3012 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 24 Jan 2023 13:31:50 -0500 Subject: DRTVWR-558: Fix merge glitch: missing LLEventDispatcher::addFail() (cherry picked from commit 3be250da90dd3d361df713056b881e017684e2b3) --- indra/llcommon/lleventdispatcher.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index a4c0ed3766..5d18d8f6c4 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -605,6 +605,14 @@ void LLEventDispatcher::addLLSD(const std::string& name, const std::string& desc mDispatch.emplace(name, new LLSDDispatchEntry(this, desc, callable, required)); } +void LLEventDispatcher::addFail(const std::string& name, const char* classname) const +{ + LL_ERRS("LLEventDispatcher") << "LLEventDispatcher(" << mDesc << ")::add(" << name + << "): " << LLError::Log::demangle(classname) + << " is not a subclass of LLEventDispatcher" + << LL_ENDL; +} + /// Unregister a callable bool LLEventDispatcher::remove(const std::string& name) { -- cgit v1.2.3 From d2738b60e2dfa255e504247f2b5009bc1dc24954 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 24 Jan 2023 13:34:42 -0500 Subject: DRTVWR-558: Fix const-ness glitch in LL::apply(func, tuple) std::get(const tuple) injects const into the type of each returned tuple element. Need to get a non-const ref to the tuple param to get the true type. (cherry picked from commit 6dda39065d3ee231998cb8a2896f94e8a45c9a82) --- indra/llcommon/apply.h | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/apply.h b/indra/llcommon/apply.h index 123813bcec..0009dd1f80 100644 --- a/indra/llcommon/apply.h +++ b/indra/llcommon/apply.h @@ -74,7 +74,7 @@ template::type = 0 > auto invoke(Fn&& f, Args&&... args) { - return std::mem_fn(f)(std::forward(args)...); + return std::mem_fn(std::forward(f))(std::forward(args)...); } template auto apply_impl(CALLABLE&& func, const std::tuple& args, std::index_sequence) { + // We accept const std::tuple& so a caller can construct an tuple on the + // fly. But std::get(const tuple) adds a const qualifier to everything + // it extracts. Get a non-const ref to this tuple so we can extract + // without the extraneous const. + auto& non_const_args{ const_cast&>(args) }; + // call func(unpacked args) - return invoke(std::forward(func), std::get(args)...); + return invoke(std::forward(func), + std::forward(std::get(non_const_args))...); } template -- cgit v1.2.3 From 25ee39dcbe7277552d8eee1ef5ad5c367f5763f9 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 24 Jan 2023 13:41:02 -0500 Subject: DRTVWR-558: Fix LLEventDispatcher::addMethod() for LazyEventAPI. addMethod() was using dynamic_cast(this) and testing for nullptr to decide whether the class owning the passed method is, or is not, a subclass of LLEventDispatcher. The trouble is that it doesn't work for the deferred add() calls queued by LazyEventAPI: the dynamic_cast was always returning nullptr. static_cast works better, but that leaves us with the problem we were trying to solve with dynamic_cast: what if the target class really isn't a subclass? Use std::is_base_of to pick which of two addMethod() overloads to invoke, one of which calls addFail(). (cherry picked from commit a4d520aa5d023d80cfeec4f40c3464b54cbcfc5b) --- indra/llcommon/lleventdispatcher.h | 51 ++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 19 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 4ca8a7c735..db67d1b361 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -517,27 +517,40 @@ private: const Callable& callable, const LLSD& required); - template + template ::value, + bool + >::type=true> void addMethod(const std::string& name, const std::string& desc, const METHOD& method, const LLSD& required) { - CLASS* downcast = dynamic_cast(this); - if (! downcast) - { - addFail(name, typeid(CLASS).name()); - } - else - { - add(name, - desc, - Callable(LL::make_always_return( - [downcast, method] - (const LLSD& args) - { - return (downcast->*method)(args); - })), - required); - } + // Why two overloaded addMethod() methods, discriminated with + // std::is_base_of? It might seem simpler to use dynamic_cast and test + // for nullptr. The trouble is that it doesn't work for LazyEventAPI + // deferred registration: we get nullptr even for a method of an + // LLEventAPI subclass. + CLASS* downcast = static_cast(this); + add(name, + desc, + Callable(LL::make_always_return( + [downcast, method] + (const LLSD& args) + { + return (downcast->*method)(args); + })), + required); + } + + template ::value, + bool + >::type=true> + void addMethod(const std::string& name, const std::string& desc, + const METHOD&, const LLSD&) + { + addFail(name, typeid(CLASS).name()); } template @@ -562,7 +575,7 @@ private: template void addV(const std::string& name, const std::string& desc, Function f); - void addFail(const std::string& name, const std::string& classname) const; + void addFail(const std::string& name, const char* classname) const; LLSD try_call(const std::string& key, const std::string& name, const LLSD& event) const; -- cgit v1.2.3 From 3fce3d14d6b8e367f4136efbfe87fcfcb23a4c8e Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 7 Mar 2023 17:39:03 -0500 Subject: DRTVWR-558: Avoid extra copy of getMetadata() LLSD map. (cherry picked from commit 2c1253c8ed2a1648317e6edd768b3fda00c56ce2) --- indra/llcommon/lleventdispatcher.cpp | 20 ++++++++------------ indra/llcommon/lleventdispatcher.h | 2 +- 2 files changed, 9 insertions(+), 13 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index 5d18d8f6c4..99e2e74376 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -394,10 +394,9 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE return mFunc(event); } - LLSD addMetadata(LLSD meta) const override + LLSD getMetadata() const override { - meta["required"] = mRequired; - return meta; + return llsd::map("required", mRequired); } }; @@ -485,15 +484,14 @@ struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::Pa return ParamsDispatchEntry::call(desc, args, fromMap, argskey); } - LLSD addMetadata(LLSD meta) const override + LLSD getMetadata() const override { LLSD array(LLSD::emptyArray()); // Resize to number of arguments required if (mArity) array[mArity - 1] = LLSD(); llassert_always(array.size() == mArity); - meta["required"] = array; - return meta; + return llsd::map("required", array); } }; @@ -568,11 +566,9 @@ struct LLEventDispatcher::MapParamsDispatchEntry: public LLEventDispatcher::Para return ParamsDispatchEntry::call(desc, mMapper.map(args), fromMap, argskey); } - LLSD addMetadata(LLSD meta) const override + LLSD getMetadata() const override { - meta["required"] = mRequired; - meta["optional"] = mOptional; - return meta; + return llsd::map("required", mRequired, "optional", mOptional); } }; @@ -733,10 +729,10 @@ LLSD LLEventDispatcher::getMetadata(const std::string& name) const { return LLSD(); } - LLSD meta; + LLSD meta{ found->second->getMetadata() }; meta["name"] = name; meta["desc"] = found->second->mDesc; - return found->second->addMetadata(meta); + return meta; } std::ostream& operator<<(std::ostream& out, const LLEventDispatcher& self) diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index db67d1b361..939e3730e1 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -472,7 +472,7 @@ private: virtual LLSD call(const std::string& desc, const LLSD& event, bool fromMap, const std::string& argskey) const = 0; - virtual LLSD addMetadata(LLSD) const = 0; + virtual LLSD getMetadata() const = 0; template LLSD callFail(ARGS&&... args) const -- cgit v1.2.3 From 7d1a3d70373f2988510eccf7dd8f9bd2f6c2694a Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 13 Jul 2023 15:58:37 -0400 Subject: DRTVWR-558: Fix a few lleventdispatcher_test merge glitches. --- indra/llcommon/tests/lleventdispatcher_test.cpp | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 2a3644e2e1..0f27211d9e 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -754,7 +754,7 @@ namespace tut set_test_name("map-style registration with non-array params"); // Pass "param names" as scalar or as map LLSD attempts(llsd::array(17, LLSDMap("pi", 3.14)("two", 2))); - foreach(LLSD ae, inArray(attempts)) + for (LLSD ae: inArray(attempts)) { std::string threw = catch_what([this, &ae](){ work.add("freena_err", "freena", freena, ae); @@ -829,7 +829,7 @@ namespace tut { set_test_name("query Callables with/out required params"); LLSD names(llsd::array("free1", "Dmethod1", "Dcmethod1", "method1")); - foreach(LLSD nm, inArray(names)) + for (LLSD nm: inArray(names)) { LLSD metadata(getMetadata(nm)); ensure_equals("name mismatch", metadata["name"], nm); @@ -858,7 +858,7 @@ namespace tut (5, llsd::array("freena_array", "smethodna_array", "methodna_array")), llsd::array (5, llsd::array("freenb_array", "smethodnb_array", "methodnb_array")))); - foreach(LLSD ae, inArray(expected)) + for (LLSD ae: inArray(expected)) { LLSD::Integer arity(ae[0].asInteger()); LLSD names(ae[1]); @@ -884,7 +884,7 @@ namespace tut // - (Free function | non-static method), map style, no params (ergo // no defaults) LLSD names(llsd::array("free0_map", "smethod0_map", "method0_map")); - foreach(LLSD nm, inArray(names)) + for (LLSD nm: inArray(names)) { LLSD metadata(getMetadata(nm)); ensure_equals("name mismatch", metadata["name"], nm); @@ -914,7 +914,7 @@ namespace tut llsd::array("smethodnb_map_adft", "smethodnb_map_mdft"), llsd::array("methodna_map_adft", "methodna_map_mdft"), llsd::array("methodnb_map_adft", "methodnb_map_mdft"))); - foreach(LLSD eq, inArray(equivalences)) + for (LLSD eq: inArray(equivalences)) { LLSD adft(eq[0]); LLSD mdft(eq[1]); @@ -1020,10 +1020,6 @@ namespace tut (llsd::array("freenb_map_mdft", "smethodnb_map_mdft", "methodnb_map_mdft"), llsd::array(LLSD::emptyMap(), dft_map_full["b"])))); // required, optional - llsd::array // group - (llsd::array("freenb_map_mdft", "smethodnb_map_mdft", "methodnb_map_mdft"), - llsd::array(LLSD::emptyMap(), dft_map_full["b"])))); // required, optional - for (LLSD grp: inArray(groups)) { // Internal structure of each group in 'groups': @@ -1156,7 +1152,7 @@ namespace tut ("free0_array", "free0_map", "smethod0_array", "smethod0_map", "method0_array", "method0_map")); - foreach(LLSD name, inArray(names)) + for (LLSD name: inArray(names)) { // Look up the Vars instance for this function. Vars* vars(varsfor(name)); @@ -1316,7 +1312,7 @@ namespace tut "freenb_map_mdft", "smethodnb_map_mdft", "methodnb_map_mdft"))); // Treat (full | overfull) (array | map) the same. LLSD argssets(llsd::array(array_full, array_overfull, map_full, map_overfull)); - foreach(const LLSD& args, inArray(argssets)) + for (const LLSD& args: inArray(argssets)) { for (LLSD::String a: ab) { -- cgit v1.2.3 From 3b46c892719ced98cdb3265801cd5f62353b3ced Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 13 Jul 2023 16:01:56 -0400 Subject: DRTVWR-558: Constrain LL::apply()'s use of std::apply(). Once std::apply() becomes available, 'using std::apply;' isn't correct because the more general template tries to handle the apply(function, vector) case that we explicitly implement below. Have to provide apply(function, tuple) and apply(function, array) signatures that can forward to std::apply(). --- indra/llcommon/apply.h | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/apply.h b/indra/llcommon/apply.h index 0009dd1f80..cf6161ed50 100644 --- a/indra/llcommon/apply.h +++ b/indra/llcommon/apply.h @@ -93,7 +93,14 @@ auto invoke(Fn&& f, Args&&... args) #if __cpp_lib_apply >= 201603L // C++17 implementation -using std::apply; +// We don't just say 'using std::apply;' because that template is too general: +// it also picks up the apply(function, vector) case, which we want to handle +// below. +template +auto apply(CALLABLE&& func, const std::tuple& args) +{ + return std::apply(std::forward(func), args); +} #else // C++14 @@ -124,6 +131,8 @@ auto apply(CALLABLE&& func, const std::tuple& args) std::index_sequence_for{}); } +#endif // C++14 + // per https://stackoverflow.com/a/57510428/5533635 template auto apply(CALLABLE&& func, const std::array& args) @@ -131,8 +140,6 @@ auto apply(CALLABLE&& func, const std::array& args) return apply(std::forward(func), std::tuple_cat(args)); } -#endif // C++14 - /***************************************************************************** * bind_front() *****************************************************************************/ -- cgit v1.2.3 From 0a014776b3361cf6b7126f50f9e7d2f2b2eb04a5 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 24 Jul 2023 17:08:51 -0400 Subject: DRTVWR-587: Pacify VS 2022 specifically. --- indra/llcommon/lleventdispatcher.h | 2 ++ 1 file changed, 2 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 939e3730e1..6c7338efb9 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -478,8 +478,10 @@ private: LLSD callFail(ARGS&&... args) const { mParent->callFail(std::forward(args)...); +#if _MSC_VER < 1930 // pre VS 2022 // pacify the compiler return {}; +#endif // pre VS 2022 } }; typedef std::map > DispatchMap; -- cgit v1.2.3 From 861cf0a5d0590eac2ce6486d94ff3d121f8f775b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 26 Jul 2023 12:36:09 -0400 Subject: DRTVWR-587: Move constexpr arity into lambda that uses it. VC doesn't recognize that a constexpr name doesn't need to be bound into a lambda. However, since it's knowable at compile time, it can be deduced within the innermost lambda. (cherry picked from commit 37c3daff1a565eaafee691dfb57702b6b8f024d6) --- indra/llcommon/lleventdispatcher.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 6c7338efb9..b4a610bf2d 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -724,10 +724,6 @@ template LLEventDispatcher::invoker_function LLEventDispatcher::make_invoker(Method f, const InstanceGetter& getter) { - // function_arity includes its implicit 'this' pointer - constexpr auto arity = LL::function_arity< - typename std::remove_reference::type>::value - 1; - return [f, getter](const LLSD& args) { // always_return() immediately calls the lambda we pass, and @@ -736,6 +732,10 @@ LLEventDispatcher::make_invoker(Method f, const InstanceGetter& getter) [f, getter, args] () { + // function_arity includes its implicit 'this' pointer + constexpr auto arity = LL::function_arity< + typename std::remove_reference::type>::value - 1; + // Use bind_front() to bind the method to (a pointer to) the object // returned by getter(). It's okay to capture and bind a pointer // because this bind_front() object will last only as long as this -- cgit v1.2.3 From 35e8d44e17bb53b01ca8c73d3302d08220f5373c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 27 Jul 2023 08:52:10 -0400 Subject: DRTVWR-587: Tweak LazyEventAPIBase::add() to mollify clang. Both the previous version and this compile and run successfully with Xcode 14.3.1, but our older TeamCity compiler chokes -- so we must iterate remotely, sigh. --- indra/llcommon/lazyeventapi.h | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lazyeventapi.h b/indra/llcommon/lazyeventapi.h index a815b119f0..e2dec8f35b 100644 --- a/indra/llcommon/lazyeventapi.h +++ b/indra/llcommon/lazyeventapi.h @@ -71,25 +71,22 @@ namespace LL mOperations.push_back(std::make_pair(name, desc)); // Use connect_extended() so the lambda is passed its own // connection. + // We can't bind an unexpanded parameter pack into a lambda -- - // shame really. Instead, capture it as a std::tuple and then, in - // the lambda, use apply() to convert back to function args. + // shame really. Instead, capture all our args as a std::tuple and + // then, in the lambda, use apply() to pass to add_trampoline(). mParams.init.connect_extended( - [name, desc, rest = std::make_tuple(std::forward(rest)...)] + [args = std::make_tuple(name, desc, std::forward(rest)...)] (const boost::signals2::connection& conn, LLEventAPI* instance) { // we only need this connection once conn.disconnect(); - // Our add() method distinguishes name and desc because we - // capture them separately. But now, because apply() - // expects a tuple specifying ALL the arguments, expand to - // a tuple including add_trampoline() arguments: instance, - // name, desc, rest. + // apply() expects a tuple specifying ALL the arguments, + // so prepend instance. // apply() can't accept a template per se; it needs a // particular specialization. apply(&LazyEventAPIBase::add_trampoline, - std::tuple_cat(std::make_tuple(instance, name, desc), - rest)); + std::tuple_cat(std::make_tuple(instance), args)); }); } -- cgit v1.2.3 From c406fa7ae97441d1d6e0ea6727c42c8f978fabed Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 27 Jul 2023 09:49:54 -0400 Subject: DRTVWR-587: Try again to address older clang difficulties. --- indra/llcommon/lazyeventapi.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lazyeventapi.h b/indra/llcommon/lazyeventapi.h index e2dec8f35b..8bedb6fe18 100644 --- a/indra/llcommon/lazyeventapi.h +++ b/indra/llcommon/lazyeventapi.h @@ -83,10 +83,11 @@ namespace LL conn.disconnect(); // apply() expects a tuple specifying ALL the arguments, // so prepend instance. + std::tuple full_args{ std::tuple_cat(std::make_tuple(instance), args) }; // apply() can't accept a template per se; it needs a // particular specialization. apply(&LazyEventAPIBase::add_trampoline, - std::tuple_cat(std::make_tuple(instance), args)); + full_args); }); } -- cgit v1.2.3 From edab599edaf67d37a3a2e954371128676a8cf9cc Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 27 Jul 2023 10:31:55 -0400 Subject: DRTVWR-587: Revert "Try again to address older clang difficulties." That wasn't the issue. This is a compiler bug: https://github.com/llvm/llvm-project/issues/41999 https://stackoverflow.com/q/57080425 https://bugs.llvm.org/show_bug.cgi?id=42654 This reverts commit c406fa7ae97441d1d6e0ea6727c42c8f978fabed. --- indra/llcommon/lazyeventapi.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lazyeventapi.h b/indra/llcommon/lazyeventapi.h index 8bedb6fe18..e2dec8f35b 100644 --- a/indra/llcommon/lazyeventapi.h +++ b/indra/llcommon/lazyeventapi.h @@ -83,11 +83,10 @@ namespace LL conn.disconnect(); // apply() expects a tuple specifying ALL the arguments, // so prepend instance. - std::tuple full_args{ std::tuple_cat(std::make_tuple(instance), args) }; // apply() can't accept a template per se; it needs a // particular specialization. apply(&LazyEventAPIBase::add_trampoline, - full_args); + std::tuple_cat(std::make_tuple(instance), args)); }); } -- cgit v1.2.3 From 5000f8bd123a87ffbc321a7e3fed61221c9a4da1 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 27 Jul 2023 10:50:46 -0400 Subject: DRTVWR-587: Try to work around clang bug. --- indra/llcommon/lazyeventapi.h | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lazyeventapi.h b/indra/llcommon/lazyeventapi.h index e2dec8f35b..cc566e35af 100644 --- a/indra/llcommon/lazyeventapi.h +++ b/indra/llcommon/lazyeventapi.h @@ -72,21 +72,24 @@ namespace LL // Use connect_extended() so the lambda is passed its own // connection. + // apply() can't accept a template per se; it needs a particular + // specialization. Specialize out here to work around a clang bug: + // https://github.com/llvm/llvm-project/issues/41999 + auto func{ &LazyEventAPIBase::add_trampoline + }; + // We can't bind an unexpanded parameter pack into a lambda -- // shame really. Instead, capture all our args as a std::tuple and // then, in the lambda, use apply() to pass to add_trampoline(). mParams.init.connect_extended( - [args = std::make_tuple(name, desc, std::forward(rest)...)] + [func, args = std::make_tuple(name, desc, std::forward(rest)...)] (const boost::signals2::connection& conn, LLEventAPI* instance) { // we only need this connection once conn.disconnect(); // apply() expects a tuple specifying ALL the arguments, // so prepend instance. - // apply() can't accept a template per se; it needs a - // particular specialization. - apply(&LazyEventAPIBase::add_trampoline, - std::tuple_cat(std::make_tuple(instance), args)); + apply(func, std::tuple_cat(std::make_tuple(instance), args)); }); } -- cgit v1.2.3 From 2159da95e0acc15daa7ab18bc17f1d5f60be71cc Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 27 Jul 2023 11:11:53 -0400 Subject: DRTVWR-587: Try harder to work around clang bug. --- indra/llcommon/lazyeventapi.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lazyeventapi.h b/indra/llcommon/lazyeventapi.h index cc566e35af..e36831270b 100644 --- a/indra/llcommon/lazyeventapi.h +++ b/indra/llcommon/lazyeventapi.h @@ -77,12 +77,13 @@ namespace LL // https://github.com/llvm/llvm-project/issues/41999 auto func{ &LazyEventAPIBase::add_trampoline }; - // We can't bind an unexpanded parameter pack into a lambda -- // shame really. Instead, capture all our args as a std::tuple and // then, in the lambda, use apply() to pass to add_trampoline(). + auto args{ std::make_tuple(name, desc, std::forward(rest)...) }; + mParams.init.connect_extended( - [func, args = std::make_tuple(name, desc, std::forward(rest)...)] + [func, args] (const boost::signals2::connection& conn, LLEventAPI* instance) { // we only need this connection once -- cgit v1.2.3 From 1cebf95f13aecf6050cde296bce6696ba791378b Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 27 Jul 2023 12:21:05 -0400 Subject: DRTVWR-587: Disable LazyEventAPI tests on TeamCity Macs. There's a limit to how much time it's worth trying to work around a compiler bug that's already been fixed in newer Xcode. --- indra/llcommon/CMakeLists.txt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index e02f69126e..0df113e2c8 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -298,7 +298,13 @@ if (LL_TESTS) LL_ADD_INTEGRATION_TEST(bitpack "" "${test_libs}") LL_ADD_INTEGRATION_TEST(classic_callback "" "${test_libs}") LL_ADD_INTEGRATION_TEST(commonmisc "" "${test_libs}") - LL_ADD_INTEGRATION_TEST(lazyeventapi "" "${test_libs}") + if (WINDOWS) + # As of 2023-07-27, lazyeventapi.h triggers a bug in older clang, + # unfortunately the version we run on our TeamCity Mac build agent. As we + # move forward, either with an updated TC agent or GitHub builds, remove + # this 'if'. + LL_ADD_INTEGRATION_TEST(lazyeventapi "" "${test_libs}") + endif () LL_ADD_INTEGRATION_TEST(llbase64 "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llcond "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lldate "" "${test_libs}") -- cgit v1.2.3 From 7fcb2bdb05ca1cd6846e6c1a8227a54b2cf0dcc2 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 27 Jul 2023 16:29:41 -0400 Subject: DRTVWR-587: Make LLSDParam simplify type when delegating. LLSDParam is the generic case, when we need to pass LLSDParam adapters to some set of function parameters whose types we don't specifically know. Its templated conversion operator notices the actual parameter type T and delegates conversion to the specific LLSDParam specialization. But when T has picked up references, e.g. somewhere along the way in the LL::apply() machinery, the compiler might not choose the desired conversion because we don't have a sufficiently specific LLSDParam specialization. LLSDParam can address that by using std::decay_t when delegating to the specific LLSDParam specialization. This removes references, const and volatile. --- indra/llcommon/llsdutil.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h index a6fd2fdac2..cd68f938ea 100644 --- a/indra/llcommon/llsdutil.h +++ b/indra/llcommon/llsdutil.h @@ -358,7 +358,7 @@ public: /// otherwise, instantiate a more specific LLSDParam to convert; that /// preserves the existing customization mechanism template - operator T() const { return LLSDParam(value_); } + operator T() const { return LLSDParam>(value_); } }; /** -- cgit v1.2.3 From 6a77d333d3eb876ccd64324c09cf63c0989164ca Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 27 Jul 2023 21:09:50 -0400 Subject: DRTVWR-587: Skip some tests that only fail with older Visual Studio --- indra/llcommon/tests/lleventdispatcher_test.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 0f27211d9e..58469313e9 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -1200,6 +1200,9 @@ namespace tut void object::test<20>() { set_test_name("call array-style functions with right-size arrays"); +#if defined(_MSC_VER) && _MSC_VER <= 1933 + skip("This test fails on VS older than VS2022 ver 17.4"); +#endif std::vector binary; for (size_t h(0x01), i(0); i < 5; h+= 0x22, ++i) { @@ -1238,6 +1241,9 @@ namespace tut void object::test<21>() { set_test_name("verify that passing LLSD() to const char* sends NULL"); +#if defined(_MSC_VER) && _MSC_VER <= 1933 + skip("This test fails on VS older than VS2022 ver 17.4"); +#endif ensure_equals("Vars::cp init", v.cp, ""); work("methodna_map_mdft", LLSDMap("cp", LLSD())); @@ -1251,6 +1257,9 @@ namespace tut template<> template<> void object::test<22>() { +#if defined(_MSC_VER) && _MSC_VER <= 1933 + skip("This test fails on VS older than VS2022 ver 17.4"); +#endif set_test_name("call map-style functions with (full | oversized) (arrays | maps)"); const char binary[] = "\x99\x88\x77\x66\x55"; LLSD array_full(LLSDMap -- cgit v1.2.3 From bfc9772d61cadc88b3fdf6f553c60e73c79e83ed Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 25 Jul 2023 11:18:12 -0400 Subject: DRTVWR-587: Use [[noreturn]] attribute on callFail() methods that unconditionally return. This eliminates the problem of pacifying a compiler that expects a return statement vs. a compiler that detects that callFail() unconditionally throws. Thanks, Ansariel. --- indra/llcommon/lleventdispatcher.cpp | 16 ++++++++-------- indra/llcommon/lleventdispatcher.h | 10 +++------- 2 files changed, 11 insertions(+), 15 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index 99e2e74376..da96de18f7 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -113,7 +113,7 @@ public: private: static std::string formatlist(const LLSD&); template - void callFail(ARGS&&... args) const; + [[noreturn]] void callFail(ARGS&&... args) const; // store a plain dumb back-pointer because we don't have to manage the // parent LLEventDispatcher's lifespan @@ -338,7 +338,7 @@ std::string LLEventDispatcher::LLSDArgsMapper::formatlist(const LLSD& list) } template -void LLEventDispatcher::LLSDArgsMapper::callFail(ARGS&&... args) const +[[noreturn]] void LLEventDispatcher::LLSDArgsMapper::callFail(ARGS&&... args) const { _parent->callFail (_function, std::forward(args)...); @@ -388,7 +388,7 @@ struct LLEventDispatcher::LLSDDispatchEntry: public LLEventDispatcher::DispatchE std::string mismatch(llsd_matches(mRequired, event)); if (! mismatch.empty()) { - return callFail(desc, ": bad request: ", mismatch); + callFail(desc, ": bad request: ", mismatch); } // Event syntax looks good, go for it! return mFunc(event); @@ -425,7 +425,7 @@ struct LLEventDispatcher::ParamsDispatchEntry: public LLEventDispatcher::Dispatc catch (const LL::apply_error& err) { // could hit runtime errors with LL::apply() - return callFail(err.what()); + callFail(err.what()); } } }; @@ -472,11 +472,11 @@ struct LLEventDispatcher::ArrayParamsDispatchEntry: public LLEventDispatcher::Pa // array, we must have an argskey. if (argskey.empty()) { - return callFail("LLEventDispatcher has no args key"); + callFail("LLEventDispatcher has no args key"); } if ((! event.has(argskey))) { - return callFail("missing required key ", std::quoted(argskey)); + callFail("missing required key ", std::quoted(argskey)); } args = event[argskey]; } @@ -708,7 +708,7 @@ LLSD LLEventDispatcher::try_call(const std::string& key, const std::string& name template //static -void LLEventDispatcher::sCallFail(ARGS&&... args) +[[noreturn]] void LLEventDispatcher::sCallFail(ARGS&&... args) { auto error = stringize(std::forward(args)...); LL_WARNS("LLEventDispatcher") << error << LL_ENDL; @@ -716,7 +716,7 @@ void LLEventDispatcher::sCallFail(ARGS&&... args) } template -void LLEventDispatcher::callFail(ARGS&&... args) const +[[noreturn]] void LLEventDispatcher::callFail(ARGS&&... args) const { // Describe this instance in addition to the error itself. sCallFail(*this, ": ", std::forward(args)...); diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index b4a610bf2d..a82bc7a69b 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -475,13 +475,9 @@ private: virtual LLSD getMetadata() const = 0; template - LLSD callFail(ARGS&&... args) const + [[noreturn]] void callFail(ARGS&&... args) const { mParent->callFail(std::forward(args)...); -#if _MSC_VER < 1930 // pre VS 2022 - // pacify the compiler - return {}; -#endif // pre VS 2022 } }; typedef std::map > DispatchMap; @@ -584,9 +580,9 @@ private: protected: // raise specified EXCEPTION with specified stringize(ARGS) template - void callFail(ARGS&&... args) const; + [[noreturn]] void callFail(ARGS&&... args) const; template - static + [[noreturn]] static void sCallFail(ARGS&&... args); // Manage transient state, e.g. which registered callable we're attempting -- cgit v1.2.3 From ef9cb58a9ea6e37f5ca8c61c258560b2ee72c8d8 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Tue, 1 Aug 2023 01:07:51 +0300 Subject: SL-18623 LLAvatarRenderInfoAccountant coroutine crash For unknown reason allocations of these coroutines often crash on client machines. 1. Limit quantity of coros running in parallel by reducing retries and wait time 2. Print out more diagnostic info --- indra/llcommon/llcoros.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp index 70d8dfc8b9..fe2adf051b 100644 --- a/indra/llcommon/llcoros.cpp +++ b/indra/llcommon/llcoros.cpp @@ -282,6 +282,7 @@ std::string LLCoros::launch(const std::string& prefix, const callable_t& callabl catch (std::bad_alloc&) { // Out of memory on stack allocation? + printActiveCoroutines(); LL_ERRS("LLCoros") << "Bad memory allocation in LLCoros::launch(" << prefix << ")!" << LL_ENDL; } -- cgit v1.2.3 From af9fe170894f16f48e1f99c500648b50fbbf5655 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Fri, 15 Sep 2023 01:22:27 +0300 Subject: SL-17135 Apr process creation crash looks like pool regularly gets corrupted, try using separate pool --- indra/llcommon/llapr.cpp | 8 +++++++- indra/llcommon/llprocess.cpp | 19 ++++++++++++++++--- indra/llcommon/llprocess.h | 1 + 3 files changed, 24 insertions(+), 4 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llapr.cpp b/indra/llcommon/llapr.cpp index 435531f86f..69466df2d1 100644 --- a/indra/llcommon/llapr.cpp +++ b/indra/llcommon/llapr.cpp @@ -38,6 +38,12 @@ const S32 FULL_VOLATILE_APR_POOL = 1024 ; //number of references to LLVolatileAP bool gAPRInitialized = false; +int abortfunc(int retcode) +{ + LL_WARNS("APR") << "Allocation failure in apr pool with code " << (S32)retcode << LL_ENDL; + return 0; +} + void ll_init_apr() { // Initialize APR and create the global pool @@ -45,7 +51,7 @@ void ll_init_apr() if (!gAPRPoolp) { - apr_pool_create(&gAPRPoolp, NULL); + apr_pool_create_ex(&gAPRPoolp, NULL, abortfunc, NULL); } if(!LLAPRFile::sAPRFilePoolp) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 97a38ea992..0d65762284 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -529,6 +529,7 @@ LLProcess::LLProcess(const LLSDOrParams& params): // preserve existing semantics, we promise that mAttached defaults to the // same setting as mAutokill. mAttached(params.attached.isProvided()? params.attached : params.autokill), + mPool(NULL), mPipes(NSLOTS) { // Hmm, when you construct a ptr_vector with a size, it merely reserves @@ -549,8 +550,14 @@ LLProcess::LLProcess(const LLSDOrParams& params): mPostend = params.postend; + apr_pool_create(&mPool, gAPRPoolp); + if (!mPool) + { + LLTHROW(LLProcessError(STRINGIZE("failed to create apr pool"))); + } + apr_procattr_t *procattr = NULL; - chkapr(apr_procattr_create(&procattr, gAPRPoolp)); + chkapr(apr_procattr_create(&procattr, mPool)); // IQA-490, CHOP-900: On Windows, ask APR to jump through hoops to // constrain the set of handles passed to the child process. Before we @@ -689,14 +696,14 @@ LLProcess::LLProcess(const LLSDOrParams& params): // one. Hand-expand chkapr() macro so we can fill in the actual command // string instead of the variable names. if (ll_apr_warn_status(apr_proc_create(&mProcess, argv[0], &argv[0], NULL, procattr, - gAPRPoolp))) + mPool))) { LLTHROW(LLProcessError(STRINGIZE(params << " failed"))); } // arrange to call status_callback() apr_proc_other_child_register(&mProcess, &LLProcess::status_callback, this, mProcess.in, - gAPRPoolp); + mPool); // and make sure we poll it once per "mainloop" tick sProcessListener.addPoll(*this); mStatus.mState = RUNNING; @@ -815,6 +822,12 @@ LLProcess::~LLProcess() { kill("destructor"); } + + if (mPool) + { + apr_pool_destroy(mPool); + mPool = NULL; + } } bool LLProcess::kill(const std::string& who) diff --git a/indra/llcommon/llprocess.h b/indra/llcommon/llprocess.h index e3386ad88e..0842f2eb07 100644 --- a/indra/llcommon/llprocess.h +++ b/indra/llcommon/llprocess.h @@ -568,6 +568,7 @@ private: // explicitly want this ptr_vector to be able to store NULLs typedef boost::ptr_vector< boost::nullable > PipeVector; PipeVector mPipes; + apr_pool_t* mPool; }; /// for logging -- cgit v1.2.3 From 34b83e4047b3170e744538580dc8f7ddee387ba7 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Wed, 25 Oct 2023 22:57:57 +0300 Subject: Post merge build fix --- indra/llcommon/tests/apply_test.cpp | 3 +++ 1 file changed, 3 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/apply_test.cpp b/indra/llcommon/tests/apply_test.cpp index 9a17afc18c..56b497e0c8 100644 --- a/indra/llcommon/tests/apply_test.cpp +++ b/indra/llcommon/tests/apply_test.cpp @@ -20,6 +20,9 @@ // other Linden headers #include "llsd.h" #include "llsdutil.h" +#include +#include +#include // for ensure_equals std::ostream& operator<<(std::ostream& out, const std::vector& stringvec) -- cgit v1.2.3 From 2667653d41d3b4799bf319783a884cbac7f826da Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 27 Oct 2023 15:40:20 -0400 Subject: DRTVWR-587: Skip Visual Studio LLSDParam tests for now. They do work fine on clang... unblocking the rest of the team during diagnosis. --- indra/llcommon/tests/lleventdispatcher_test.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 58469313e9..40643172ee 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -1200,8 +1200,8 @@ namespace tut void object::test<20>() { set_test_name("call array-style functions with right-size arrays"); -#if defined(_MSC_VER) && _MSC_VER <= 1933 - skip("This test fails on VS older than VS2022 ver 17.4"); +#if defined(_MSC_VER) + skip("This test fails on VS"); #endif std::vector binary; for (size_t h(0x01), i(0); i < 5; h+= 0x22, ++i) @@ -1241,8 +1241,8 @@ namespace tut void object::test<21>() { set_test_name("verify that passing LLSD() to const char* sends NULL"); -#if defined(_MSC_VER) && _MSC_VER <= 1933 - skip("This test fails on VS older than VS2022 ver 17.4"); +#if defined(_MSC_VER) + skip("This test fails on VS"); #endif ensure_equals("Vars::cp init", v.cp, ""); @@ -1257,8 +1257,8 @@ namespace tut template<> template<> void object::test<22>() { -#if defined(_MSC_VER) && _MSC_VER <= 1933 - skip("This test fails on VS older than VS2022 ver 17.4"); +#if defined(_MSC_VER) + skip("This test fails on VS"); #endif set_test_name("call map-style functions with (full | oversized) (arrays | maps)"); const char binary[] = "\x99\x88\x77\x66\x55"; -- cgit v1.2.3 From f7d2d40b3057f5bc249c88784b35443aad8de7aa Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Sun, 29 Oct 2023 11:56:17 -0400 Subject: DRTVWR-587: Fix LL::apply(function, LLSD array). We define a specialization of LLSDParam to support passing an LLSD object to a const char* function parameter. Needless to remark, passing object.asString().c_str() would be Bad: destroying the temporary std::string returned by asString() would immediately invalidate the pointer returned by its c_str(). But when you pass LLSDParam(object) as the parameter, that specialization itself stores the std::string so the c_str() pointer remains valid as long as the LLSDParam object does. Then there's LLSDParam, used when we don't have the parameter type available to select the LLSDParam specialization. LLSDParam defines a templated conversion operator T() that constructs an LLSDParam to provide the actual parameter value. So far, so good. The trouble was with the implementation of LLSDParam: it constructed a _temporary_ LLSDParam, implicitly called its operator T() and immediately destroyed it. Destroying LLSDParam destroyed its stored string, thus invalidating the c_str() pointer before the target function was entered. Instead, make LLSDParam::operator T() capture each LLSDParam it constructs, extending its lifespan to the lifespan of the LLSDParam instance. For this, derive each LLSDParam specialization from LLSDParamBase, a trivial base class that simply establishes the virtual destructor. We can then capture any specialization as a pointer to LLSDParamBase. Also restore LazyEventAPI tests on Mac. --- indra/llcommon/CMakeLists.txt | 8 +-- indra/llcommon/llsdutil.h | 75 ++++++++++++++++++------- indra/llcommon/tests/lleventdispatcher_test.cpp | 15 ++--- 3 files changed, 60 insertions(+), 38 deletions(-) (limited to 'indra/llcommon') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 0df113e2c8..e02f69126e 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -298,13 +298,7 @@ if (LL_TESTS) LL_ADD_INTEGRATION_TEST(bitpack "" "${test_libs}") LL_ADD_INTEGRATION_TEST(classic_callback "" "${test_libs}") LL_ADD_INTEGRATION_TEST(commonmisc "" "${test_libs}") - if (WINDOWS) - # As of 2023-07-27, lazyeventapi.h triggers a bug in older clang, - # unfortunately the version we run on our TeamCity Mac build agent. As we - # move forward, either with an updated TC agent or GitHub builds, remove - # this 'if'. - LL_ADD_INTEGRATION_TEST(lazyeventapi "" "${test_libs}") - endif () + LL_ADD_INTEGRATION_TEST(lazyeventapi "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llbase64 "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llcond "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lldate "" "${test_libs}") diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h index cd68f938ea..ad54d1b0be 100644 --- a/indra/llcommon/llsdutil.h +++ b/indra/llcommon/llsdutil.h @@ -34,7 +34,9 @@ #include "llsd.h" #include #include +#include // std::shared_ptr #include +#include // U32 LL_COMMON_API LLSD ll_sd_from_U32(const U32); @@ -302,6 +304,11 @@ LLSD map(Ts&&... vs) /***************************************************************************** * LLSDParam *****************************************************************************/ +struct LLSDParamBase +{ + virtual ~LLSDParamBase() {} +}; + /** * LLSDParam is a customization point for passing LLSD values to function * parameters of more or less arbitrary type. LLSD provides a small set of @@ -319,7 +326,7 @@ LLSD map(Ts&&... vs) * @endcode */ template -class LLSDParam +class LLSDParam: public LLSDParamBase { public: /** @@ -327,13 +334,13 @@ public: * value for later retrieval */ LLSDParam(const LLSD& value): - _value(value) + value_(value) {} - operator T() const { return _value; } + operator T() const { return value_; } private: - T _value; + T value_; }; /** @@ -343,10 +350,30 @@ private: * specialization. */ template <> -class LLSDParam +class LLSDParam: public LLSDParamBase { private: LLSD value_; + // LLSDParam::operator T() works by instantiating an LLSDParam on + // demand. Returning that engages LLSDParam::operator T(), producing + // the desired result. But LLSDParam owns a std::string whose + // c_str() is returned by its operator const char*(). If we return a temp + // LLSDParam, the compiler can destroy it right away, as soon + // as we've called operator const char*(). That's a problem! That + // invalidates the const char* we've just passed to the subject function. + // This LLSDParam is presumably guaranteed to survive until the + // subject function has returned, so we must ensure that any constructed + // LLSDParam lives just as long as this LLSDParam does. Putting + // each LLSDParam on the heap and capturing a smart pointer in a vector + // works. We would have liked to use std::unique_ptr, but vector entries + // must be copyable. + // (Alternatively we could assume that every instance of LLSDParam + // will be asked for at most ONE conversion. We could store a scalar + // std::unique_ptr and, when constructing an new LLSDParam, assert that + // the unique_ptr is empty. But some future change in usage patterns, and + // consequent failure of that assertion, would be very mysterious. Instead + // of explaining how to fix it, just fix it now.) + mutable std::vector> converters_; public: LLSDParam(const LLSD& value): value_(value) {} @@ -358,7 +385,15 @@ public: /// otherwise, instantiate a more specific LLSDParam to convert; that /// preserves the existing customization mechanism template - operator T() const { return LLSDParam>(value_); } + operator T() const + { + // capture 'ptr' with the specific subclass type because converters_ + // only stores LLSDParamBase pointers + auto ptr{ std::make_shared>>(value_) }; + // keep the new converter alive until we ourselves are destroyed + converters_.push_back(ptr); + return *ptr; + } }; /** @@ -375,17 +410,17 @@ public: */ #define LLSDParam_for(T, AS) \ template <> \ -class LLSDParam \ +class LLSDParam: public LLSDParamBase \ { \ public: \ LLSDParam(const LLSD& value): \ - _value((T)value.AS()) \ + value_((T)value.AS()) \ {} \ \ - operator T() const { return _value; } \ + operator T() const { return value_; } \ \ private: \ - T _value; \ + T value_; \ } LLSDParam_for(float, asReal); @@ -401,31 +436,31 @@ LLSDParam_for(LLSD::Binary, asBinary); * safely pass an LLSDParam(yourLLSD). */ template <> -class LLSDParam +class LLSDParam: public LLSDParamBase { private: // The difference here is that we store a std::string rather than a const // char*. It's important that the LLSDParam object own the std::string. - std::string _value; + std::string value_; // We don't bother storing the incoming LLSD object, but we do have to - // distinguish whether _value is an empty string because the LLSD object + // distinguish whether value_ is an empty string because the LLSD object // contains an empty string or because it's isUndefined(). - bool _undefined; + bool undefined_; public: LLSDParam(const LLSD& value): - _value(value), - _undefined(value.isUndefined()) + value_(value), + undefined_(value.isUndefined()) {} - // The const char* we retrieve is for storage owned by our _value member. + // The const char* we retrieve is for storage owned by our value_ member. // That's how we guarantee that the const char* is valid for the lifetime // of this LLSDParam object. Constructing your LLSDParam in the argument // list should ensure that the LLSDParam object will persist for the // duration of the function call. operator const char*() const { - if (_undefined) + if (undefined_) { // By default, an isUndefined() LLSD object's asString() method // will produce an empty string. But for a function accepting @@ -435,7 +470,7 @@ public: // case, though, no LLSD value could pass NULL. return NULL; } - return _value.c_str(); + return value_.c_str(); } }; @@ -592,7 +627,7 @@ namespace LL * apply(function, LLSD array) *****************************************************************************/ // validate incoming LLSD blob, and return an LLSD array suitable to pass to -// apply_impl() +// the function of interest LLSD apply_llsd_fix(size_t arity, const LLSD& args); // Derived from https://stackoverflow.com/a/20441189 diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 40643172ee..b0c532887c 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -178,6 +178,7 @@ struct Vars /*-------- Arbitrary-params (non-const, const, static) methods ---------*/ void methodna(NPARAMSa) { + DEBUG; // Because our const char* param cp might be NULL, and because we // intend to capture the value in a std::string, have to distinguish // between the NULL value and any non-NULL value. Use a convention @@ -189,7 +190,7 @@ struct Vars else vcp = std::string("'") + cp + "'"; - debug()("methodna(", b, + this->debug()("methodna(", b, ", ", i, ", ", f, ", ", d, @@ -227,7 +228,8 @@ struct Vars void cmethodna(NPARAMSa) const { - debug()('c', NONL); + DEBUG; + this->debug()('c', NONL); const_cast(this)->methodna(NARGSa); } @@ -1200,9 +1202,6 @@ namespace tut void object::test<20>() { set_test_name("call array-style functions with right-size arrays"); -#if defined(_MSC_VER) - skip("This test fails on VS"); -#endif std::vector binary; for (size_t h(0x01), i(0); i < 5; h+= 0x22, ++i) { @@ -1241,9 +1240,6 @@ namespace tut void object::test<21>() { set_test_name("verify that passing LLSD() to const char* sends NULL"); -#if defined(_MSC_VER) - skip("This test fails on VS"); -#endif ensure_equals("Vars::cp init", v.cp, ""); work("methodna_map_mdft", LLSDMap("cp", LLSD())); @@ -1257,9 +1253,6 @@ namespace tut template<> template<> void object::test<22>() { -#if defined(_MSC_VER) - skip("This test fails on VS"); -#endif set_test_name("call map-style functions with (full | oversized) (arrays | maps)"); const char binary[] = "\x99\x88\x77\x66\x55"; LLSD array_full(LLSDMap -- cgit v1.2.3 From b9b38db5678556e1aa2710a86004dc5a14f97242 Mon Sep 17 00:00:00 2001 From: "Brad Kittenbrink (Brad Linden)" Date: Fri, 28 Jul 2023 16:52:06 -0700 Subject: Fix for SL-19968 objects missing timing bug due to stall during login ensure inventory skeleton loading doesn't block the message system from processing packets. --- indra/llcommon/llsdserialize.cpp | 6 ++++++ indra/llcommon/llsdserialize_xml.cpp | 2 ++ 2 files changed, 8 insertions(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llsdserialize.cpp b/indra/llcommon/llsdserialize.cpp index 3db456ddb3..a475be6293 100644 --- a/indra/llcommon/llsdserialize.cpp +++ b/indra/llcommon/llsdserialize.cpp @@ -475,6 +475,7 @@ LLSDNotationParser::~LLSDNotationParser() // virtual S32 LLSDNotationParser::doParse(std::istream& istr, LLSD& data, S32 max_depth) const { + LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD // map: { string:object, string:object } // array: [ object, object, object ] // undef: ! @@ -734,6 +735,7 @@ S32 LLSDNotationParser::doParse(std::istream& istr, LLSD& data, S32 max_depth) c S32 LLSDNotationParser::parseMap(std::istream& istr, LLSD& map, S32 max_depth) const { + LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD // map: { string:object, string:object } map = LLSD::emptyMap(); S32 parse_count = 0; @@ -794,6 +796,7 @@ S32 LLSDNotationParser::parseMap(std::istream& istr, LLSD& map, S32 max_depth) c S32 LLSDNotationParser::parseArray(std::istream& istr, LLSD& array, S32 max_depth) const { + LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD // array: [ object, object, object ] array = LLSD::emptyArray(); S32 parse_count = 0; @@ -833,6 +836,7 @@ S32 LLSDNotationParser::parseArray(std::istream& istr, LLSD& array, S32 max_dept bool LLSDNotationParser::parseString(std::istream& istr, LLSD& data) const { + LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD std::string value; auto count = deserialize_string(istr, value, mMaxBytesLeft); if(PARSE_FAILURE == count) return false; @@ -843,6 +847,7 @@ bool LLSDNotationParser::parseString(std::istream& istr, LLSD& data) const bool LLSDNotationParser::parseBinary(std::istream& istr, LLSD& data) const { + LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD // binary: b##"ff3120ab1" // or: b(len)"..." @@ -945,6 +950,7 @@ LLSDBinaryParser::~LLSDBinaryParser() // virtual S32 LLSDBinaryParser::doParse(std::istream& istr, LLSD& data, S32 max_depth) const { + LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD /** * Undefined: '!'
* Boolean: '1' for true '0' for false
diff --git a/indra/llcommon/llsdserialize_xml.cpp b/indra/llcommon/llsdserialize_xml.cpp index ac128c9f86..38b11eb32b 100644 --- a/indra/llcommon/llsdserialize_xml.cpp +++ b/indra/llcommon/llsdserialize_xml.cpp @@ -923,6 +923,8 @@ void LLSDXMLParser::parsePart(const char *buf, llssize len) // virtual S32 LLSDXMLParser::doParse(std::istream& input, LLSD& data, S32 max_depth) const { + LL_PROFILE_ZONE_SCOPED_CATEGORY_LLSD + #ifdef XML_PARSER_PERFORMANCE_TESTS XML_Timer timer( &parseTime ); #endif // XML_PARSER_PERFORMANCE_TESTS -- cgit v1.2.3 From dfb44aec706532cd26a1fcc6415cc7fdadec9907 Mon Sep 17 00:00:00 2001 From: Brad Linden Date: Thu, 2 Nov 2023 17:28:10 -0700 Subject: Fix build error from overly fancy tracy macro usage that nobody else is using for DRTVWR-559 --- indra/llcommon/llprofiler.h | 1 + 1 file changed, 1 insertion(+) (limited to 'indra/llcommon') diff --git a/indra/llcommon/llprofiler.h b/indra/llcommon/llprofiler.h index 736a069f49..af5e5777bf 100644 --- a/indra/llcommon/llprofiler.h +++ b/indra/llcommon/llprofiler.h @@ -114,6 +114,7 @@ extern thread_local bool gProfilerEnabled; #define LL_PROFILER_SET_THREAD_NAME( name ) (void)(name) #define LL_RECORD_BLOCK_TIME(name) const LLTrace::BlockTimer& LL_GLUE_TOKENS(block_time_recorder, __LINE__)(LLTrace::timeThisBlock(name)); (void)LL_GLUE_TOKENS(block_time_recorder, __LINE__); #define LL_PROFILE_ZONE_NAMED(name) // LL_PROFILE_ZONE_NAMED is a no-op when Tracy is disabled + #define LL_PROFILE_ZONE_NAMED_COLOR(name,color) // LL_PROFILE_ZONE_NAMED_COLOR is a no-op when Tracy is disabled #define LL_PROFILE_ZONE_SCOPED // LL_PROFILE_ZONE_SCOPED is a no-op when Tracy is disabled #define LL_PROFILE_ZONE_COLOR(name,color) // LL_RECORD_BLOCK_TIME(name) -- cgit v1.2.3