From a2379d68713e210187149c05dd20435d7a244503 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 30 May 2019 15:35:54 -0400 Subject: SL-11216: Add llsd::drill() function to drill into an LLSD blob. We include both const and non-const overloads. The latter returns LLSD&, so you can assign to the located element. In fact we already implemented the non-const logic in a less public form as storeToLLSDPath() in lleventcoro.cpp. Reimplement the latter to use the new llsd::drill() function. --- indra/llcommon/lleventcoro.cpp | 50 +++++------------------------------------- 1 file changed, 5 insertions(+), 45 deletions(-) (limited to 'indra/llcommon/lleventcoro.cpp') diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp index 56367b8f54..43e41f250d 100644 --- a/indra/llcommon/lleventcoro.cpp +++ b/indra/llcommon/lleventcoro.cpp @@ -36,6 +36,7 @@ // external library headers // other Linden headers #include "llsdserialize.h" +#include "llsdutil.h" #include "llerror.h" #include "llcoros.h" #include "llmake.h" @@ -92,57 +93,16 @@ std::string listenerNameForCoro() * In the degenerate case in which @a path is an empty array, @a dest will * @em become @a value rather than @em containing it. */ -void storeToLLSDPath(LLSD& dest, const LLSD& rawPath, const LLSD& value) +void storeToLLSDPath(LLSD& dest, const LLSD& path, const LLSD& value) { - if (rawPath.isUndefined()) + if (path.isUndefined()) { // no-op case return; } - // Arrange to treat rawPath uniformly as an array. If it's not already an - // array, store it as the only entry in one. - LLSD path; - if (rawPath.isArray()) - { - path = rawPath; - } - else - { - path.append(rawPath); - } - - // Need to indicate a current destination -- but that current destination - // needs to change as we step through the path array. Where normally we'd - // use an LLSD& to capture a subscripted LLSD lvalue, this time we must - // instead use a pointer -- since it must be reassigned. - LLSD* pdest = &dest; - - // Now loop through that array - for (LLSD::Integer i = 0; i < path.size(); ++i) - { - if (path[i].isString()) - { - // *pdest is an LLSD map - pdest = &((*pdest)[path[i].asString()]); - } - else if (path[i].isInteger()) - { - // *pdest is an LLSD array - pdest = &((*pdest)[path[i].asInteger()]); - } - else - { - // What do we do with Real or Array or Map or ...? - // As it's a coder error -- not a user error -- rub the coder's - // face in it so it gets fixed. - LL_ERRS("lleventcoro") << "storeToLLSDPath(" << dest << ", " << rawPath << ", " << value - << "): path[" << i << "] bad type " << path[i].type() << LL_ENDL; - } - } - - // Here *pdest is where we should store value. - *pdest = value; + // Drill down to where we should store 'value'. + llsd::drill(dest, path) = value; } /// For LLCoros::Future::make_callback(), the callback has a signature -- cgit v1.2.3 From 66981fab0b3c8dcc3a031d50710a2b24ec6b0603 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 10 May 2018 21:46:07 -0400 Subject: SL-793: Use Boost.Fiber instead of the "dcoroutine" library. Longtime fans will remember that the "dcoroutine" library is a Google Summer of Code project by Giovanni P. Deretta. He originally called it "Boost.Coroutine," and we originally added it to our 3p-boost autobuild package as such. But when the official Boost.Coroutine library came along (with a very different API), and we still needed the API of the GSoC project, we renamed the unofficial one "dcoroutine" to allow coexistence. The "dcoroutine" library had an internal low-level API more or less analogous to Boost.Context. We later introduced an implementation of that internal API based on Boost.Context, a step towards eliminating the GSoC code in favor of official, supported Boost code. However, recent versions of Boost.Context no longer support the API on which we built the shim for "dcoroutine." We started down the path of reimplementing that shim using the current Boost.Context API -- then realized that it's time to bite the bullet and replace the "dcoroutine" API with the Boost.Fiber API, which we've been itching to do for literally years now. Naturally, most of the heavy lifting is in llcoros.{h,cpp} and lleventcoro.{h,cpp} -- which is good: the LLCoros layer abstracts away most of the differences between "dcoroutine" and Boost.Fiber. The one feature Boost.Fiber does not provide is the ability to forcibly terminate some other fiber. Accordingly, disable LLCoros::kill() and LLCoprocedureManager::shutdown(). The only known shutdown() call was in LLCoprocedurePool's destructor. We also took the opportunity to remove postAndSuspend2() and its associated machinery: FutureListener2, LLErrorEvent, errorException(), errorLog(), LLCoroEventPumps. All that dual-LLEventPump stuff was introduced at a time when the Responder pattern was king, and we assumed we'd want to listen on one LLEventPump with the success handler and on another with the error handler. We have never actually used that in practice. Remove associated tests, of course. There is one other semantic difference that necessitates patching a number of tests: with "dcoroutine," fulfilling a future IMMEDIATELY resumes the waiting coroutine. With Boost.Fiber, fulfilling a future merely marks the fiber as ready to resume next time the scheduler gets around to it. To observe the test side effects, we've inserted a number of llcoro::suspend() calls -- also in the main loop. For a long time we retained a single unit test exercising the raw "dcoroutine" API. Remove that. Eliminate llcoro_get_id.{h,cpp}, which provided llcoro::get_id(), which was a hack to emulate fiber-local variables. Since Boost.Fiber has an actual API for that, remove the hack. In fact, use (new alias) LLCoros::local_ptr for LLSingleton's dependency tracking in place of llcoro::get_id(). In CMake land, replace BOOST_COROUTINE_LIBRARY with BOOST_FIBER_LIBRARY. We don't actually use the Boost.Coroutine for anything (though there exist plausible use cases). --- indra/llcommon/lleventcoro.cpp | 268 +++++++++++++---------------------------- 1 file changed, 81 insertions(+), 187 deletions(-) (limited to 'indra/llcommon/lleventcoro.cpp') diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp index 43e41f250d..47d99f0050 100644 --- a/indra/llcommon/lleventcoro.cpp +++ b/indra/llcommon/lleventcoro.cpp @@ -31,18 +31,15 @@ // associated header #include "lleventcoro.h" // STL headers -#include +#include // std headers // external library headers +#include // other Linden headers #include "llsdserialize.h" #include "llsdutil.h" #include "llerror.h" #include "llcoros.h" -#include "llmake.h" -#include "llexception.h" - -#include "lleventfilter.h" namespace { @@ -105,65 +102,47 @@ void storeToLLSDPath(LLSD& dest, const LLSD& path, const LLSD& value) llsd::drill(dest, path) = value; } -/// For LLCoros::Future::make_callback(), the callback has a signature -/// like void callback(LLSD), which isn't a valid LLEventPump listener: such -/// listeners must return bool. -template -class FutureListener -{ -public: - // FutureListener is instantiated on the coroutine stack: the stack, in - // other words, that wants to suspend. - FutureListener(const LISTENER& listener): - mListener(listener), - // Capture the suspending coroutine's flag as a consuming or - // non-consuming listener. - mConsume(LLCoros::get_consuming()) - {} - - // operator()() is called on the main stack: the stack on which the - // expected event is fired. - bool operator()(const LLSD& event) - { - mListener(event); - // tell upstream LLEventPump whether listener consumed - return mConsume; - } - -protected: - LISTENER mListener; - bool mConsume; -}; - } // anonymous void llcoro::suspend() { - // By viewer convention, we post an event on the "mainloop" LLEventPump - // each iteration of the main event-handling loop. So waiting for a single - // event on "mainloop" gives us a one-frame suspend. - suspendUntilEventOn("mainloop"); + boost::this_fiber::yield(); } void llcoro::suspendUntilTimeout(float seconds) { - LLEventTimeout timeout; - - timeout.eventAfter(seconds, LLSD()); - llcoro::suspendUntilEventOn(timeout); + // The fact that we accept non-integer seconds means we should probably + // use granularity finer than one second. However, given the overhead of + // the rest of our processing, it seems silly to use granularity finer + // than a millisecond. + boost::this_fiber::sleep_for(std::chrono::milliseconds(long(seconds * 1000))); } -LLSD llcoro::postAndSuspend(const LLSD& event, const LLEventPumpOrPumpName& requestPump, - const LLEventPumpOrPumpName& replyPump, const LLSD& replyPumpNamePath) +namespace { - // declare the future - LLCoros::Future future; + +LLBoundListener postAndSuspendSetup(const std::string& callerName, + const std::string& listenerName, + LLCoros::Promise& promise, + const LLSD& event, + const LLEventPumpOrPumpName& requestPump, + const LLEventPumpOrPumpName& replyPump, + const LLSD& replyPumpNamePath) +{ + // Get the consuming attribute for THIS coroutine, the one that's about to + // suspend. Don't call get_consuming() in the lambda body: that would + // return the consuming attribute for some other coroutine, most likely + // the main routine. + bool consuming(LLCoros::get_consuming()); // make a callback that will assign a value to the future, and listen on // the specified LLEventPump with that callback - std::string listenerName(listenerNameForCoro()); - LLTempBoundListener connection( + LLBoundListener connection( replyPump.getPump().listen(listenerName, - llmake(future.make_callback()))); + [&promise, consuming](const LLSD& result) + { + promise.set_value(result); + return consuming; + })); // skip the "post" part if requestPump is default-constructed if (requestPump) { @@ -171,7 +150,7 @@ LLSD llcoro::postAndSuspend(const LLSD& event, const LLEventPumpOrPumpName& requ // request event. LLSD modevent(event); storeToLLSDPath(modevent, replyPumpNamePath, replyPump.getPump().getName()); - LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << listenerName + LL_DEBUGS("lleventcoro") << callerName << ": coroutine " << listenerName << " posting to " << requestPump.getPump().getName() << LL_ENDL; @@ -179,158 +158,73 @@ LLSD llcoro::postAndSuspend(const LLSD& event, const LLEventPumpOrPumpName& requ // << ": " << modevent << LL_ENDL; requestPump.getPump().post(modevent); } - LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << listenerName + LL_DEBUGS("lleventcoro") << callerName << ": coroutine " << listenerName << " about to wait on LLEventPump " << replyPump.getPump().getName() << LL_ENDL; - // calling get() on the future makes us wait for it - LLSD value(future.get()); - LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << listenerName - << " resuming with " << value << LL_ENDL; - // returning should disconnect the connection - return value; -} - -LLSD llcoro::suspendUntilEventOnWithTimeout(const LLEventPumpOrPumpName& suspendPumpOrName, - F32 timeoutin, const LLSD &timeoutResult) -{ - /** - * The timeout pump is attached upstream of of the waiting pump and will - * pass the timeout event through it. We CAN NOT attach downstream since - * doing so will cause the suspendPump to fire any waiting events immediately - * and they will be lost. This becomes especially problematic with the - * LLEventTimeout(pump) constructor which will also attempt to fire those - * events using the virtual listen_impl method in the not yet fully constructed - * timeoutPump. - */ - LLEventTimeout timeoutPump; - LLEventPump &suspendPump = suspendPumpOrName.getPump(); - - LLTempBoundListener timeoutListener(timeoutPump.listen(suspendPump.getName(), - boost::bind(&LLEventPump::post, &suspendPump, _1))); - - timeoutPump.eventAfter(timeoutin, timeoutResult); - return llcoro::suspendUntilEventOn(suspendPump); + return connection; } -namespace -{ - -/** - * This helper is specifically for postAndSuspend2(). We use a single future - * object, but we want to listen on two pumps with it. Since we must still - * adapt from the callable constructed by boost::dcoroutines::make_callback() - * (void return) to provide an event listener (bool return), we've adapted - * FutureListener for the purpose. The basic idea is that we construct a - * distinct instance of FutureListener2 -- binding different instance data -- - * for each of the pumps. Then, when a pump delivers an LLSD value to either - * FutureListener2, it can combine that LLSD with its discriminator to feed - * the future object. - * - * DISCRIM is a template argument so we can use llmake() rather than - * having to write our own argument-deducing helper function. - */ -template -class FutureListener2: public FutureListener -{ - typedef FutureListener super; - -public: - // instantiated on coroutine stack: the stack about to suspend - FutureListener2(const LISTENER& listener, DISCRIM discriminator): - super(listener), - mDiscrim(discriminator) - {} - - // called on main stack: the stack on which event is fired - bool operator()(const LLSD& event) - { - // our future object is defined to accept LLEventWithID - super::mListener(LLEventWithID(event, mDiscrim)); - // tell LLEventPump whether or not event was consumed - return super::mConsume; - } - -private: - const DISCRIM mDiscrim; -}; - } // anonymous -namespace llcoro +LLSD llcoro::postAndSuspend(const LLSD& event, const LLEventPumpOrPumpName& requestPump, + const LLEventPumpOrPumpName& replyPump, const LLSD& replyPumpNamePath) { + LLCoros::Promise promise; + std::string listenerName(listenerNameForCoro()); + + // Store connection into an LLTempBoundListener so we implicitly + // disconnect on return from this function. + LLTempBoundListener connection = + postAndSuspendSetup("postAndSuspend()", listenerName, promise, + event, requestPump, replyPump, replyPumpNamePath); -LLEventWithID postAndSuspend2(const LLSD& event, - const LLEventPumpOrPumpName& requestPump, - const LLEventPumpOrPumpName& replyPump0, - const LLEventPumpOrPumpName& replyPump1, - const LLSD& replyPump0NamePath, - const LLSD& replyPump1NamePath) -{ // declare the future - LLCoros::Future future; - // either callback will assign a value to this future; listen on - // each specified LLEventPump with a callback - std::string name(listenerNameForCoro()); - LLTempBoundListener connection0( - replyPump0.getPump().listen( - name + "a", - llmake(future.make_callback(), 0))); - LLTempBoundListener connection1( - replyPump1.getPump().listen( - name + "b", - llmake(future.make_callback(), 1))); - // skip the "post" part if requestPump is default-constructed - if (requestPump) - { - // If either replyPumpNamePath is non-empty, store the corresponding - // replyPump name in the request event. - LLSD modevent(event); - storeToLLSDPath(modevent, replyPump0NamePath, - replyPump0.getPump().getName()); - storeToLLSDPath(modevent, replyPump1NamePath, - replyPump1.getPump().getName()); - LL_DEBUGS("lleventcoro") << "postAndSuspend2(): coroutine " << name - << " posting to " << requestPump.getPump().getName() - << ": " << modevent << LL_ENDL; - requestPump.getPump().post(modevent); - } - LL_DEBUGS("lleventcoro") << "postAndSuspend2(): coroutine " << name - << " about to wait on LLEventPumps " << replyPump0.getPump().getName() - << ", " << replyPump1.getPump().getName() << LL_ENDL; + LLCoros::Future future = LLCoros::getFuture(promise); // calling get() on the future makes us wait for it - LLEventWithID value(future.get()); - LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << name - << " resuming with (" << value.first << ", " << value.second << ")" - << LL_ENDL; - // returning should disconnect both connections + LLSD value(future.get()); + LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << listenerName + << " resuming with " << value << LL_ENDL; + // returning should disconnect the connection return value; } -LLSD errorException(const LLEventWithID& result, const std::string& desc) +LLSD llcoro::postAndSuspendWithTimeout(const LLSD& event, + const LLEventPumpOrPumpName& requestPump, + const LLEventPumpOrPumpName& replyPump, + const LLSD& replyPumpNamePath, + F32 timeout, const LLSD& timeoutResult) { - // If the result arrived on the error pump (pump 1), instead of - // returning it, deliver it via exception. - if (result.second) + LLCoros::Promise promise; + std::string listenerName(listenerNameForCoro()); + + // Store connection into an LLTempBoundListener so we implicitly + // disconnect on return from this function. + LLTempBoundListener connection = + postAndSuspendSetup("postAndSuspendWithTimeout()", listenerName, promise, + event, requestPump, replyPump, replyPumpNamePath); + + // declare the future + LLCoros::Future future = LLCoros::getFuture(promise); + // wait for specified timeout + boost::fibers::future_status status = + future.wait_for(std::chrono::milliseconds(long(timeout * 1000))); + // if the future is NOT yet ready, return timeoutResult instead + if (status == boost::fibers::future_status::timeout) { - LLTHROW(LLErrorEvent(desc, result.first)); + LL_DEBUGS("lleventcoro") << "postAndSuspendWithTimeout(): coroutine " << listenerName + << " timed out after " << timeout << " seconds," + << " resuming with " << timeoutResult << LL_ENDL; + return timeoutResult; } - // That way, our caller knows a simple return must be from the reply - // pump (pump 0). - return result.first; -} - -LLSD errorLog(const LLEventWithID& result, const std::string& desc) -{ - // If the result arrived on the error pump (pump 1), log it as a fatal - // error. - if (result.second) + else { - LL_ERRS("errorLog") << desc << ":" << std::endl; - LLSDSerialize::toPrettyXML(result.first, LL_CONT); - LL_CONT << LL_ENDL; + llassert_always(status == boost::fibers::future_status::ready); + + // future is now ready, no more waiting + LLSD value(future.get()); + LL_DEBUGS("lleventcoro") << "postAndSuspendWithTimeout(): coroutine " << listenerName + << " resuming with " << value << LL_ENDL; + // returning should disconnect the connection + return value; } - // A simple return must therefore be from the reply pump (pump 0). - return result.first; } - -} // namespace llcoro -- cgit v1.2.3 From a26905b0c5570644bb7a65b952accfe2c3f81c33 Mon Sep 17 00:00:00 2001 From: Brad Kittenbrink Date: Wed, 27 Feb 2019 16:55:08 -0800 Subject: Added try/catch closer to source of error so LL_ERRS fatal can be more useful for debugging; --- indra/llcommon/lleventcoro.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'indra/llcommon/lleventcoro.cpp') diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp index 47d99f0050..367ddf485d 100644 --- a/indra/llcommon/lleventcoro.cpp +++ b/indra/llcommon/lleventcoro.cpp @@ -138,9 +138,17 @@ LLBoundListener postAndSuspendSetup(const std::string& callerName, // the specified LLEventPump with that callback LLBoundListener connection( replyPump.getPump().listen(listenerName, - [&promise, consuming](const LLSD& result) + [&promise, consuming, listenerName](const LLSD& result) { - promise.set_value(result); + try + { + promise.set_value(result); + } + catch(boost::fibers::promise_already_satisfied & ex) + { + LL_ERRS("lleventcoro") << "promise already satisfied in '" + << listenerName << "' " << ex.what() << LL_ENDL; + } return consuming; })); // skip the "post" part if requestPump is default-constructed -- cgit v1.2.3 From 66abe4ccab7b60e9056ee03b9536b9980599d5f0 Mon Sep 17 00:00:00 2001 From: Brad Kittenbrink Date: Mon, 4 Mar 2019 13:00:52 -0800 Subject: Attempt to close LLEventCoro's LLBoundListener connection when promise has been fulfilled. --- indra/llcommon/lleventcoro.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'indra/llcommon/lleventcoro.cpp') diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp index 367ddf485d..aa9f4b7840 100644 --- a/indra/llcommon/lleventcoro.cpp +++ b/indra/llcommon/lleventcoro.cpp @@ -134,12 +134,15 @@ LLBoundListener postAndSuspendSetup(const std::string& callerName, // return the consuming attribute for some other coroutine, most likely // the main routine. bool consuming(LLCoros::get_consuming()); + + std::shared_ptr connection_ptr = std::make_shared(); + // make a callback that will assign a value to the future, and listen on // the specified LLEventPump with that callback - LLBoundListener connection( - replyPump.getPump().listen(listenerName, - [&promise, consuming, listenerName](const LLSD& result) + *connection_ptr = replyPump.getPump().listen(listenerName, + [&promise, consuming, listenerName, connection_ptr](const LLSD& result) { + connection_ptr->disconnect(); try { promise.set_value(result); @@ -150,7 +153,7 @@ LLBoundListener postAndSuspendSetup(const std::string& callerName, << listenerName << "' " << ex.what() << LL_ENDL; } return consuming; - })); + }); // skip the "post" part if requestPump is default-constructed if (requestPump) { @@ -169,7 +172,7 @@ LLBoundListener postAndSuspendSetup(const std::string& callerName, LL_DEBUGS("lleventcoro") << callerName << ": coroutine " << listenerName << " about to wait on LLEventPump " << replyPump.getPump().getName() << LL_ENDL; - return connection; + return *connection_ptr; } } // anonymous -- cgit v1.2.3 From 8013a81adca731ebaade605290b2a4e7f94d5d05 Mon Sep 17 00:00:00 2001 From: Brad Kittenbrink Date: Thu, 14 Mar 2019 17:08:32 -0700 Subject: Switched LL_ERRS to LL_WARNS for case where promise is fulfilled multiple times by multiple events. --- indra/llcommon/lleventcoro.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llcommon/lleventcoro.cpp') diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp index aa9f4b7840..421897cb98 100644 --- a/indra/llcommon/lleventcoro.cpp +++ b/indra/llcommon/lleventcoro.cpp @@ -149,7 +149,7 @@ LLBoundListener postAndSuspendSetup(const std::string& callerName, } catch(boost::fibers::promise_already_satisfied & ex) { - LL_ERRS("lleventcoro") << "promise already satisfied in '" + LL_WARNS("lleventcoro") << "promise already satisfied in '" << listenerName << "' " << ex.what() << LL_ENDL; } return consuming; -- cgit v1.2.3 From 6419c6e279816b21e30fef1a6ac4864df7fcf91d Mon Sep 17 00:00:00 2001 From: Brad Kittenbrink Date: Thu, 18 Apr 2019 15:44:45 -0700 Subject: Removed unnecessary disconnection of listener in postAndSuspendSetup --- indra/llcommon/lleventcoro.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) (limited to 'indra/llcommon/lleventcoro.cpp') diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp index 421897cb98..b1fb8ffd04 100644 --- a/indra/llcommon/lleventcoro.cpp +++ b/indra/llcommon/lleventcoro.cpp @@ -134,15 +134,12 @@ LLBoundListener postAndSuspendSetup(const std::string& callerName, // return the consuming attribute for some other coroutine, most likely // the main routine. bool consuming(LLCoros::get_consuming()); - - std::shared_ptr connection_ptr = std::make_shared(); - // make a callback that will assign a value to the future, and listen on // the specified LLEventPump with that callback - *connection_ptr = replyPump.getPump().listen(listenerName, - [&promise, consuming, listenerName, connection_ptr](const LLSD& result) + LLBoundListener connection( + replyPump.getPump().listen(listenerName, + [&promise, consuming, listenerName](const LLSD& result) { - connection_ptr->disconnect(); try { promise.set_value(result); @@ -153,7 +150,7 @@ LLBoundListener postAndSuspendSetup(const std::string& callerName, << listenerName << "' " << ex.what() << LL_ENDL; } return consuming; - }); + })); // skip the "post" part if requestPump is default-constructed if (requestPump) { @@ -172,7 +169,7 @@ LLBoundListener postAndSuspendSetup(const std::string& callerName, LL_DEBUGS("lleventcoro") << callerName << ": coroutine " << listenerName << " about to wait on LLEventPump " << replyPump.getPump().getName() << LL_ENDL; - return *connection_ptr; + return connection; } } // anonymous -- cgit v1.2.3 From e4d6383c47241fa4c58c2491c2d32046126fe52c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Mon, 7 Oct 2019 17:18:29 -0400 Subject: DRTVWR-476: Fix overflow case in llcoro::postAndSuspend(). Actually the fix is in postAndSuspendSetup(), which affects postAndSuspend(), postAndSuspendWithTimeout(), suspendUntilEventOnWithTimeout() and suspendUntilEventOn(). By "overflow case" we mean the special circumstance in which: * the LLEventPump in question is an LLEventMailDrop, meaning its listeners eventually expect to see every post()ed value * one of the listeners is supposed to consume those values (has called LLCoros::set_consuming(true)) * post() is called more than once before that listener is resumed. The magic of postAndSuspend() (et al.) is a temporary LLCoros::Promise. The waiting coroutine calls get() on the corresponding Future, causing it to suspend (as promised) until the Promise is fulfilled. With the Boost.Fiber implementation of coroutines, fulfilling the Promise doesn't immediately resume the suspended coroutine -- it merely marks it ready to resume, next time the scheduler gets control. A second post() call before the suspended coroutine is resumed results in a second call to Promise::set_value(). But Promise is a one-shot entity. This results in a promise_already_satisfied exception. Because a second post() call during that time window is perfectly reasonable, we catch that exception and carry on. The tricky part is: when that exception is thrown, what should the listener return? Previously we were returning the listener's current consuming setting, just as when the set_value() call succeeds. But when the LLEventPump is an LLEventMailDrop, and the listener's consuming flag is true, that told LLEventMailDrop::post() that the value got through, and that it needn't bother to save it in its history queue. The net effect was to discard the value. Instead, return the listener's consuming flag only when Promise::set_value() succeeds. When it throws promise_already_satisfied, unconditionally return false. That directs LLEventMailDrop::post() to enqueue the undelivered value so that the *next* suspendUntilEventOn() call can pick it up. --- indra/llcommon/lleventcoro.cpp | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) (limited to 'indra/llcommon/lleventcoro.cpp') diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp index b1fb8ffd04..18a3595c24 100644 --- a/indra/llcommon/lleventcoro.cpp +++ b/indra/llcommon/lleventcoro.cpp @@ -137,20 +137,27 @@ LLBoundListener postAndSuspendSetup(const std::string& callerName, // make a callback that will assign a value to the future, and listen on // the specified LLEventPump with that callback LLBoundListener connection( - replyPump.getPump().listen(listenerName, - [&promise, consuming, listenerName](const LLSD& result) - { - try - { - promise.set_value(result); - } - catch(boost::fibers::promise_already_satisfied & ex) - { - LL_WARNS("lleventcoro") << "promise already satisfied in '" - << listenerName << "' " << ex.what() << LL_ENDL; - } - return consuming; - })); + replyPump.getPump().listen( + listenerName, + [&promise, consuming, listenerName](const LLSD& result) + { + try + { + promise.set_value(result); + // We did manage to propagate the result value to the + // (real) listener. If we're supposed to indicate that + // we've consumed it, do so. + return consuming; + } + catch(boost::fibers::promise_already_satisfied & ex) + { + LL_WARNS("lleventcoro") << "promise already satisfied in '" + << listenerName << "' " << ex.what() << LL_ENDL; + // We could not propagate the result value to the + // listener. + return false; + } + })); // skip the "post" part if requestPump is default-constructed if (requestPump) { -- cgit v1.2.3 From 6945755e5299e071e46d53f09821ac73993f7afe Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Wed, 16 Oct 2019 08:52:51 -0400 Subject: DRTVWR-476: Validate LLEventPumpOrPumpName replyPump passed to postAndSuspendsetup(). The requestPump is optional, and the function varies its behavior depending on whether that parameter is empty or meaningful. But it unconditionally uses the replyPump. Passing an empty LLEventPumpOrPumpName caused mysterious crashes. Add llassert_always_msg() to make the coding error explicit in such a case. Also streamline access to meaningful requestPump and replyPump by temporarily caching the bound LLEventPump reference. --- indra/llcommon/lleventcoro.cpp | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) (limited to 'indra/llcommon/lleventcoro.cpp') diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp index 18a3595c24..bc7f947be1 100644 --- a/indra/llcommon/lleventcoro.cpp +++ b/indra/llcommon/lleventcoro.cpp @@ -125,8 +125,8 @@ LLBoundListener postAndSuspendSetup(const std::string& callerName, const std::string& listenerName, LLCoros::Promise& promise, const LLSD& event, - const LLEventPumpOrPumpName& requestPump, - const LLEventPumpOrPumpName& replyPump, + const LLEventPumpOrPumpName& requestPumpP, + const LLEventPumpOrPumpName& replyPumpP, const LLSD& replyPumpNamePath) { // Get the consuming attribute for THIS coroutine, the one that's about to @@ -134,10 +134,12 @@ LLBoundListener postAndSuspendSetup(const std::string& callerName, // return the consuming attribute for some other coroutine, most likely // the main routine. bool consuming(LLCoros::get_consuming()); - // make a callback that will assign a value to the future, and listen on - // the specified LLEventPump with that callback + // listen on the specified LLEventPump with a lambda that will assign a + // value to the promise, thus fulfilling its future + llassert_always_msg(replyPumpP, ("replyPump required for " + callerName)); + LLEventPump& replyPump{replyPumpP.getPump()}; LLBoundListener connection( - replyPump.getPump().listen( + replyPump.listen( listenerName, [&promise, consuming, listenerName](const LLSD& result) { @@ -151,30 +153,31 @@ LLBoundListener postAndSuspendSetup(const std::string& callerName, } catch(boost::fibers::promise_already_satisfied & ex) { - LL_WARNS("lleventcoro") << "promise already satisfied in '" - << listenerName << "' " << ex.what() << LL_ENDL; + LL_DEBUGS("lleventcoro") << "promise already satisfied in '" + << listenerName << "': " << ex.what() << LL_ENDL; // We could not propagate the result value to the // listener. return false; } })); // skip the "post" part if requestPump is default-constructed - if (requestPump) + if (requestPumpP) { + LLEventPump& requestPump{requestPumpP.getPump()}; // If replyPumpNamePath is non-empty, store the replyPump name in the // request event. LLSD modevent(event); - storeToLLSDPath(modevent, replyPumpNamePath, replyPump.getPump().getName()); + storeToLLSDPath(modevent, replyPumpNamePath, replyPump.getName()); LL_DEBUGS("lleventcoro") << callerName << ": coroutine " << listenerName - << " posting to " << requestPump.getPump().getName() + << " posting to " << requestPump.getName() << LL_ENDL; // *NOTE:Mani - Removed because modevent could contain user's hashed passwd. // << ": " << modevent << LL_ENDL; - requestPump.getPump().post(modevent); + requestPump.post(modevent); } LL_DEBUGS("lleventcoro") << callerName << ": coroutine " << listenerName - << " about to wait on LLEventPump " << replyPump.getPump().getName() + << " about to wait on LLEventPump " << replyPump.getName() << LL_ENDL; return connection; } -- cgit v1.2.3 From 3ec92b0eded755fe9e1965c6300385effdf733cf Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 18 Oct 2019 11:54:05 -0400 Subject: DRTVWR-476: Seems gcc 4.8 forbids brace-init of reference vars. Although this is legal, apparently there was a bug in the C++11 standard (to which gcc 4.8 conforms) that was subsequently fixed in the standard (and thus in gcc 4.9). Thanks Henri Beauchamp. --- indra/llcommon/lleventcoro.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indra/llcommon/lleventcoro.cpp') diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp index bc7f947be1..b374c9fa04 100644 --- a/indra/llcommon/lleventcoro.cpp +++ b/indra/llcommon/lleventcoro.cpp @@ -137,7 +137,7 @@ LLBoundListener postAndSuspendSetup(const std::string& callerName, // listen on the specified LLEventPump with a lambda that will assign a // value to the promise, thus fulfilling its future llassert_always_msg(replyPumpP, ("replyPump required for " + callerName)); - LLEventPump& replyPump{replyPumpP.getPump()}; + LLEventPump& replyPump(replyPumpP.getPump()); LLBoundListener connection( replyPump.listen( listenerName, @@ -163,7 +163,7 @@ LLBoundListener postAndSuspendSetup(const std::string& callerName, // skip the "post" part if requestPump is default-constructed if (requestPumpP) { - LLEventPump& requestPump{requestPumpP.getPump()}; + LLEventPump& requestPump(requestPumpP.getPump()); // If replyPumpNamePath is non-empty, store the replyPump name in the // request event. LLSD modevent(event); -- cgit v1.2.3 From 1345a02b21a83bc4ee7ff72efc1858e956f18c53 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 22 Oct 2019 17:14:26 -0400 Subject: DRTVWR-476: Terminate long-lived coroutines to avoid shutdown crash. Add LLCoros::TempStatus instances around known suspension points so printActiveCoroutines() can report what each suspended coroutine is waiting for. Similarly, sprinkle checkStop() calls at known suspension points. Make LLApp::setStatus() post an event to a new LLEventPump "LLApp" with a string corresponding to the status value being set, but only until ~LLEventPumps() -- since setStatus() also gets called very late in the application's lifetime. Make postAndSuspendSetup() (used by postAndSuspend(), suspendUntilEventOn(), postAndSuspendWithTimeout(), suspendUntilEventOnWithTimeout()) add a listener on the new "LLApp" LLEventPump that pushes the new LLCoros::Stopping exception to the coroutine waiting on the LLCoros::Promise. Make it return the new LLBoundListener along with the previous one. Accordingly, make postAndSuspend() and postAndSuspendWithTimeout() store the new LLBoundListener returned by postAndSuspendSetup() in a LLTempBoundListener (as with the previous one) so it will automatically disconnect once the wait is over. Make each LLCoprocedurePool instance listen on "LLApp" with a listener that closes the queue on which new work items are dispatched. Closing the queue causes the waiting dispatch coroutine to terminate. Store the connection in an LLTempBoundListener on the LLCoprocedurePool so it will disconnect automatically on destruction. Refactor the loop in coprocedureInvokerCoro() to instantiate TempStatus around the suspending call. Change a couple spammy LL_INFOS() calls to LL_DEBUGS(). Give all logging calls in that module a "CoProcMgr" tag to make it straightforward to re-enable the LL_DEBUGS() calls as desired. --- indra/llcommon/lleventcoro.cpp | 80 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 66 insertions(+), 14 deletions(-) (limited to 'indra/llcommon/lleventcoro.cpp') diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp index b374c9fa04..23e0012a1a 100644 --- a/indra/llcommon/lleventcoro.cpp +++ b/indra/llcommon/lleventcoro.cpp @@ -32,6 +32,7 @@ #include "lleventcoro.h" // STL headers #include +#include // std headers // external library headers #include @@ -40,6 +41,7 @@ #include "llsdutil.h" #include "llerror.h" #include "llcoros.h" +#include "stringize.h" namespace { @@ -106,29 +108,39 @@ void storeToLLSDPath(LLSD& dest, const LLSD& path, const LLSD& value) void llcoro::suspend() { + LLCoros::checkStop(); + LLCoros::TempStatus st("waiting one tick"); boost::this_fiber::yield(); } void llcoro::suspendUntilTimeout(float seconds) { + LLCoros::checkStop(); // The fact that we accept non-integer seconds means we should probably // use granularity finer than one second. However, given the overhead of // the rest of our processing, it seems silly to use granularity finer // than a millisecond. + LLCoros::TempStatus st(STRINGIZE("waiting for " << seconds << "s")); boost::this_fiber::sleep_for(std::chrono::milliseconds(long(seconds * 1000))); } namespace { -LLBoundListener postAndSuspendSetup(const std::string& callerName, - const std::string& listenerName, - LLCoros::Promise& promise, - const LLSD& event, - const LLEventPumpOrPumpName& requestPumpP, - const LLEventPumpOrPumpName& replyPumpP, - const LLSD& replyPumpNamePath) +// returns a listener on replyPumpP, also on "mainloop" -- both should be +// stored in LLTempBoundListeners on the caller's stack frame +std::pair +postAndSuspendSetup(const std::string& callerName, + const std::string& listenerName, + LLCoros::Promise& promise, + const LLSD& event, + const LLEventPumpOrPumpName& requestPumpP, + const LLEventPumpOrPumpName& replyPumpP, + const LLSD& replyPumpNamePath) { + // Before we get any farther -- should we be stopping instead of + // suspending? + LLCoros::checkStop(); // Get the consuming attribute for THIS coroutine, the one that's about to // suspend. Don't call get_consuming() in the lambda body: that would // return the consuming attribute for some other coroutine, most likely @@ -138,6 +150,38 @@ LLBoundListener postAndSuspendSetup(const std::string& callerName, // value to the promise, thus fulfilling its future llassert_always_msg(replyPumpP, ("replyPump required for " + callerName)); LLEventPump& replyPump(replyPumpP.getPump()); + // The relative order of the two listen() calls below would only matter if + // "LLApp" were an LLEventMailDrop. But if we ever go there, we'd want to + // notice the pending LLApp status first. + LLBoundListener stopper( + LLEventPumps::instance().obtain("LLApp").listen( + listenerName, + [&promise, listenerName](const LLSD& status) + { + // anything except "running" should wake up the waiting + // coroutine + auto& statsd = status["status"]; + if (statsd.asString() != "running") + { + LL_DEBUGS("lleventcoro") << listenerName + << " spotted status " << statsd + << ", throwing Stopping" << LL_ENDL; + try + { + promise.set_exception( + std::make_exception_ptr( + LLCoros::Stopping("status " + statsd.asString()))); + } + catch (const boost::fibers::promise_already_satisfied& exc) + { + LL_WARNS("lleventcoro") << listenerName + << " couldn't throw Stopping " + "because promise already set" << LL_ENDL; + } + } + // do not consume -- every listener must see status + return false; + })); LLBoundListener connection( replyPump.listen( listenerName, @@ -160,6 +204,7 @@ LLBoundListener postAndSuspendSetup(const std::string& callerName, return false; } })); + // skip the "post" part if requestPump is default-constructed if (requestPumpP) { @@ -179,7 +224,7 @@ LLBoundListener postAndSuspendSetup(const std::string& callerName, LL_DEBUGS("lleventcoro") << callerName << ": coroutine " << listenerName << " about to wait on LLEventPump " << replyPump.getName() << LL_ENDL; - return connection; + return { connection, stopper }; } } // anonymous @@ -190,15 +235,17 @@ LLSD llcoro::postAndSuspend(const LLSD& event, const LLEventPumpOrPumpName& requ LLCoros::Promise promise; std::string listenerName(listenerNameForCoro()); - // Store connection into an LLTempBoundListener so we implicitly + // Store both connections into LLTempBoundListeners so we implicitly // disconnect on return from this function. - LLTempBoundListener connection = + auto connections = postAndSuspendSetup("postAndSuspend()", listenerName, promise, event, requestPump, replyPump, replyPumpNamePath); + LLTempBoundListener connection(connections.first), stopper(connections.second); // declare the future LLCoros::Future future = LLCoros::getFuture(promise); // calling get() on the future makes us wait for it + LLCoros::TempStatus st(STRINGIZE("waiting for " << replyPump.getPump().getName())); LLSD value(future.get()); LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << listenerName << " resuming with " << value << LL_ENDL; @@ -215,17 +262,22 @@ LLSD llcoro::postAndSuspendWithTimeout(const LLSD& event, LLCoros::Promise promise; std::string listenerName(listenerNameForCoro()); - // Store connection into an LLTempBoundListener so we implicitly + // Store both connections into LLTempBoundListeners so we implicitly // disconnect on return from this function. - LLTempBoundListener connection = + auto connections = postAndSuspendSetup("postAndSuspendWithTimeout()", listenerName, promise, event, requestPump, replyPump, replyPumpNamePath); + LLTempBoundListener connection(connections.first), stopper(connections.second); // declare the future LLCoros::Future future = LLCoros::getFuture(promise); // wait for specified timeout - boost::fibers::future_status status = - future.wait_for(std::chrono::milliseconds(long(timeout * 1000))); + boost::fibers::future_status status; + { + LLCoros::TempStatus st(STRINGIZE("waiting for " << replyPump.getPump().getName() + << " for " << timeout << "s")); + status = future.wait_for(std::chrono::milliseconds(long(timeout * 1000))); + } // if the future is NOT yet ready, return timeoutResult instead if (status == boost::fibers::future_status::timeout) { -- cgit v1.2.3 From 7541e784d991888d6f600f4cb9439d5fcf15e452 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Tue, 22 Oct 2019 20:48:36 -0400 Subject: DRTVWR-476: Don't name the caught exception unless we're going to reference it. --- indra/llcommon/lleventcoro.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llcommon/lleventcoro.cpp') diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp index 23e0012a1a..967c4d74d8 100644 --- a/indra/llcommon/lleventcoro.cpp +++ b/indra/llcommon/lleventcoro.cpp @@ -172,7 +172,7 @@ postAndSuspendSetup(const std::string& callerName, std::make_exception_ptr( LLCoros::Stopping("status " + statsd.asString()))); } - catch (const boost::fibers::promise_already_satisfied& exc) + catch (const boost::fibers::promise_already_satisfied&) { LL_WARNS("lleventcoro") << listenerName << " couldn't throw Stopping " -- cgit v1.2.3 From cbf146f2b3fc255bc83f2b01101dc29658bea6ea Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 24 Oct 2019 12:54:38 -0400 Subject: DRTVWR-476: Pump coroutines a few more times when we start quitting. By the time "LLApp" listeners are notified that the app is quitting, the mainloop is no longer running. Even though those listeners do things like close work queues and inject exceptions into pending promises, any coroutines waiting on those resources must regain control before they can notice and shut down properly. Add a final "LLApp" listener that resumes ready coroutines a few more times. Make sure every other "LLApp" listener is positioned before that new one. --- indra/llcommon/lleventcoro.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'indra/llcommon/lleventcoro.cpp') diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp index 967c4d74d8..785c231f2c 100644 --- a/indra/llcommon/lleventcoro.cpp +++ b/indra/llcommon/lleventcoro.cpp @@ -153,6 +153,7 @@ postAndSuspendSetup(const std::string& callerName, // The relative order of the two listen() calls below would only matter if // "LLApp" were an LLEventMailDrop. But if we ever go there, we'd want to // notice the pending LLApp status first. + // Run this listener before the "final" listener. LLBoundListener stopper( LLEventPumps::instance().obtain("LLApp").listen( listenerName, @@ -181,7 +182,9 @@ postAndSuspendSetup(const std::string& callerName, } // do not consume -- every listener must see status return false; - })); + }, + LLEventPump::NameList{}, // after + LLEventPump::NameList{ "final "})); // before LLBoundListener connection( replyPump.listen( listenerName, -- cgit v1.2.3 From 26c8ccfc06bc9334c9a4d0d027e83ad0b1b92a86 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 24 Oct 2019 16:05:37 -0400 Subject: DRTVWR-476: Back out changeset 40c0c6a8407d ("final" LLApp listener) --- indra/llcommon/lleventcoro.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'indra/llcommon/lleventcoro.cpp') diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp index 785c231f2c..967c4d74d8 100644 --- a/indra/llcommon/lleventcoro.cpp +++ b/indra/llcommon/lleventcoro.cpp @@ -153,7 +153,6 @@ postAndSuspendSetup(const std::string& callerName, // The relative order of the two listen() calls below would only matter if // "LLApp" were an LLEventMailDrop. But if we ever go there, we'd want to // notice the pending LLApp status first. - // Run this listener before the "final" listener. LLBoundListener stopper( LLEventPumps::instance().obtain("LLApp").listen( listenerName, @@ -182,9 +181,7 @@ postAndSuspendSetup(const std::string& callerName, } // do not consume -- every listener must see status return false; - }, - LLEventPump::NameList{}, // after - LLEventPump::NameList{ "final "})); // before + })); LLBoundListener connection( replyPump.listen( listenerName, -- cgit v1.2.3 From 2a56ab44360aa49bd0df9281efc8a8a97a510013 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 22 Nov 2019 11:58:27 -0500 Subject: DRTVWR-476, SL-12197: Don't throw Stopping from main coroutine. The new LLCoros::Stop exception is intended to terminate long-lived coroutines -- not interrupt mainstream shutdown processing. Only throw it on an explicitly-launched coroutine. Make LLCoros::getName() (used by the above test) static. As with other LLCoros methods, it might be called after the LLCoros LLSingleton instance has been deleted. Requiring the caller to call instance() implies a possible need to also call wasDeleted(). Encapsulate that nuance into a static method instead. --- indra/llcommon/lleventcoro.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llcommon/lleventcoro.cpp') diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp index 967c4d74d8..11b6e5bb2f 100644 --- a/indra/llcommon/lleventcoro.cpp +++ b/indra/llcommon/lleventcoro.cpp @@ -62,7 +62,7 @@ namespace std::string listenerNameForCoro() { // If this coroutine was launched by LLCoros::launch(), find that name. - std::string name(LLCoros::instance().getName()); + std::string name(LLCoros::getName()); if (! name.empty()) { return name; -- cgit v1.2.3 From 1231cb4585290a26e29cca221f7c81fda3e2c623 Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Thu, 25 Jun 2020 20:59:04 -0400 Subject: DRTVWR-476, SL-13512: Make suspendUntilTimeout() notice shutdown. Specifically, the shutdown crash reported in SL-13512 was due to LLExperienceCache::idleCoro() looping on suspendUntilTimeout(), failing to notice in its slumbers that the viewer was shutting down around it. Make suspendUntilTimeout() internally call suspendUntilEventOnWithTimeout(), which already listens for "LLApp" state-change events and throws Stopping when LLApp enters its shutdown sequence. --- indra/llcommon/lleventcoro.cpp | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) (limited to 'indra/llcommon/lleventcoro.cpp') diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp index 11b6e5bb2f..bc02ab99de 100644 --- a/indra/llcommon/lleventcoro.cpp +++ b/indra/llcommon/lleventcoro.cpp @@ -116,12 +116,21 @@ void llcoro::suspend() void llcoro::suspendUntilTimeout(float seconds) { LLCoros::checkStop(); - // The fact that we accept non-integer seconds means we should probably - // use granularity finer than one second. However, given the overhead of - // the rest of our processing, it seems silly to use granularity finer - // than a millisecond. - LLCoros::TempStatus st(STRINGIZE("waiting for " << seconds << "s")); - boost::this_fiber::sleep_for(std::chrono::milliseconds(long(seconds * 1000))); + // We used to call boost::this_fiber::sleep_for(). But some coroutines + // (e.g. LLExperienceCache::idleCoro()) sit in a suspendUntilTimeout() + // loop, in which case a sleep_for() call risks sleeping through shutdown. + // So instead, listen for "LLApp" state-changing events -- which + // fortunately is handled for us by suspendUntilEventOnWithTimeout(). + // Wait for an event on a bogus LLEventPump on which nobody ever posts + // events. Don't make it static because that would force instantiation of + // the LLEventPumps LLSingleton registry at static initialization time. + LLEventStream bogus("xyzzy"); // could use an LLUUID if it matters + // Timeout is the NORMAL case for this call! + static LLSD timedout; + // Deliver, but ignore, timedout when (as usual) we did not receive any + // "LLApp" event. The point is that suspendUntilEventOnWithTimeout() will + // itself throw Stopping when "LLApp" starts broadcasting shutdown events. + suspendUntilEventOnWithTimeout(bogus, seconds, timedout); } namespace @@ -276,6 +285,10 @@ LLSD llcoro::postAndSuspendWithTimeout(const LLSD& event, { LLCoros::TempStatus st(STRINGIZE("waiting for " << replyPump.getPump().getName() << " for " << timeout << "s")); + // The fact that we accept non-integer seconds means we should probably + // use granularity finer than one second. However, given the overhead of + // the rest of our processing, it seems silly to use granularity finer + // than a millisecond. status = future.wait_for(std::chrono::milliseconds(long(timeout * 1000))); } // if the future is NOT yet ready, return timeoutResult instead -- cgit v1.2.3 From 75b5f72e6951cae855a6abacc110a886e0ae701c Mon Sep 17 00:00:00 2001 From: Nat Goodspeed Date: Fri, 26 Jun 2020 14:40:07 -0400 Subject: DRTVWR-476, SL-13512: Fix flawed fix for former failure. Specifically, llcoro::suspendUntilTimeout() is definitely called concurrently by multiple coroutines. New code that instantiates a local LLEventStream must allow the name to be tweaked for uniqueness. --- indra/llcommon/lleventcoro.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'indra/llcommon/lleventcoro.cpp') diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp index bc02ab99de..995356dc52 100644 --- a/indra/llcommon/lleventcoro.cpp +++ b/indra/llcommon/lleventcoro.cpp @@ -124,7 +124,11 @@ void llcoro::suspendUntilTimeout(float seconds) // Wait for an event on a bogus LLEventPump on which nobody ever posts // events. Don't make it static because that would force instantiation of // the LLEventPumps LLSingleton registry at static initialization time. - LLEventStream bogus("xyzzy"); // could use an LLUUID if it matters + // DO allow tweaking the name for uniqueness, this definitely gets + // re-entered on multiple coroutines! + // We could use an LLUUID if it were important to actively prohibit anyone + // from ever posting on this LLEventPump. + LLEventStream bogus("xyzzy", true); // Timeout is the NORMAL case for this call! static LLSD timedout; // Deliver, but ignore, timedout when (as usual) we did not receive any -- cgit v1.2.3