aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthew Wild <mwild1@gmail.com>2021-11-29 14:14:30 +0000
committerMatthew Wild <mwild1@gmail.com>2021-11-29 14:14:30 +0000
commitd46c43fbeb5e2fcc1d3cccf348ebb92f60f4bc2d (patch)
treefd0045f0a108f19889e8d87dfbf579fe5bd8bd48
parentcfef2c6ef6609d3d917edb144123295f64187545 (diff)
downloadprosody-d46c43fbeb5e2fcc1d3cccf348ebb92f60f4bc2d.tar.gz
prosody-d46c43fbeb5e2fcc1d3cccf348ebb92f60f4bc2d.zip
util.async: Add next-tick configuration
Running woken runners in the next iteration of the event loop prevents unexpected recursion, unexpected tracebacks, and is generally more predictable. The pattern is borrowed from util.promise, where we're now doing the same.
-rw-r--r--spec/util_async_spec.lua46
-rw-r--r--util/async.lua10
2 files changed, 54 insertions, 2 deletions
diff --git a/spec/util_async_spec.lua b/spec/util_async_spec.lua
index 435a844c..77afd1fc 100644
--- a/spec/util_async_spec.lua
+++ b/spec/util_async_spec.lua
@@ -669,4 +669,50 @@ describe("util.async", function()
assert.spy(r.watchers.ready).was.called();
end);
end);
+
+ describe("#set_nexttick()", function ()
+ after_each(function ()
+ -- Restore to default
+ async.set_nexttick(nil);
+ end);
+ it("should work", function ()
+ local queue = {};
+ local nexttick = spy.new(function (f)
+ assert.is_function(f);
+ table.insert(queue, f);
+ end);
+ async.set_nexttick(nexttick);
+
+ local processed_item;
+ local wait, done;
+ local r = new(function (item)
+ wait, done = async.waiter();
+ wait();
+ processed_item = item;
+ end);
+ r:run("test");
+
+ -- Nothing happened, because the runner is waiting
+ assert.is_nil(processed_item);
+ assert.equal(r.state, "waiting");
+ assert.spy(nexttick).was_called(0);
+ assert.spy(r.watchers.waiting).was.called();
+ assert.spy(r.watchers.ready).was_not.called();
+
+ -- Mark the runner as ready, it should be scheduled for
+ -- the next tick
+ done();
+
+ assert.spy(nexttick).was_called(1);
+ assert.spy(nexttick).was_called_with(match.is_function());
+ assert.equal(1, #queue);
+
+ -- Pretend it's the next tick - call the pending function
+ queue[1]();
+
+ assert.equal(processed_item, "test");
+ assert.equal(r.state, "ready");
+ assert.spy(r.watchers.ready).was.called();
+ end);
+ end);
end);
diff --git a/util/async.lua b/util/async.lua
index 551a5e5c..ece589cb 100644
--- a/util/async.lua
+++ b/util/async.lua
@@ -13,6 +13,9 @@ end
-- Configurable functions
local schedule_task = nil; -- schedule_task(seconds, callback)
+local next_tick = function (f)
+ f();
+end
local function runner_from_thread(thread)
local level = 0;
@@ -62,8 +65,10 @@ local function runner_continue(thread)
-- If state is 'ready', it is our responsibility to update runner.state from 'waiting'.
-- We also have to :run(), because the queue might have further items that will not be
-- processed otherwise. FIXME: It's probably best to do this in a nexttick (0 timer).
- runner.state = "ready";
- runner:run();
+ next_tick(function ()
+ runner.state = "ready";
+ runner:run();
+ end);
end
return true;
end
@@ -286,5 +291,6 @@ return {
wait_for = wait_for;
sleep = sleep;
+ set_nexttick = function(new_next_tick) next_tick = new_next_tick; end;
set_schedule_function = function (new_schedule_function) schedule_task = new_schedule_function; end;
};