diff options
author | nat-goodspeed <nat@lindenlab.com> | 2024-04-19 08:55:11 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-04-19 08:55:11 -0400 |
commit | 40a881dd26cbf0b92e03ec20e296b94e89bdb2c3 (patch) | |
tree | 02a60bb2dbaf2e4071150ccb033e9ab8223c5e87 | |
parent | 78d985c154b0d71064b89891f5b2005e48c300ce (diff) | |
parent | 426dc4da45f91afbb1107aecf075e501e774e8ee (diff) |
Merge pull request #1262 from secondlife/lua-kill-coro
Reintroduce `LLCoros::killreq()` to request killing a named coroutine.
-rw-r--r-- | indra/llcommon/llapp.cpp | 1 | ||||
-rw-r--r-- | indra/llcommon/llcoros.cpp | 127 | ||||
-rw-r--r-- | indra/llcommon/llcoros.h | 67 | ||||
-rw-r--r-- | indra/llcommon/lleventcoro.cpp | 43 | ||||
-rw-r--r-- | indra/llcommon/lua_function.cpp | 44 | ||||
-rw-r--r-- | indra/llcommon/lua_function.h | 3 | ||||
-rw-r--r-- | indra/llcommon/lualistener.cpp | 21 | ||||
-rw-r--r-- | indra/llcommon/lualistener.h | 3 | ||||
-rw-r--r-- | indra/llcommon/threadpool.cpp | 19 | ||||
-rw-r--r-- | indra/llcommon/threadpool.h | 6 | ||||
-rw-r--r-- | indra/llcommon/workqueue.cpp | 7 | ||||
-rw-r--r-- | indra/llcommon/workqueue.h | 3 | ||||
-rw-r--r-- | indra/llimage/tests/llimageworker_test.cpp | 2 | ||||
-rw-r--r-- | indra/llmessage/llcoproceduremanager.cpp | 21 | ||||
-rw-r--r-- | indra/newview/llfloaterluascripts.cpp | 3 | ||||
-rw-r--r-- | indra/newview/llluamanager.cpp | 50 | ||||
-rw-r--r-- | indra/newview/llluamanager.h | 3 | ||||
-rw-r--r-- | indra/newview/tests/llluamanager_test.cpp | 35 | ||||
-rw-r--r-- | indra/test/debug.h | 48 |
19 files changed, 324 insertions, 182 deletions
diff --git a/indra/llcommon/llapp.cpp b/indra/llcommon/llapp.cpp index 6fe80c9954..90d0c28eb1 100644 --- a/indra/llcommon/llapp.cpp +++ b/indra/llcommon/llapp.cpp @@ -428,7 +428,6 @@ void LLApp::setStatus(EAppStatus status) statsd = LLSD::Integer(status); } LLEventPumps::instance().obtain("LLApp").post(llsd::map("status", statsd)); - LLEventPumps::instance().obtain("LLLua").post(llsd::map("status", "close_all")); } } diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp index c13900f74a..1926941d1f 100644 --- a/indra/llcommon/llcoros.cpp +++ b/indra/llcommon/llcoros.cpp @@ -51,18 +51,19 @@ #endif // other Linden headers #include "llapp.h" -#include "lltimer.h" -#include "llevents.h" #include "llerror.h" -#include "stringize.h" +#include "llevents.h" #include "llexception.h" +#include "llsdutil.h" +#include "lltimer.h" +#include "stringize.h" #if LL_WINDOWS #include <excpt.h> #endif // static -LLCoros::CoroData& LLCoros::get_CoroData(const std::string& caller) +LLCoros::CoroData& LLCoros::get_CoroData(const std::string&) { CoroData* current{ nullptr }; // be careful about attempted accesses in the final throes of app shutdown @@ -94,7 +95,7 @@ LLCoros::coro::id LLCoros::get_self() //static void LLCoros::set_consuming(bool consuming) { - CoroData& data(get_CoroData("set_consuming()")); + auto& data(get_CoroData("set_consuming()")); // DO NOT call this on the main() coroutine. llassert_always(! data.mName.empty()); data.mConsuming = consuming; @@ -128,6 +129,15 @@ LLCoros::LLCoros(): // points to it. So initialize it with a no-op deleter. mCurrent{ [](CoroData*){} } { + auto& llapp{ LLEventPumps::instance().obtain("LLApp") }; + if (llapp.getListener("LLCoros") == LLBoundListener()) + { + // chain our "LLCoros" pump onto "LLApp" pump: echo events posted to "LLApp" + mConn = llapp.listen( + "LLCoros", + [](const LLSD& event) + { return LLEventPumps::instance().obtain("LLCoros").post(event); }); + } } LLCoros::~LLCoros() @@ -177,26 +187,26 @@ std::string LLCoros::generateDistinctName(const std::string& prefix) const // Until we find an unused name, append a numeric suffix for uniqueness. while (CoroData::getInstance(name)) { - name = STRINGIZE(prefix << unique++); + name = stringize(prefix, unique++); } return name; } -/*==========================================================================*| -bool LLCoros::kill(const std::string& name) +bool LLCoros::killreq(const std::string& name) { - CoroMap::iterator found = mCoros.find(name); - if (found == mCoros.end()) + auto found = CoroData::getInstance(name); + if (! found) { return false; } - // Because this is a boost::ptr_map, erasing the map entry also destroys - // the referenced heap object, in this case the boost::coroutine object, - // which will terminate the coroutine. - mCoros.erase(found); + // Next time the subject coroutine calls checkStop(), make it terminate. + found->mKilledBy = getName(); + // But if it's waiting for something, notify anyone in a position to poke + // it. + LLEventPumps::instance().obtain("LLCoros").post( + llsd::map("status", "killreq", "coro", name)); return true; } -|*==========================================================================*/ //static std::string LLCoros::getName() @@ -207,7 +217,7 @@ std::string LLCoros::getName() //static std::string LLCoros::logname() { - LLCoros::CoroData& data(get_CoroData("logname()")); + auto& data(get_CoroData("logname()")); return data.mName.empty()? data.getKey() : data.mName; } @@ -360,7 +370,7 @@ void LLCoros::toplevel(std::string name, callable_t callable) // Any uncaught exception derived from LLContinueError will be caught // here and logged. This coroutine will terminate but the rest of the // viewer will carry on. - LOG_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << name)); + LOG_UNHANDLED_EXCEPTION(stringize("coroutine ", name)); } catch (...) { @@ -373,15 +383,24 @@ void LLCoros::toplevel(std::string name, callable_t callable) } //static -void LLCoros::checkStop() +void LLCoros::checkStop(callable_t cleanup) { + // don't replicate this 'if' test throughout the code below + if (! cleanup) + { + cleanup = {[](){}}; // hey, look, I'm coding in Haskell! + } + if (wasDeleted()) { + cleanup(); LLTHROW(Shutdown("LLCoros was deleted")); } - // do this AFTER the check above, because getName() depends on - // get_CoroData(), which depends on the local_ptr in our instance(). - if (getName().empty()) + + // do this AFTER the check above, because get_CoroData() depends on the + // local_ptr in our instance(). + auto& data(get_CoroData("checkStop()")); + if (data.mName.empty()) { // Our Stop exception and its subclasses are intended to stop loitering // coroutines. Don't throw it from the main coroutine. @@ -389,19 +408,80 @@ void LLCoros::checkStop() } if (LLApp::isStopped()) { + cleanup(); LLTHROW(Stopped("viewer is stopped")); } if (! LLApp::isRunning()) { + cleanup(); LLTHROW(Stopping("viewer is stopping")); } + if (! data.mKilledBy.empty()) + { + // Someone wants to kill this coroutine + cleanup(); + LLTHROW(Killed(stringize("coroutine ", data.mName, " killed by ", data.mKilledBy))); + } +} + +LLBoundListener LLCoros::getStopListener(const std::string& caller, LLVoidListener cleanup) +{ + if (! cleanup) + return {}; + + // This overload only responds to viewer shutdown. + return LLEventPumps::instance().obtain("LLCoros") + .listen( + LLEventPump::inventName(caller), + [cleanup](const LLSD& event) + { + auto status{ event["status"].asString() }; + if (status != "running" && status != "killreq") + { + cleanup(event); + } + return false; + }); +} + +LLBoundListener LLCoros::getStopListener(const std::string& caller, + const std::string& cnsmr, + LLVoidListener cleanup) +{ + if (! cleanup) + return {}; + + std::string consumer{cnsmr}; + if (consumer.empty()) + { + consumer = getName(); + } + + // This overload responds to viewer shutdown and to killreq(consumer). + return LLEventPumps::instance().obtain("LLCoros") + .listen( + LLEventPump::inventName(caller), + [consumer, cleanup](const LLSD& event) + { + auto status{ event["status"].asString() }; + if (status == "killreq") + { + if (event["coro"].asString() == consumer) + { + cleanup(event); + } + } + else if (status != "running") + { + cleanup(event); + } + return false; + }); } LLCoros::CoroData::CoroData(const std::string& name): LLInstanceTracker<CoroData, std::string>(name), mName(name), - // don't consume events unless specifically directed - mConsuming(false), mCreationTime(LLTimer::getTotalSeconds()) { } @@ -414,7 +494,6 @@ LLCoros::CoroData::CoroData(int n): // empty string as its visible name because some consumers test for that. LLInstanceTracker<CoroData, std::string>("main" + stringize(n)), mName(), - mConsuming(false), mCreationTime(LLTimer::getTotalSeconds()) { } diff --git a/indra/llcommon/llcoros.h b/indra/llcommon/llcoros.h index fd878f20ad..61c0fef1c3 100644 --- a/indra/llcommon/llcoros.h +++ b/indra/llcommon/llcoros.h @@ -29,17 +29,18 @@ #if ! defined(LL_LLCOROS_H) #define LL_LLCOROS_H +#include "llevents.h" #include "llexception.h" +#include "llinstancetracker.h" +#include "llsingleton.h" +#include "mutex.h" #include <boost/fiber/fss.hpp> #include <boost/fiber/future/promise.hpp> #include <boost/fiber/future/future.hpp> -#include "mutex.h" -#include "llsingleton.h" -#include "llinstancetracker.h" -#include <boost/function.hpp> -#include <string> #include <exception> +#include <functional> #include <queue> +#include <string> // e.g. #include LLCOROS_MUTEX_HEADER #define LLCOROS_MUTEX_HEADER <boost/fiber/mutex.hpp> @@ -101,7 +102,7 @@ public: /// stuck with the term "coroutine." typedef boost::fibers::fiber coro; /// Canonical callable type - typedef boost::function<void()> callable_t; + typedef std::function<void()> callable_t; /** * Create and start running a new coroutine with specified name. The name @@ -143,13 +144,13 @@ public: std::string launch(const std::string& prefix, const callable_t& callable); /** - * Abort a running coroutine by name. Normally, when a coroutine either + * Ask the named coroutine to abort. Normally, when a coroutine either * runs to completion or terminates with an exception, LLCoros quietly * cleans it up. This is for use only when you must explicitly interrupt * one prematurely. Returns @c true if the specified name was found and * still running at the time. */ -// bool kill(const std::string& name); + bool killreq(const std::string& name); /** * From within a coroutine, look up the (tweaked) name string by which @@ -251,15 +252,21 @@ public: /// thrown by checkStop() // It may sound ironic that Stop is derived from LLContinueError, but the // point is that LLContinueError is the category of exception that should - // not immediately crash the viewer. Stop and its subclasses are to notify - // coroutines that the viewer intends to shut down. The expected response - // is to terminate the coroutine, rather than abort the viewer. + // not immediately crash the viewer. Stop and its subclasses are to tell + // coroutines to terminate, e.g. because the viewer is shutting down. We + // do not want any such exception to crash the viewer. struct Stop: public LLContinueError { Stop(const std::string& what): LLContinueError(what) {} }; - /// early stages + /// someone wants to kill this specific coroutine + struct Killed: public Stop + { + Killed(const std::string& what): Stop(what) {} + }; + + /// early shutdown stages struct Stopping: public Stop { Stopping(const std::string& what): Stop(what) {} @@ -278,9 +285,31 @@ public: }; /// Call this intermittently if there's a chance your coroutine might - /// continue running into application shutdown. Throws Stop if LLCoros has - /// been cleaned up. - static void checkStop(); + /// still be running at application shutdown. Throws one of the Stop + /// subclasses if the caller needs to terminate. Pass a cleanup function + /// if you need to execute that cleanup before terminating. + /// Of course, if your cleanup function throws, that will be the exception + /// propagated by checkStop(). + static void checkStop(callable_t cleanup={}); + + /// Call getStopListener() at the source end of a queue, promise or other + /// resource on which coroutines will wait, so that shutdown can wake up + /// consuming coroutines. @a caller should distinguish who's calling. The + /// passed @a cleanup function must close the queue, break the promise or + /// otherwise cause waiting consumers to wake up in an abnormal way. It's + /// advisable to store the returned LLBoundListener in an + /// LLTempBoundListener, or otherwise arrange to disconnect it. + static LLBoundListener getStopListener(const std::string& caller, LLVoidListener cleanup); + + /// This getStopListener() overload is like the two-argument one, for use + /// when we know the name of the only coroutine that will wait on the + /// resource in question. Pass @a consumer as the empty string if the + /// consumer coroutine is the same as the calling coroutine. Unlike the + /// two-argument getStopListener(), this one also responds to + /// killreq(target). + static LLBoundListener getStopListener(const std::string& caller, + const std::string& consumer, + LLVoidListener cleanup); /** * Aliases for promise and future. An older underlying future implementation @@ -312,6 +341,8 @@ private: static CoroData& get_CoroData(const std::string& caller); void saveException(const std::string& name, std::exception_ptr exc); + LLTempBoundListener mConn; + struct ExceptionData { ExceptionData(const std::string& nm, std::exception_ptr exc): @@ -335,8 +366,10 @@ private: // tweaked name of the current coroutine const std::string mName; - // set_consuming() state - bool mConsuming; + // set_consuming() state -- don't consume events unless specifically directed + bool mConsuming{ false }; + // killed by which coroutine + std::string mKilledBy; // setStatus() state std::string mStatus; F64 mCreationTime; // since epoch diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp index 067b5e6fbc..d651aae39c 100644 --- a/indra/llcommon/lleventcoro.cpp +++ b/indra/llcommon/lleventcoro.cpp @@ -119,7 +119,7 @@ void llcoro::suspendUntilTimeout(float seconds) // 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 + // 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 @@ -132,8 +132,8 @@ void llcoro::suspendUntilTimeout(float seconds) // 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. + // LLApp event. The point is that suspendUntilEventOnWithTimeout() will + // itself throw Stopping when LLApp starts broadcasting shutdown events. suspendUntilEventOnWithTimeout(bogus, seconds, timedout); } @@ -167,33 +167,26 @@ postAndSuspendSetup(const std::string& callerName, // "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( + LLCoros::getStopListener( listenerName, + LLCoros::instance().getName(), [&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 " << status + << ", throwing Stopping" << LL_ENDL; + try + { + promise.set_exception( + std::make_exception_ptr( + LLCoros::Stopping("status " + stringize(status)))); + } + catch (const boost::fibers::promise_already_satisfied&) { - 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&) - { - LL_WARNS("lleventcoro") << listenerName - << " couldn't throw Stopping " - "because promise already set" << LL_ENDL; - } + 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( diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp index 332a08a444..08bc65e0c5 100644 --- a/indra/llcommon/lua_function.cpp +++ b/indra/llcommon/lua_function.cpp @@ -31,6 +31,9 @@ #include "lualistener.h" #include "stringize.h" +const S32 INTERRUPTS_MAX_LIMIT = 20000; +const S32 INTERRUPTS_SUSPEND_LIMIT = 100; + #define lua_register(L, n, f) (lua_pushcfunction(L, (f), n), lua_setglobal(L, (n))) #define lua_rawlen lua_objlen @@ -77,6 +80,35 @@ fsyspath lluau::source_path(lua_State* L) return ar.source; } +void lluau::set_interrupts_counter(lua_State *L, S32 counter) +{ + luaL_checkstack(L, 2, nullptr); + lua_pushstring(L, "_INTERRUPTS"); + lua_pushinteger(L, counter); + lua_rawset(L, LUA_REGISTRYINDEX); +} + +void lluau::check_interrupts_counter(lua_State* L) +{ + luaL_checkstack(L, 1, nullptr); + lua_pushstring(L, "_INTERRUPTS"); + lua_rawget(L, LUA_REGISTRYINDEX); + S32 counter = lua_tointeger(L, -1); + lua_pop(L, 1); + + lluau::set_interrupts_counter(L, ++counter); + if (counter > INTERRUPTS_MAX_LIMIT) + { + lluau::error(L, "Possible infinite loop, terminated."); + } + else if (counter % INTERRUPTS_SUSPEND_LIMIT == 0) + { + LL_DEBUGS("Lua") << LLCoros::getName() << " suspending at " << counter << " interrupts" + << LL_ENDL; + llcoro::suspend(); + } +} + /***************************************************************************** * Lua <=> C++ conversions *****************************************************************************/ @@ -485,6 +517,18 @@ bool LuaState::checkLua(const std::string& desc, int r) std::pair<int, LLSD> LuaState::expr(const std::string& desc, const std::string& text) { + lluau::set_interrupts_counter(mState, 0); + + lua_callbacks(mState)->interrupt = [](lua_State *L, int gc) + { + // skip if we're interrupting only for garbage collection + if (gc >= 0) + return; + + LLCoros::checkStop(); + lluau::check_interrupts_counter(L); + }; + if (! checkLua(desc, lluau::dostring(mState, desc, text))) { LL_WARNS("Lua") << desc << " error: " << mError << LL_ENDL; diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h index ec1e6cdb10..e7013f92c6 100644 --- a/indra/llcommon/lua_function.h +++ b/indra/llcommon/lua_function.h @@ -52,6 +52,9 @@ namespace lluau int loadstring(lua_State* L, const std::string& desc, const std::string& text); fsyspath source_path(lua_State* L); + + void set_interrupts_counter(lua_State *L, S32 counter); + void check_interrupts_counter(lua_State* L); } // namespace lluau std::string lua_tostdstring(lua_State* L, int index); diff --git a/indra/llcommon/lualistener.cpp b/indra/llcommon/lualistener.cpp index d4bd73a9fb..5c4989e891 100644 --- a/indra/llcommon/lualistener.cpp +++ b/indra/llcommon/lualistener.cpp @@ -38,21 +38,16 @@ LuaListener::LuaListener(lua_State* L): "LuaListener", [this](const std::string& pump, const LLSD& data) { return queueEvent(pump, data); })), - // Listen for shutdown events on the "LLApp" LLEventPump. + // Listen for shutdown events. mShutdownConnection( - LLEventPumps::instance().obtain("LLLua").listen( - LLEventPump::inventName("LuaState"), - [this](const LLSD& status) + LLCoros::getStopListener( + "LuaState", + mCoroName, + [this](const LLSD&) { - auto coro_name = status["coro"].asString(); - auto statsd = status["status"].asString(); - if ((statsd == "close_all") || ((statsd == "close") && (coro_name == mCoroName))) - { - // If a Lua script is still blocked in getNext() during - // viewer shutdown, close the queue to wake up getNext(). - mQueue.close(); - } - return false; + // If a Lua script is still blocked in getNext() during + // viewer shutdown, close the queue to wake up getNext(). + mQueue.close(); })) {} diff --git a/indra/llcommon/lualistener.h b/indra/llcommon/lualistener.h index d349ee23fd..85fb093cd6 100644 --- a/indra/llcommon/lualistener.h +++ b/indra/llcommon/lualistener.h @@ -73,10 +73,9 @@ private: LLThreadSafeQueue<PumpData> mQueue; + std::string mCoroName; std::unique_ptr<LLLeapListener> mListener; LLTempBoundListener mShutdownConnection; - - std::string mCoroName; }; #endif /* ! defined(LL_LUALISTENER_H) */ diff --git a/indra/llcommon/threadpool.cpp b/indra/llcommon/threadpool.cpp index c48989358e..edccdb097b 100644 --- a/indra/llcommon/threadpool.cpp +++ b/indra/llcommon/threadpool.cpp @@ -18,6 +18,7 @@ // external library headers // other Linden headers #include "commoncontrol.h" +#include "llcoros.h" #include "llerror.h" #include "llevents.h" #include "llsd.h" @@ -90,20 +91,14 @@ void LL::ThreadPoolBase::start() return; } - // Listen on "LLApp", and when the app is shutting down, close the queue - // and join the workers. - LLEventPumps::instance().obtain("LLApp").listen( + // When the app is shutting down, close the queue and join the workers. + mStopListener = LLCoros::getStopListener( mName, - [this](const LLSD& stat) + [this](const LLSD& status) { - std::string status(stat["status"]); - if (status != "running") - { - // viewer is starting shutdown -- proclaim the end is nigh! - LL_DEBUGS("ThreadPool") << mName << " saw " << status << LL_ENDL; - close(); - } - return false; + // viewer is starting shutdown -- proclaim the end is nigh! + LL_DEBUGS("ThreadPool") << mName << " saw " << status << LL_ENDL; + close(); }); } diff --git a/indra/llcommon/threadpool.h b/indra/llcommon/threadpool.h index 74056aea17..7ced7fbf9f 100644 --- a/indra/llcommon/threadpool.h +++ b/indra/llcommon/threadpool.h @@ -13,6 +13,7 @@ #if ! defined(LL_THREADPOOL_H) #define LL_THREADPOOL_H +#include "llcoros.h" #include "threadpool_fwd.h" #include "workqueue.h" #include <memory> // std::unique_ptr @@ -52,8 +53,8 @@ namespace LL void start(); /** - * ThreadPool listens for application shutdown messages on the "LLApp" - * LLEventPump. Call close() to shut down this ThreadPool early. + * ThreadPool listens for application shutdown events. Call close() to + * shut down this ThreadPool early. */ virtual void close(); @@ -95,6 +96,7 @@ namespace LL std::string mName; size_t mThreadCount; + LLTempBoundListener mStopListener; }; /** diff --git a/indra/llcommon/workqueue.cpp b/indra/llcommon/workqueue.cpp index cf80ce0656..800547084a 100644 --- a/indra/llcommon/workqueue.cpp +++ b/indra/llcommon/workqueue.cpp @@ -32,8 +32,11 @@ using Lock = LLCoros::LockType; LL::WorkQueueBase::WorkQueueBase(const std::string& name): super(makeName(name)) { - // TODO: register for "LLApp" events so we can implicitly close() on - // viewer shutdown. + // Register for status change events so we'll implicitly close() on viewer + // shutdown. + mStopListener = LLCoros::getStopListener( + "WorkQueue:" + getKey(), + [this](const LLSD&){ close(); }); } void LL::WorkQueueBase::runUntilClose() diff --git a/indra/llcommon/workqueue.h b/indra/llcommon/workqueue.h index ec0700a718..4c46290f2a 100644 --- a/indra/llcommon/workqueue.h +++ b/indra/llcommon/workqueue.h @@ -13,6 +13,7 @@ #define LL_WORKQUEUE_H #include "llcoros.h" +#include "llevents.h" #include "llexception.h" #include "llinstancetracker.h" #include "llinstancetrackersubclass.h" @@ -194,6 +195,8 @@ namespace LL static std::string makeName(const std::string& name); void callWork(const Work& work); + LLTempBoundListener mStopListener; + private: virtual Work pop_() = 0; virtual bool tryPop_(Work&) = 0; diff --git a/indra/llimage/tests/llimageworker_test.cpp b/indra/llimage/tests/llimageworker_test.cpp index 0a97b739b0..ffcd7d257f 100644 --- a/indra/llimage/tests/llimageworker_test.cpp +++ b/indra/llimage/tests/llimageworker_test.cpp @@ -98,7 +98,7 @@ namespace tut done = res; *done = false; } - virtual void completed(bool success, LLImageRaw* raw, LLImageRaw* aux) + virtual void completed(bool success, LLImageRaw* raw, LLImageRaw* aux, U32) { *done = true; } diff --git a/indra/llmessage/llcoproceduremanager.cpp b/indra/llmessage/llcoproceduremanager.cpp index ebbaea9b12..ad0e0178b6 100644 --- a/indra/llmessage/llcoproceduremanager.cpp +++ b/indra/llmessage/llcoproceduremanager.cpp @@ -307,25 +307,20 @@ LLCoprocedurePool::LLCoprocedurePool(const std::string &poolName, size_t size): { try { - // store in our LLTempBoundListener so that when the LLCoprocedurePool is - // destroyed, we implicitly disconnect from this LLEventPump - // Monitores application status - mStatusListener = LLEventPumps::instance().obtain("LLApp").listen( + // Store in our LLTempBoundListener so that when the LLCoprocedurePool is + // destroyed, we implicitly disconnect from this LLEventPump. + // Monitors application status. + mStatusListener = LLCoros::getStopListener( poolName + "_pool", // Make sure it won't repeat names from lleventcoro - [pendingCoprocs = mPendingCoprocs, poolName](const LLSD& status) - { - auto& statsd = status["status"]; - if (statsd.asString() != "running") + [pendingCoprocs = mPendingCoprocs, poolName](const LLSD& event) { LL_INFOS("CoProcMgr") << "Pool " << poolName - << " closing queue because status " << statsd + << " closing queue because status " << event << LL_ENDL; // This should ensure that all waiting coprocedures in this // pool will wake up and terminate. pendingCoprocs->close(); - } - return false; - }); + }); } catch (const LLEventPump::DupListenerName &) { @@ -334,7 +329,7 @@ LLCoprocedurePool::LLCoprocedurePool(const std::string &poolName, size_t size): // // If this somehow happens again it is better to crash later on shutdown due to pump // not stopping coroutine and see warning in logs than on startup or during login. - LL_WARNS("CoProcMgr") << "Attempted to register dupplicate listener name: " << poolName + LL_WARNS("CoProcMgr") << "Attempted to register duplicate listener name: " << poolName << "_pool. Failed to start listener." << LL_ENDL; llassert(0); // Fix Me! Ignoring missing listener! diff --git a/indra/newview/llfloaterluascripts.cpp b/indra/newview/llfloaterluascripts.cpp index 30353a7210..39d5816b0d 100644 --- a/indra/newview/llfloaterluascripts.cpp +++ b/indra/newview/llfloaterluascripts.cpp @@ -54,8 +54,7 @@ LLFloaterLUAScripts::LLFloaterLUAScripts(const LLSD &key) if (mScriptList->hasSelectedItem()) { std::string coro_name = mScriptList->getSelectedValue(); - LLEventPumps::instance().obtain("LLLua").post(llsd::map("status", "close", "coro", coro_name)); - LLLUAmanager::terminateScript(coro_name); + LLCoros::instance().killreq(coro_name); } }); } diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp index dfa27ebc37..97779a12ad 100644 --- a/indra/newview/llluamanager.cpp +++ b/indra/newview/llluamanager.cpp @@ -49,24 +49,13 @@ #include <vector> std::map<std::string, std::string> LLLUAmanager::sScriptNames; -std::set<std::string> LLLUAmanager::sTerminationList; - -const S32 INTERRUPTS_MAX_LIMIT = 20000; -const S32 INTERRUPTS_SUSPEND_LIMIT = 100; - -void set_interrupts_counter(lua_State *L, S32 counter) -{ - lua_pushstring(L, "_INTERRUPTS"); - lua_pushinteger(L, counter); - lua_rawset(L, LUA_REGISTRYINDEX); -} lua_function(sleep, "sleep(seconds): pause the running coroutine") { F32 seconds = lua_tonumber(L, -1); lua_pop(L, 1); llcoro::suspendUntilTimeout(seconds); - set_interrupts_counter(L, 0); + lluau::set_interrupts_counter(L, 0); return 0; }; @@ -168,7 +157,7 @@ lua_function(get_event_next, const auto& [pump, data]{ listener->getNext() }; lua_pushstdstring(L, pump); lua_pushllsd(L, data); - set_interrupts_counter(L, 0); + lluau::set_interrupts_counter(L, 0); return 2; } @@ -189,25 +178,6 @@ std::pair<int, LLSD> LLLUAmanager::waitScriptFile(const std::string& filename) return startScriptFile(filename).get(); } -void check_interrupts_counter(lua_State* L) -{ - lua_pushstring(L, "_INTERRUPTS"); - lua_rawget(L, LUA_REGISTRYINDEX); - S32 counter = lua_tointeger(L, -1); - lua_pop(L, 1); - - counter++; - if (counter > INTERRUPTS_MAX_LIMIT) - { - lluau::error(L, "Possible infinite loop, terminated."); - } - else if (counter % INTERRUPTS_SUSPEND_LIMIT == 0) - { - llcoro::suspend(); - } - set_interrupts_counter(L, counter); -} - void LLLUAmanager::runScriptFile(const std::string &filename, script_result_fn result_cb, script_finished_fn finished_cb) { // A script_result_fn will be called when LuaState::expr() completes. @@ -222,22 +192,6 @@ void LLLUAmanager::runScriptFile(const std::string &filename, script_result_fn r // A script_finished_fn is used to initialize the LuaState. // It will be called when the LuaState is destroyed. LuaState L(finished_cb); - set_interrupts_counter(L, 0); - - lua_callbacks(L)->interrupt = [](lua_State *L, int gc) - { - // skip if we're interrupting only for garbage collection - if (gc >= 0) - return; - - auto it = sTerminationList.find(LLCoros::getName()); - if (it != sTerminationList.end()) - { - sTerminationList.erase(it); - lluau::error(L, "Script was terminated"); - } - check_interrupts_counter(L); - }; std::string text{std::istreambuf_iterator<char>(in_file), {}}; auto [count, result] = L.expr(filename, text); if (result_cb) diff --git a/indra/newview/llluamanager.h b/indra/newview/llluamanager.h index d671719bc4..af9dcf70c2 100644 --- a/indra/newview/llluamanager.h +++ b/indra/newview/llluamanager.h @@ -85,12 +85,9 @@ public: static void runScriptOnLogin(); static const std::map<std::string, std::string> getScriptNames() { return sScriptNames; } - static std::set<std::string> getTerminationList() { return sTerminationList; } - static void terminateScript(std::string& coro_name) { sTerminationList.insert(coro_name); } private: static std::map<std::string, std::string> sScriptNames; - static std::set<std::string> sTerminationList; }; class LLRequireResolver diff --git a/indra/newview/tests/llluamanager_test.cpp b/indra/newview/tests/llluamanager_test.cpp index b907ac6619..cf1bf25b5c 100644 --- a/indra/newview/tests/llluamanager_test.cpp +++ b/indra/newview/tests/llluamanager_test.cpp @@ -400,9 +400,16 @@ namespace tut auto future = LLLUAmanager::startScriptLine(L, lua); auto replyname{ L.obtainListener()->getReplyName() }; auto& replypump{ LLEventPumps::instance().obtain(replyname) }; - // By the time leap.process() calls get_event_next() and wakes us up, - // we expect that both requester() coroutines have posted and are - // waiting for a reply. + // LuaState::expr() periodically interrupts a running chunk to ensure + // the rest of our coroutines get cycles. Nonetheless, for this test + // we have to wait until both requester() coroutines have posted and + // are waiting for a reply. + for (unsigned count=0; count < 100; ++count) + { + if (requests.size() == 2) + break; + llcoro::suspend(); + } ensure_equals("didn't get both requests", requests.size(), 2); // moreover, we expect they arrived in the order they were created ensure_equals("a wasn't first", requests[0]["name"].asString(), "a"); @@ -413,7 +420,7 @@ namespace tut replypump.post(llsd::map("name", "not special")); // now respond to requester(a) replypump.post(requests[0]); - // tell leap.process() we're done + // tell leap we're done replypump.post(LLSD()); auto [count, result] = future.get(); ensure_equals("leap.lua: " + result.asString(), count, 0); @@ -438,4 +445,24 @@ namespace tut ensure_equals("unexpected killed Lua script error", result.asString(), "viewer is stopping"); } + + template<> template<> + void object::test<8>() + { + set_test_name("stop looping Lua script"); + const std::string desc("looping Lua script should terminate"); + const std::string lua( + "-- " + desc + "\n" + "\n" + "while true do\n" + " x = 1\n" + "end\n" + ); + LuaState L; + auto [count, result] = LLLUAmanager::waitScriptLine(L, lua); + // We expect the above erroneous script has been forcibly terminated + // because it ran too long without doing any actual work. + ensure_equals(desc + " count: " + result.asString(), count, -1); + ensure_contains(desc + " result", result.asString(), "terminated"); + } } // namespace tut diff --git a/indra/test/debug.h b/indra/test/debug.h index 3c4f3cabb4..2f6a114761 100644 --- a/indra/test/debug.h +++ b/indra/test/debug.h @@ -37,29 +37,37 @@ * Debugging stuff *****************************************************************************/ /** - * This class is intended to illuminate entry to a given block, exit from the - * same block and checkpoints along the way. It also provides a convenient - * place to turn std::cerr output on and off. - * - * If the environment variable LOGTEST is non-empty, each Debug instance will - * announce its construction and destruction, presumably at entry and exit to - * the block in which it's declared. Moreover, any arguments passed to its - * operator()() will be streamed to std::cerr, prefixed by the block - * description. + * Return true if the environment variable LOGTEST is non-empty. * * The variable LOGTEST is used because that's the environment variable * checked by test.cpp, our TUT main() program, to turn on LLError logging. It * is expected that Debug is solely for use in test programs. */ +inline +bool LOGTEST_enabled() +{ + auto LOGTEST{ getenv("LOGTEST") }; + // debug output enabled when LOGTEST is set AND non-empty + return LOGTEST && *LOGTEST; +} + +/** + * This class is intended to illuminate entry to a given block, exit from the + * same block and checkpoints along the way. It also provides a convenient + * place to turn std::cerr output on and off. + * + * If enabled, each Debug instance will announce its construction and + * destruction, presumably at entry and exit to the block in which it's + * declared. Moreover, any arguments passed to its operator()() will be + * streamed to std::cerr, prefixed by the block description. + */ class Debug { public: template <typename... ARGS> Debug(ARGS&&... args): mBlock(stringize(std::forward<ARGS>(args)...)), - mLOGTEST(getenv("LOGTEST")), - // debug output enabled when LOGTEST is set AND non-empty - mEnabled(mLOGTEST && *mLOGTEST) + mEnabled(LOGTEST_enabled()) { (*this)("entry"); } @@ -85,7 +93,6 @@ public: private: const std::string mBlock; - const char* mLOGTEST; bool mEnabled; }; @@ -93,4 +100,19 @@ private: // of the Debug block. #define DEBUG Debug debug(LL_PRETTY_FUNCTION) +/// If enabled, debug_expr(expression) gives you output concerning an inline +/// expression such as a class member initializer. +#define debug_expr(expr) debug_expr_(#expr, [&](){ return expr; }) + +template <typename EXPR> +inline auto debug_expr_(const char* strexpr, EXPR&& lambda) +{ + if (! LOGTEST_enabled()) + return std::forward<EXPR>(lambda)(); + print("Before: ", strexpr); + auto result{ std::forward<EXPR>(lambda)() }; + print(strexpr, " -> ", result); + return result; +} + #endif /* ! defined(LL_DEBUG_H) */ |