summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
authornat-goodspeed <nat@lindenlab.com>2024-04-19 08:55:11 -0400
committerGitHub <noreply@github.com>2024-04-19 08:55:11 -0400
commit40a881dd26cbf0b92e03ec20e296b94e89bdb2c3 (patch)
tree02a60bb2dbaf2e4071150ccb033e9ab8223c5e87 /indra
parent78d985c154b0d71064b89891f5b2005e48c300ce (diff)
parent426dc4da45f91afbb1107aecf075e501e774e8ee (diff)
Merge pull request #1262 from secondlife/lua-kill-coro
Reintroduce `LLCoros::killreq()` to request killing a named coroutine.
Diffstat (limited to 'indra')
-rw-r--r--indra/llcommon/llapp.cpp1
-rw-r--r--indra/llcommon/llcoros.cpp127
-rw-r--r--indra/llcommon/llcoros.h67
-rw-r--r--indra/llcommon/lleventcoro.cpp43
-rw-r--r--indra/llcommon/lua_function.cpp44
-rw-r--r--indra/llcommon/lua_function.h3
-rw-r--r--indra/llcommon/lualistener.cpp21
-rw-r--r--indra/llcommon/lualistener.h3
-rw-r--r--indra/llcommon/threadpool.cpp19
-rw-r--r--indra/llcommon/threadpool.h6
-rw-r--r--indra/llcommon/workqueue.cpp7
-rw-r--r--indra/llcommon/workqueue.h3
-rw-r--r--indra/llimage/tests/llimageworker_test.cpp2
-rw-r--r--indra/llmessage/llcoproceduremanager.cpp21
-rw-r--r--indra/newview/llfloaterluascripts.cpp3
-rw-r--r--indra/newview/llluamanager.cpp50
-rw-r--r--indra/newview/llluamanager.h3
-rw-r--r--indra/newview/tests/llluamanager_test.cpp35
-rw-r--r--indra/test/debug.h48
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) */