summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNat Goodspeed <nat@lindenlab.com>2024-03-22 21:04:48 +0900
committerNat Goodspeed <nat@lindenlab.com>2024-03-22 21:04:48 +0900
commitbb39a8b223f78205a10ffcb61e3b3bfe05b3fd1a (patch)
tree77beefb75440e9016a44adfa872227f3790ce3b2
parent0566af988790e95414ed18cd82206710094d8fae (diff)
Fix a couple bugs in fiber.lua machinery.
This fixes a hang if the Lua script explicitly calls fiber.run() before LuaState::expr()'s implicit fiber.run() call. Make fiber.run() remove the calling fiber from the ready list to avoid an infinite loop when all other fibers have terminated: "You're ready!" "Okay, yield()." "You're ready again!" ... But don't claim it's waiting, either, because then when all other fibers have terminated, we'd call idle() in the vain hope that something would make that one last fiber ready. WaitQueue:_wake_waiters() needs to wake waiting fibers if the queue's not empty OR it's been closed. Introduce leap.WaitFor:close() to close the queue gracefully so that a looping waiter can terminate, instead of using WaitFor:exception(), which stops the whole script once it propagates. Make leap's cleanup() function call close(). Streamline fiber.get_name() by using 'or' instead of if ... then. Streamline fiber.status() and fiber.set_waiting() by using table.find() instead of a loop.
-rw-r--r--indra/newview/scripts/lua/ErrorQueue.lua4
-rw-r--r--indra/newview/scripts/lua/WaitQueue.lua2
-rw-r--r--indra/newview/scripts/lua/fiber.lua42
-rw-r--r--indra/newview/scripts/lua/leap.lua9
-rw-r--r--indra/newview/tests/llluamanager_test.cpp2
5 files changed, 32 insertions, 27 deletions
diff --git a/indra/newview/scripts/lua/ErrorQueue.lua b/indra/newview/scripts/lua/ErrorQueue.lua
index a6d4470044..076742815a 100644
--- a/indra/newview/scripts/lua/ErrorQueue.lua
+++ b/indra/newview/scripts/lua/ErrorQueue.lua
@@ -3,18 +3,22 @@
-- raise that error.
local WaitQueue = require('WaitQueue')
+-- local debug = require('printf')
+local function debug(...) end
local ErrorQueue = WaitQueue:new()
function ErrorQueue:Error(message)
-- Setting Error() is a marker, like closing the queue. Once we reach the
-- error, every subsequent Dequeue() call will raise the same error.
+ debug('Setting self._closed to %q', message)
self._closed = message
self:_wake_waiters()
end
function ErrorQueue:Dequeue()
local value = WaitQueue.Dequeue(self)
+ debug('ErrorQueue:Dequeue: base Dequeue() got %s', value)
if value ~= nil then
-- queue not yet closed, show caller
return value
diff --git a/indra/newview/scripts/lua/WaitQueue.lua b/indra/newview/scripts/lua/WaitQueue.lua
index b15e9c443b..f69baff09b 100644
--- a/indra/newview/scripts/lua/WaitQueue.lua
+++ b/indra/newview/scripts/lua/WaitQueue.lua
@@ -38,7 +38,7 @@ function WaitQueue:_wake_waiters()
-- Unlike OS threads, with cooperative concurrency it doesn't make sense
-- to "notify all": we need wake only one of the waiting Dequeue()
-- callers.
- if not self:IsEmpty() and next(self._waiters) then
+ if ((not self:IsEmpty()) or self._closed) and next(self._waiters) then
-- Pop the oldest waiting coroutine instead of the most recent, for
-- more-or-less round robin fairness. But skip any coroutines that
-- have gone dead in the meantime.
diff --git a/indra/newview/scripts/lua/fiber.lua b/indra/newview/scripts/lua/fiber.lua
index f18d133cc8..8ed99f12b7 100644
--- a/indra/newview/scripts/lua/fiber.lua
+++ b/indra/newview/scripts/lua/fiber.lua
@@ -104,13 +104,7 @@ end
-- Query a fiber's name (nil for the running fiber)
function fiber.get_name(co)
- if not co then
- co = fiber.running()
- end
- if not names[co] then
- return 'unknown'
- end
- return names[co]
+ return names[co or fiber.running()] or 'unknown'
end
-- Query status of the passed fiber
@@ -140,10 +134,8 @@ function fiber.status(co)
return 'waiting'
end
-- not waiting should imply ready: sanity check
- for _, maybe in pairs(ready) do
- if maybe == co then
- return 'ready'
- end
+ if table.find(ready, co) then
+ return 'ready'
end
-- Calls within yield() between popping the next ready fiber and
-- re-appending it to the list are in this state. Once we're done
@@ -158,11 +150,9 @@ local function set_waiting()
-- if called from the main fiber, inject a 'main' marker into the list
co = fiber.running()
-- delete from ready list
- for i, maybe in pairs(ready) do
- if maybe == co then
- table.remove(ready, i)
- break
- end
+ local i = table.find(ready, co)
+ if i then
+ table.remove(ready, i)
end
-- add to waiting list
waiting[co] = true
@@ -191,11 +181,16 @@ 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. Tell yield()
- -- that we're waiting. Otherwise it would keep seeing that our caller is
- -- ready and return to us, instead of realizing that all coroutines are
- -- waiting and call idle().
- set_waiting()
+ -- 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())
@@ -204,9 +199,10 @@ function fiber.run()
tostring(others), tostring(idle_done))
until (not others)
debug('%s fiber.run() done', fiber.get_name())
- fiber.wake(fiber.running())
+ -- 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
- -- main, return to main. All previously-launched fibers are done. Possibly
+ -- 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
diff --git a/indra/newview/scripts/lua/leap.lua b/indra/newview/scripts/lua/leap.lua
index 60e8266a76..77f3a3e116 100644
--- a/indra/newview/scripts/lua/leap.lua
+++ b/indra/newview/scripts/lua/leap.lua
@@ -198,10 +198,10 @@ end
local function cleanup(message)
-- we're done: clean up all pending coroutines
for i, waitfor in pairs(leap._pending) do
- waitfor:exception(message)
+ waitfor:close()
end
for i, waitfor in pairs(leap._waitfors) do
- waitfor:exception(message)
+ waitfor:close()
end
end
@@ -407,6 +407,11 @@ function leap.WaitFor:process(item)
self._queue:Enqueue(item)
end
+-- called by cleanup() at end
+function leap.WaitFor:close()
+ self._queue:close()
+end
+
-- called by leap.process() when get_event_next() raises an error
function leap.WaitFor:exception(message)
print_warning(self.name .. ' error: ' .. message)
diff --git a/indra/newview/tests/llluamanager_test.cpp b/indra/newview/tests/llluamanager_test.cpp
index 1dd081fb98..d1beed84ef 100644
--- a/indra/newview/tests/llluamanager_test.cpp
+++ b/indra/newview/tests/llluamanager_test.cpp
@@ -361,8 +361,8 @@ namespace tut
"fiber.launch('requester(a)', requester, 'a')\n"
"-- requester(a)\n"
"fiber.launch('requester(b)', requester, 'b')\n"
+ "fiber.run()\n"
"-- fiber.print_all()\n"
- "-- fiber.run()\n"
);
LLSD requests;