summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authornat-goodspeed <nat@lindenlab.com>2024-08-21 11:52:55 -0400
committerGitHub <noreply@github.com>2024-08-21 11:52:55 -0400
commit7ee93ea34dfc41640f852279888bbee0cafcecbe (patch)
tree371a14729aa2d2ffa2c62fc55fd5d203752eb0d9
parent8324ef8edf68c074f4d30322d37b091b3ea10539 (diff)
parentf2abd050bce3c4132f12d962fc6436d1f06666bd (diff)
Merge pull request #2373 from secondlife/viewer-lua-2237
Fix for #2237: intermittent Lua data stack overflow.
-rw-r--r--indra/llcommon/lua_function.cpp193
-rw-r--r--indra/llcommon/lua_function.h56
-rw-r--r--indra/llcommon/scope_exit.h34
-rwxr-xr-xindra/llcommon/tempset.h2
-rw-r--r--indra/newview/llluamanager.cpp15
-rw-r--r--indra/newview/scripts/lua/require/timers.lua34
6 files changed, 263 insertions, 71 deletions
diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp
index b173d17ede..452bc24b16 100644
--- a/indra/llcommon/lua_function.cpp
+++ b/indra/llcommon/lua_function.cpp
@@ -21,6 +21,7 @@
#include <map>
#include <memory> // std::unique_ptr
#include <typeinfo>
+#include <unordered_map>
// external library headers
// other Linden headers
#include "fsyspath.h"
@@ -54,7 +55,10 @@ namespace
};
} // anonymous namespace
-int lluau::dostring(lua_State* L, const std::string& desc, const std::string& text)
+namespace lluau
+{
+
+int dostring(lua_State* L, const std::string& desc, const std::string& text)
{
auto r = loadstring(L, desc, text);
if (r != LUA_OK)
@@ -66,7 +70,7 @@ int lluau::dostring(lua_State* L, const std::string& desc, const std::string& te
return lua_pcall(L, 0, LUA_MULTRET, 0);
}
-int lluau::loadstring(lua_State *L, const std::string &desc, const std::string &text)
+int loadstring(lua_State *L, const std::string &desc, const std::string &text)
{
size_t bytecodeSize = 0;
// The char* returned by luau_compile() must be freed by calling free().
@@ -76,7 +80,7 @@ int lluau::loadstring(lua_State *L, const std::string &desc, const std::string &
return luau_load(L, desc.data(), bytecode.get(), bytecodeSize, 0);
}
-fsyspath lluau::source_path(lua_State* L)
+fsyspath source_path(lua_State* L)
{
//Luau lua_Debug and lua_getinfo() are different compared to default Lua:
//see https://github.com/luau-lang/luau/blob/80928acb92d1e4b6db16bada6d21b1fb6fa66265/VM/include/lua.h
@@ -93,33 +97,14 @@ fsyspath lluau::source_path(lua_State* L)
return ar.source;
}
-void lluau::set_interrupts_counter(lua_State *L, S32 counter)
-{
- lua_rawsetfield(L, LUA_REGISTRYINDEX, "_INTERRUPTS"sv, lua_Integer(counter));
-}
-
-void lluau::check_interrupts_counter(lua_State* L)
-{
- auto counter = lua_rawgetfield<lua_Integer>(L, LUA_REGISTRYINDEX, "_INTERRUPTS"sv);
-
- 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();
- }
-}
+} // namespace lluau
/*****************************************************************************
* Lua <=> C++ conversions
*****************************************************************************/
std::string lua_tostdstring(lua_State* L, int index)
{
+ lua_checkdelta(L);
size_t len;
const char* strval{ lua_tolstring(L, index, &len) };
return { strval, len };
@@ -127,7 +112,8 @@ std::string lua_tostdstring(lua_State* L, int index)
void lua_pushstdstring(lua_State* L, const std::string& str)
{
- luaL_checkstack(L, 1, nullptr);
+ lua_checkdelta(L, 1);
+ lluau_checkstack(L, 1);
lua_pushlstring(L, str.c_str(), str.length());
}
@@ -148,6 +134,7 @@ void lua_pushstdstring(lua_State* L, const std::string& str)
// reached by that block raises a Lua error.
LLSD lua_tollsd(lua_State* L, int index)
{
+ lua_checkdelta(L);
switch (lua_type(L, index))
{
case LUA_TNONE:
@@ -240,10 +227,10 @@ LLSD lua_tollsd(lua_State* L, int index)
// undefined LLSD. Naturally, though, those won't survive a second
// round trip.
- // This is the most important of the luaL_checkstack() calls because a
+ // This is the most important of the lluau_checkstack() calls because a
// deeply nested Lua structure will enter this case at each level, and
// we'll need another 2 stack slots to traverse each nested table.
- luaL_checkstack(L, 2, nullptr);
+ lluau_checkstack(L, 2);
// BEFORE we push nil to initialize the lua_next() traversal, convert
// 'index' to absolute! Our caller might have passed a relative index;
// we do, below: lua_tollsd(L, -1). If 'index' is -1, then when we
@@ -399,8 +386,9 @@ LLSD lua_tollsd(lua_State* L, int index)
// stack a Lua object corresponding to the passed LLSD object.
void lua_pushllsd(lua_State* L, const LLSD& data)
{
+ lua_checkdelta(L, 1);
// might need 2 slots for array or map
- luaL_checkstack(L, 2, nullptr);
+ lluau_checkstack(L, 2);
switch (data.type())
{
case LLSD::TypeUndefined:
@@ -471,10 +459,23 @@ void lua_pushllsd(lua_State* L, const LLSD& data)
/*****************************************************************************
* LuaState class
*****************************************************************************/
+namespace
+{
+
+// If we find we're running Lua scripts from more than one thread, sLuaStateMap
+// should be thread_local. Until then, avoid the overhead.
+using LuaStateMap = std::unordered_map<lua_State*, LuaState*>;
+static LuaStateMap sLuaStateMap;
+
+} // anonymous namespace
+
LuaState::LuaState(script_finished_fn cb):
mCallback(cb),
mState(luaL_newstate())
{
+ // Ensure that we can always find this LuaState instance, given the
+ // lua_State we just created or any of its coroutines.
+ sLuaStateMap.emplace(mState, this);
luaL_openlibs(mState);
// publish to this new lua_State all the LL entry points we defined using
// the lua_function() macro
@@ -487,11 +488,9 @@ LuaState::LuaState(script_finished_fn cb):
LuaState::~LuaState()
{
- // We're just about to destroy this lua_State mState. lua_close() doesn't
- // implicitly garbage-collect everything, so (for instance) any lingering
- // objects with __gc metadata methods aren't cleaned up. This is why we
- // provide atexit().
- luaL_checkstack(mState, 2, nullptr);
+ // We're just about to destroy this lua_State mState. Did this Lua chunk
+ // register any atexit() functions?
+ lluau_checkstack(mState, 3);
// look up Registry.atexit
lua_getfield(mState, LUA_REGISTRYINDEX, "atexit");
// stack contains Registry.atexit
@@ -502,24 +501,46 @@ LuaState::~LuaState()
// there are no holes, and therefore lua_objlen() should be correct.
// That's important because we walk the atexit table backwards, to
// destroy last the things we created (passed to LL.atexit()) first.
- for (int i(lua_objlen(mState, -1)); i >= 1; --i)
+ int len(lua_objlen(mState, -1));
+ LL_DEBUGS("Lua") << "Registry.atexit is a table with " << len << " entries" << LL_ENDL;
+
+ // Push debug.traceback() onto the stack as lua_pcall()'s error
+ // handler function. On error, lua_pcall() calls the specified error
+ // handler function with the original error message; the message
+ // returned by the error handler is then returned by lua_pcall().
+ // Luau's debug.traceback() is called with a message to prepend to the
+ // returned traceback string. Almost as if they'd been designed to
+ // work together...
+ lua_getglobal(mState, "debug");
+ lua_getfield(mState, -1, "traceback");
+ // ditch "debug"
+ lua_remove(mState, -2);
+ // stack now contains atexit, debug.traceback()
+
+ for (int i(len); i >= 1; --i)
{
lua_pushinteger(mState, i);
- // stack contains Registry.atexit, i
- lua_gettable(mState, -2);
- // stack contains Registry.atexit, atexit[i]
+ // stack contains Registry.atexit, debug.traceback(), i
+ lua_gettable(mState, -3);
+ // stack contains Registry.atexit, debug.traceback(), atexit[i]
// Call atexit[i](), no args, no return values.
// Use lua_pcall() because errors in any one atexit() function
- // shouldn't cancel the rest of them.
- if (lua_pcall(mState, 0, 0, 0) != LUA_OK)
+ // shouldn't cancel the rest of them. Pass debug.traceback() as
+ // the error handler function.
+ LL_DEBUGS("Lua") << "Calling atexit(" << i << ")" << LL_ENDL;
+ if (lua_pcall(mState, 0, 0, -2) != LUA_OK)
{
auto error{ lua_tostdstring(mState, -1) };
- LL_WARNS("Lua") << "atexit() function error: " << error << LL_ENDL;
+ LL_WARNS("Lua") << "atexit(" << i << ") error: " << error << LL_ENDL;
// pop error message
lua_pop(mState, 1);
}
- // lua_pcall() has already popped atexit[i]: stack contains atexit
+ LL_DEBUGS("Lua") << "atexit(" << i << ") done" << LL_ENDL;
+ // lua_pcall() has already popped atexit[i]:
+ // stack contains atexit, debug.traceback()
}
+ // pop debug.traceback()
+ lua_pop(mState, 1);
}
// pop Registry.atexit (either table or nil)
lua_pop(mState, 1);
@@ -530,7 +551,9 @@ LuaState::~LuaState()
{
// mError potentially set by previous checkLua() call(s)
mCallback(mError);
- }
+ }
+ // with the demise of this LuaState, remove sLuaStateMap entry
+ sLuaStateMap.erase(mState);
}
bool LuaState::checkLua(const std::string& desc, int r)
@@ -548,7 +571,7 @@ 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);
+ set_interrupts_counter(0);
lua_callbacks(mState)->interrupt = [](lua_State *L, int gc)
{
@@ -557,7 +580,7 @@ std::pair<int, LLSD> LuaState::expr(const std::string& desc, const std::string&
return;
LLCoros::checkStop();
- lluau::check_interrupts_counter(L);
+ LuaState::getParent(L).check_interrupts_counter();
};
LL_INFOS("Lua") << desc << " run" << LL_ENDL;
@@ -625,7 +648,7 @@ std::pair<int, LLSD> LuaState::expr(const std::string& desc, const std::string&
LuaListener& LuaState::obtainListener(lua_State* L)
{
- luaL_checkstack(L, 2, nullptr);
+ lluau_checkstack(L, 2);
lua_getfield(L, LUA_REGISTRYINDEX, "LuaListener");
// compare lua_type() because lua_isuserdata() also accepts light userdata
if (lua_type(L, -1) != LUA_TUSERDATA)
@@ -649,13 +672,54 @@ LuaListener& LuaState::obtainListener(lua_State* L)
return *listener;
}
+LuaState& LuaState::getParent(lua_State* L)
+{
+ // Look up the LuaState instance associated with the *script*, not the
+ // specific Lua *coroutine*. In other words, first find this lua_State's
+ // main thread.
+ auto found{ sLuaStateMap.find(lua_mainthread(L)) };
+ // Our constructor creates the map entry, our destructor deletes it. As
+ // long as the LuaState exists, we should be able to find it. And we
+ // SHOULD only be talking to a lua_State managed by a LuaState instance.
+ llassert(found != sLuaStateMap.end());
+ return *found->second;
+}
+
+void LuaState::set_interrupts_counter(S32 counter)
+{
+ mInterrupts = counter;
+}
+
+void LuaState::check_interrupts_counter()
+{
+ // The official way to manage data associated with a lua_State is to store
+ // it *as* Lua data within the lua_State. But this method is called by the
+ // Lua engine via lua_callbacks(L)->interrupt, and empirically we've hit
+ // mysterious Lua data stack overflows trying to use stack-based Lua data
+ // access functions in that situation. It seems the Lua engine is capable
+ // of interrupting itself at a moment when re-entry is not valid. So only
+ // touch data in this LuaState.
+ ++mInterrupts;
+ if (mInterrupts > INTERRUPTS_MAX_LIMIT)
+ {
+ lluau::error(mState, "Possible infinite loop, terminated.");
+ }
+ else if (mInterrupts % INTERRUPTS_SUSPEND_LIMIT == 0)
+ {
+ LL_DEBUGS("Lua") << LLCoros::getName() << " suspending at " << mInterrupts
+ << " interrupts" << LL_ENDL;
+ llcoro::suspend();
+ }
+}
+
/*****************************************************************************
* atexit()
*****************************************************************************/
lua_function(atexit, "atexit(function): "
"register Lua function to be called at script termination")
{
- luaL_checkstack(L, 4, nullptr);
+ lua_checkdelta(L, -1);
+ lluau_checkstack(L, 4);
// look up the global name "table"
lua_getglobal(L, "table");
// stack contains function, table
@@ -705,7 +769,7 @@ LuaFunction::LuaFunction(const std::string_view& name, lua_CFunction function,
void LuaFunction::init(lua_State* L)
{
const auto& [registry, lookup] = getRState();
- luaL_checkstack(L, 2, nullptr);
+ lluau_checkstack(L, 2);
// create LL table --
// it happens that we know exactly how many non-array members we want
lua_createtable(L, 0, int(narrow(lookup.size())));
@@ -743,7 +807,8 @@ std::pair<LuaFunction::Registry&, LuaFunction::Lookup&> LuaFunction::getState()
*****************************************************************************/
lua_function(source_path, "source_path(): return the source path of the running Lua script")
{
- luaL_checkstack(L, 1, nullptr);
+ lua_checkdelta(L, 1);
+ lluau_checkstack(L, 1);
lua_pushstdstring(L, lluau::source_path(L).u8string());
return 1;
}
@@ -753,7 +818,8 @@ lua_function(source_path, "source_path(): return the source path of the running
*****************************************************************************/
lua_function(source_dir, "source_dir(): return the source directory of the running Lua script")
{
- luaL_checkstack(L, 1, nullptr);
+ lua_checkdelta(L, 1);
+ lluau_checkstack(L, 1);
lua_pushstdstring(L, lluau::source_path(L).parent_path().u8string());
return 1;
}
@@ -764,6 +830,7 @@ lua_function(source_dir, "source_dir(): return the source directory of the runni
lua_function(abspath, "abspath(path): "
"for given filesystem path relative to running script, return absolute path")
{
+ lua_checkdelta(L);
auto path{ lua_tostdstring(L, 1) };
lua_pop(L, 1);
lua_pushstdstring(L, (lluau::source_path(L).parent_path() / path).u8string());
@@ -775,6 +842,7 @@ lua_function(abspath, "abspath(path): "
*****************************************************************************/
lua_function(check_stop, "check_stop(): ensure that a Lua script responds to viewer shutdown")
{
+ lua_checkdelta(L);
LLCoros::checkStop();
return 0;
}
@@ -979,3 +1047,30 @@ std::ostream& operator<<(std::ostream& out, const lua_stack& self)
out << ']';
return out;
}
+
+/*****************************************************************************
+* LuaStackDelta
+*****************************************************************************/
+LuaStackDelta::LuaStackDelta(lua_State* L, const std::string& where, int delta):
+ L(L),
+ mWhere(where),
+ mDepth(lua_gettop(L)),
+ mDelta(delta)
+{}
+
+LuaStackDelta::~LuaStackDelta()
+{
+ auto depth{ lua_gettop(L) };
+ // If we're unwinding the stack due to an exception, then of course we
+ // can't expect the logic in the block containing this LuaStackDelta
+ // instance to keep its contract wrt the Lua data stack.
+ if (std::uncaught_exceptions() == 0 && mDepth + mDelta != depth)
+ {
+ LL_ERRS("Lua") << mWhere << ": Lua stack went from " << mDepth << " to " << depth;
+ if (mDelta)
+ {
+ LL_CONT << ", rather than expected " << (mDepth + mDelta) << " (" << mDelta << ")";
+ }
+ LL_ENDL;
+ }
+}
diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h
index 7b59af30f5..6965e206ab 100644
--- a/indra/llcommon/lua_function.h
+++ b/indra/llcommon/lua_function.h
@@ -60,11 +60,11 @@ 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
+// must be a macro because LL_PRETTY_FUNCTION is context-sensitive
+#define lluau_checkstack(L, n) luaL_checkstack((L), (n), LL_PRETTY_FUNCTION)
+
std::string lua_tostdstring(lua_State* L, int index);
void lua_pushstdstring(lua_State* L, const std::string& str);
LLSD lua_tollsd(lua_State* L, int index);
@@ -107,10 +107,18 @@ public:
// Find or create LuaListener for passed lua_State.
static LuaListener& obtainListener(lua_State* L);
+ // Given lua_State* L, return the LuaState object managing (the main Lua
+ // thread for) L.
+ static LuaState& getParent(lua_State* L);
+
+ void set_interrupts_counter(S32 counter);
+ void check_interrupts_counter();
+
private:
script_finished_fn mCallback;
lua_State* mState;
std::string mError;
+ S32 mInterrupts{ 0 };
};
/*****************************************************************************
@@ -170,6 +178,32 @@ private:
};
/*****************************************************************************
+* LuaStackDelta
+*****************************************************************************/
+/**
+ * Instantiate LuaStackDelta in a block to compare the Lua data stack depth on
+ * entry (LuaStackDelta construction) and exit. Optionally, pass the expected
+ * depth increment. (But be aware that LuaStackDelta cannot observe the effect
+ * of a LuaPopper or LuaRemover declared previously in the same block.)
+ */
+class LuaStackDelta
+{
+public:
+ LuaStackDelta(lua_State* L, const std::string& where, int delta=0);
+ LuaStackDelta(const LuaStackDelta&) = delete;
+ LuaStackDelta& operator=(const LuaStackDelta&) = delete;
+
+ ~LuaStackDelta();
+
+private:
+ lua_State* L;
+ std::string mWhere;
+ int mDepth, mDelta;
+};
+
+#define lua_checkdelta(L, ...) LuaStackDelta delta(L, LL_PRETTY_FUNCTION, ##__VA_ARGS__)
+
+/*****************************************************************************
* lua_push() wrappers for generic code
*****************************************************************************/
inline
@@ -294,7 +328,8 @@ auto lua_to<void*>(lua_State* L, int index)
template <typename T>
auto lua_getfieldv(lua_State* L, int index, const char* k)
{
- luaL_checkstack(L, 1, nullptr);
+ lua_checkdelta(L);
+ lluau_checkstack(L, 1);
lua_getfield(L, index, k);
LuaPopper pop(L, 1);
return lua_to<T>(L, -1);
@@ -304,7 +339,8 @@ auto lua_getfieldv(lua_State* L, int index, const char* k)
template <typename T>
auto lua_setfieldv(lua_State* L, int index, const char* k, const T& value)
{
- luaL_checkstack(L, 1, nullptr);
+ lua_checkdelta(L);
+ lluau_checkstack(L, 1);
lua_push(L, value);
lua_setfield(L, index, k);
}
@@ -313,7 +349,8 @@ auto lua_setfieldv(lua_State* L, int index, const char* k, const T& value)
template <typename T>
auto lua_rawgetfield(lua_State* L, int index, const std::string_view& k)
{
- luaL_checkstack(L, 1, nullptr);
+ lua_checkdelta(L);
+ lluau_checkstack(L, 1);
lua_pushlstring(L, k.data(), k.length());
lua_rawget(L, index);
LuaPopper pop(L, 1);
@@ -324,7 +361,8 @@ auto lua_rawgetfield(lua_State* L, int index, const std::string_view& k)
template <typename T>
void lua_rawsetfield(lua_State* L, int index, const std::string_view& k, const T& value)
{
- luaL_checkstack(L, 2, nullptr);
+ lua_checkdelta(L);
+ lluau_checkstack(L, 2);
lua_pushlstring(L, k.data(), k.length());
lua_push(L, value);
lua_rawset(L, index);
@@ -430,7 +468,8 @@ DistinctInt TypeTag<T>::value;
template <class T, typename... ARGS>
void lua_emplace(lua_State* L, ARGS&&... args)
{
- luaL_checkstack(L, 1, nullptr);
+ lua_checkdelta(L, 1);
+ lluau_checkstack(L, 1);
int tag{ TypeTag<T>::value };
if (! lua_getuserdatadtor(L, tag))
{
@@ -464,6 +503,7 @@ void lua_emplace(lua_State* L, ARGS&&... args)
template <class T>
T* lua_toclass(lua_State* L, int index)
{
+ lua_checkdelta(L);
// get void* pointer to userdata (if that's what it is)
void* ptr{ lua_touserdatatagged(L, index, TypeTag<T>::value) };
// Derive the T* from ptr. If in future lua_emplace() must manually
diff --git a/indra/llcommon/scope_exit.h b/indra/llcommon/scope_exit.h
new file mode 100644
index 0000000000..00fab069c4
--- /dev/null
+++ b/indra/llcommon/scope_exit.h
@@ -0,0 +1,34 @@
+/**
+ * @file scope_exit.h
+ * @author Nat Goodspeed
+ * @date 2024-08-15
+ * @brief Cheap imitation of std::experimental::scope_exit
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Copyright (c) 2024, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_SCOPE_EXIT_H)
+#define LL_SCOPE_EXIT_H
+
+#include <functional>
+
+namespace LL
+{
+
+class scope_exit
+{
+public:
+ scope_exit(const std::function<void()>& func): mFunc(func) {}
+ scope_exit(const scope_exit&) = delete;
+ scope_exit& operator=(const scope_exit&) = delete;
+ ~scope_exit() { mFunc(); }
+
+private:
+ std::function<void()> mFunc;
+};
+
+} // namespace LL
+
+#endif /* ! defined(LL_SCOPE_EXIT_H) */
diff --git a/indra/llcommon/tempset.h b/indra/llcommon/tempset.h
index e1496bd5fc..07e607a576 100755
--- a/indra/llcommon/tempset.h
+++ b/indra/llcommon/tempset.h
@@ -35,7 +35,7 @@ public:
private:
VAR& mVar;
- VALUE mOldValue;
+ VAR mOldValue;
};
#endif /* ! defined(LL_TEMPSET_H) */
diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp
index 7014c59e4e..22b51d7b72 100644
--- a/indra/newview/llluamanager.cpp
+++ b/indra/newview/llluamanager.cpp
@@ -53,10 +53,11 @@ std::map<std::string, std::string> LLLUAmanager::sScriptNames;
lua_function(sleep, "sleep(seconds): pause the running coroutine")
{
+ lua_checkdelta(L, -1);
F32 seconds = lua_tonumber(L, -1);
lua_pop(L, 1);
llcoro::suspendUntilTimeout(seconds);
- lluau::set_interrupts_counter(L, 0);
+ LuaState::getParent(L).set_interrupts_counter(0);
return 0;
};
@@ -66,7 +67,7 @@ std::string lua_print_msg(lua_State* L, const std::string_view& level)
{
// On top of existing Lua arguments, we're going to push tostring() and
// duplicate each existing stack entry so we can stringize each one.
- luaL_checkstack(L, 2, nullptr);
+ lluau_checkstack(L, 2);
luaL_where(L, 1);
// start with the 'where' info at the top of the stack
std::ostringstream out;
@@ -125,6 +126,7 @@ lua_function(print_warning, "print_warning(args...): WARNING level logging")
lua_function(post_on, "post_on(pumpname, data): post specified data to specified LLEventPump")
{
+ lua_checkdelta(L, -2);
std::string pumpname{ lua_tostdstring(L, 1) };
LLSD data{ lua_tollsd(L, 2) };
lua_pop(L, 2);
@@ -139,7 +141,8 @@ lua_function(get_event_pumps,
"Events posted to replypump are queued for get_event_next().\n"
"post_on(commandpump, ...) to engage LLEventAPI operations (see helpleap()).")
{
- luaL_checkstack(L, 2, nullptr);
+ lua_checkdelta(L, 2);
+ lluau_checkstack(L, 2);
auto& listener{ LuaState::obtainListener(L) };
// return the reply pump name and the command pump name on caller's lua_State
lua_pushstdstring(L, listener.getReplyName());
@@ -153,12 +156,13 @@ lua_function(get_event_next,
"is returned by get_event_pumps(). Blocks the calling chunk until an\n"
"event becomes available.")
{
- luaL_checkstack(L, 2, nullptr);
+ lua_checkdelta(L, 2);
+ lluau_checkstack(L, 2);
auto& listener{ LuaState::obtainListener(L) };
const auto& [pump, data]{ listener.getNext() };
lua_pushstdstring(L, pump);
lua_pushllsd(L, data);
- lluau::set_interrupts_counter(L, 0);
+ LuaState::getParent(L).set_interrupts_counter(0);
return 2;
}
@@ -271,6 +275,7 @@ std::string read_file(const std::string &name)
lua_function(require, "require(module_name) : load module_name.lua from known places")
{
+ lua_checkdelta(L);
std::string name = lua_tostdstring(L, 1);
lua_pop(L, 1);
diff --git a/indra/newview/scripts/lua/require/timers.lua b/indra/newview/scripts/lua/require/timers.lua
index e4938078dc..ab1615ffbf 100644
--- a/indra/newview/scripts/lua/require/timers.lua
+++ b/indra/newview/scripts/lua/require/timers.lua
@@ -34,35 +34,53 @@ function timers.Timer:new(delay, callback, iterate)
callback = callback or function() obj:tick() end
- local first = true
+ local calls = 0
if iterate then
+ -- With iterative timers, beware of running a timer callback which
+ -- performs async actions lasting longer than the timer interval. The
+ -- lengthy callback suspends, allowing leap to retrieve the next
+ -- event, which is a timer tick. leap calls a new instance of the
+ -- callback, even though the previous callback call is still
+ -- suspended... etc. 'in_callback' defends against that recursive
+ -- case. Rather than re-enter the suspended callback, drop the
+ -- too-soon timer event. (We could count the too-soon timer events and
+ -- iterate calling the callback, but it's a bathtub problem: the
+ -- callback could end up getting farther and farther behind.)
+ local in_callback = false
obj.id = leap.eventstream(
'Timers',
{op='scheduleEvery', every=delay},
function (event)
local reqid = event.reqid
- if first then
- first = false
+ calls += 1
+ if calls == 1 then
dbg('timer(%s) first callback', reqid)
-- discard the first (immediate) response: don't call callback
return nil
else
- dbg('timer(%s) nth callback', reqid)
- return callback(event)
+ if in_callback then
+ dbg('dropping timer(%s) callback %d', reqid, calls)
+ else
+ dbg('timer(%s) callback %d', reqid, calls)
+ in_callback = true
+ local ret = callback(event)
+ in_callback = false
+ return ret
+ end
end
end
).reqid
- else
+ else -- (not iterate)
obj.id = leap.eventstream(
'Timers',
{op='scheduleAfter', after=delay},
function (event)
+ calls += 1
-- Arrange to return nil the first time, true the second. This
-- callback is called immediately with the response to
-- 'scheduleAfter', and if we immediately returned true, we'd
-- be done, and the subsequent timer event would be discarded.
- if first then
- first = false
+ if calls == 1 then
-- Caller doesn't expect an immediate callback.
return nil
else