summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
Diffstat (limited to 'indra')
-rw-r--r--indra/newview/scripts/lua/WaitQueue.lua2
-rw-r--r--indra/newview/scripts/lua/fiber.lua106
-rw-r--r--indra/newview/tests/llluamanager_test.cpp43
3 files changed, 108 insertions, 43 deletions
diff --git a/indra/newview/scripts/lua/WaitQueue.lua b/indra/newview/scripts/lua/WaitQueue.lua
index f69baff09b..a34dbef4d7 100644
--- a/indra/newview/scripts/lua/WaitQueue.lua
+++ b/indra/newview/scripts/lua/WaitQueue.lua
@@ -43,7 +43,7 @@ function WaitQueue:_wake_waiters()
-- more-or-less round robin fairness. But skip any coroutines that
-- have gone dead in the meantime.
local waiter = table.remove(self._waiters, 1)
- while waiter and coroutine.status(waiter) ~= "suspended" do
+ while waiter and fiber.status(waiter) == "dead" do
waiter = table.remove(self._waiters, 1)
end
-- do we still have at least one waiting coroutine?
diff --git a/indra/newview/scripts/lua/fiber.lua b/indra/newview/scripts/lua/fiber.lua
index 8ed99f12b7..7dc67f510c 100644
--- a/indra/newview/scripts/lua/fiber.lua
+++ b/indra/newview/scripts/lua/fiber.lua
@@ -178,36 +178,6 @@ function fiber.wake(co)
-- but don't yet resume it: that happens next time we reach yield()
end
--- Run fibers until all but main have terminated: return nil.
--- Or until configured idle() callback returns x ~= nil: return x.
-function fiber.run()
- -- A fiber calling run() is not also doing other useful work. Remove the
- -- calling fiber from the ready list. Otherwise yield() would keep seeing
- -- that our caller is ready and return to us, instead of realizing that
- -- all coroutines are waiting and call idle(). But don't say we're
- -- waiting, either, because then when all other fibers have terminated
- -- we'd call idle() forever waiting for something to make us ready again.
- local i = table.find(ready, fiber.running())
- if i then
- table.remove(ready, i)
- end
- local others, idle_done
- repeat
- debug('%s calling fiber.run() calling yield()', fiber.get_name())
- others, idle_done = fiber.yield()
- debug("%s fiber.run()'s yield() returned %s, %s", fiber.get_name(),
- tostring(others), tostring(idle_done))
- until (not others)
- debug('%s fiber.run() done', fiber.get_name())
- -- For whatever it's worth, put our own fiber back in the ready list.
- table.insert(ready, fiber.running())
- -- Once there are no more waiting fibers, and the only ready fiber is
- -- us, return to caller. All previously-launched fibers are done. Possibly
- -- the chunk is done, or the chunk may decide to launch a new batch of
- -- fibers.
- return idle_done
-end
-
-- pop and return the next not-dead fiber in the ready list, or nil if none remain
local function live_ready_iter()
-- don't write
@@ -237,16 +207,24 @@ local function prune_waiting()
end
end
--- Give other ready fibers a chance to run, leaving this one ready, returning
--- after a cycle. Returns:
--- * true, nil if there remain other live fibers, whether ready or waiting
+-- Run other ready fibers, leaving this one ready, returning after a cycle.
+-- Returns:
+-- * true, nil if there remain other live fibers, whether ready or waiting,
+-- but it's our turn to run
-- * false, nil if this is the only remaining fiber
--- * nil, x if configured idle() callback returned non-nil x
-function fiber.yield()
+-- * nil, x if configured idle() callback returns non-nil x
+local function scheduler()
+ -- scheduler() is asymmetric because Lua distinguishes the main thread
+ -- from other coroutines. The main thread can't yield; it can only resume
+ -- other coroutines. So although an arbitrary coroutine could resume still
+ -- other arbitrary coroutines, it could NOT resume the main thread because
+ -- the main thread can't yield. Therefore, scheduler() delegates its real
+ -- processing to the main thread. If called from a coroutine, pass control
+ -- back to the main thread.
if coroutine.running() then
-- seize the opportunity to make sure the viewer isn't shutting down
-- check_stop()
- -- this is a real coroutine, yield normally to main or whoever
+ -- this is a real coroutine, yield normally to main thread
coroutine.yield()
-- main certainly still exists
return true
@@ -294,4 +272,60 @@ function fiber.yield()
until false
end
+-- Let other fibers run. This is useful in either of two cases:
+-- * fiber.wait() calls this to run other fibers while this one is waiting.
+-- fiber.yield() (and therefore fiber.wait()) works from the main thread as
+-- well as from explicitly-launched fibers, without the caller having to
+-- care.
+-- * A long-running fiber that doesn't often call fiber.wait() should sprinkle
+-- in fiber.yield() calls to interleave processing on other fibers.
+function fiber.yield()
+ -- The difference between this and fiber.run() is that fiber.yield()
+ -- assumes its caller has work to do. yield() returns to its caller as
+ -- soon as scheduler() pops this fiber from the ready list. fiber.run()
+ -- continues looping until all other fibers have terminated, or the
+ -- set_idle() callback tells it to stop.
+ local others, idle_done = scheduler()
+ -- scheduler() returns either if we're ready, or if idle_done ~= nil.
+ if idle_done ~= nil then
+ -- Returning normally from yield() means the caller can carry on with
+ -- its pending work. But in this case scheduler() returned because the
+ -- configured set_idle() function interrupted it -- not because we're
+ -- actually ready. Don't return normally.
+ error('fiber.set_idle() interrupted yield() with: ' .. tostring(idle_done))
+ end
+ -- We're ready! Just return to caller. In this situation we don't care
+ -- whether there are other ready fibers.
+end
+
+-- Run fibers until all but main have terminated: return nil.
+-- Or until configured idle() callback returns x ~= nil: return x.
+function fiber.run()
+ -- A fiber calling run() is not also doing other useful work. Remove the
+ -- calling fiber from the ready list. Otherwise yield() would keep seeing
+ -- that our caller is ready and return to us, instead of realizing that
+ -- all coroutines are waiting and call idle(). But don't say we're
+ -- waiting, either, because then when all other fibers have terminated
+ -- we'd call idle() forever waiting for something to make us ready again.
+ local i = table.find(ready, fiber.running())
+ if i then
+ table.remove(ready, i)
+ end
+ local others, idle_done
+ repeat
+ debug('%s calling fiber.run() calling scheduler()', fiber.get_name())
+ others, idle_done = scheduler()
+ debug("%s fiber.run()'s scheduler() returned %s, %s", fiber.get_name(),
+ tostring(others), tostring(idle_done))
+ until (not others)
+ debug('%s fiber.run() done', fiber.get_name())
+ -- For whatever it's worth, put our own fiber back in the ready list.
+ table.insert(ready, fiber.running())
+ -- Once there are no more waiting fibers, and the only ready fiber is
+ -- us, return to caller. All previously-launched fibers are done. Possibly
+ -- the chunk is done, or the chunk may decide to launch a new batch of
+ -- fibers.
+ return idle_done
+end
+
return fiber
diff --git a/indra/newview/tests/llluamanager_test.cpp b/indra/newview/tests/llluamanager_test.cpp
index d1beed84ef..0fd91c1354 100644
--- a/indra/newview/tests/llluamanager_test.cpp
+++ b/indra/newview/tests/llluamanager_test.cpp
@@ -307,9 +307,43 @@ namespace tut
template<> template<>
void object::test<5>()
{
- set_test_name("test leap.lua");
+ set_test_name("leap.request() from main thread");
const std::string lua(
- "-- test leap.lua\n"
+ "-- leap.request() from main thread\n"
+ "\n"
+ "leap = require 'leap'\n"
+ "\n"
+ "return {\n"
+ " a=leap.request('echo', {data='a'}).data,\n"
+ " b=leap.request('echo', {data='b'}).data\n"
+ "}\n"
+ );
+
+ LLEventStream pump("echo", false);
+ LLTempBoundListener conn{
+ pump.listen(
+ "test<5>()",
+ listener([](const LLSD& data)
+ {
+ LL_DEBUGS("Lua") << "echo pump got: " << data << LL_ENDL;
+ LLEventPumps::instance().post(
+ data["reply"],
+ llsd::map("reqid", data["reqid"], "data", data["data"]));
+ }))
+ };
+
+ LuaState L;
+ auto [count, result] = LLLUAmanager::waitScriptLine(L, lua);
+ ensure_equals("Lua script didn't return item", count, 1);
+ ensure_equals("echo failed", result, llsd::map("a", "a", "b", "b"));
+ }
+
+ template<> template<>
+ void object::test<6>()
+ {
+ set_test_name("interleave leap.request() responses");
+ const std::string lua(
+ "-- interleave leap.request() responses\n"
"\n"
"fiber = require('fiber')\n"
"leap = require('leap')\n"
@@ -359,16 +393,13 @@ namespace tut
"fiber.launch('catchall', drain, catchall)\n"
"fiber.launch('catch_special', drain, catch_special)\n"
"fiber.launch('requester(a)', requester, 'a')\n"
- "-- requester(a)\n"
"fiber.launch('requester(b)', requester, 'b')\n"
- "fiber.run()\n"
- "-- fiber.print_all()\n"
);
LLSD requests;
LLEventStream pump("testpump", false);
LLTempBoundListener conn{
- pump.listen("test<5>()",
+ pump.listen("test<6>()",
listener([&requests](const LLSD& data)
{
LL_DEBUGS("Lua") << "testpump got: " << data << LL_ENDL;