Changeset

8609:9f6ab206d741

util.async: Ensure runner is left in correct state after out-of-main-loop error (+tests)
author Matthew Wild <mwild1@gmail.com>
date Fri, 16 Mar 2018 22:26:15 +0000
parents 8608:a2e6caf5848d
children 8610:b03c7884fade
files spec/util_async_spec.lua util/async.lua
diffstat 2 files changed, 29 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/spec/util_async_spec.lua	Fri Mar 16 22:19:33 2018 +0000
+++ b/spec/util_async_spec.lua	Fri Mar 16 22:26:15 2018 +0000
@@ -35,9 +35,14 @@
 		describe("#errors", function ()
 			local last_processed_item, last_error;
 			local r;
+			local wait, done;
 			r = async.runner(function (item)
 				if item == "error" then
 					error({ e = "test error" });
+				elseif item == "wait" then
+					wait, done = async.waiter();
+					wait();
+					error({ e = "post wait error" });
 				end
 				last_processed_item = item;
 			end, {
@@ -81,6 +86,24 @@
 				assert.equal(r.state, "ready");
 				assert.equal(last_processed_item, "world");
 			end);
+			it("should work despite a #waiter", function ()
+				-- This test covers an important case where a runner
+				-- throws an error while being executed outside of the
+				-- main loop. This happens when it was blocked ('waiting'),
+				-- and then released (via a call to done()).
+				last_error = nil;
+				r:run("wait");
+				assert.equal(r.state, "waiting");
+				done();
+				-- At this point an error happens (state goes error->ready)
+				assert.equal(r.state, "ready");
+				assert.is_table(last_error);
+				assert.equal(last_error.e, "post wait error");
+				last_error = nil;
+				r:run("hello again");
+				assert.equal(r.state, "ready");
+				assert.equal(last_processed_item, "hello again");
+			end);
 		end);
 	end);
 	describe("#waiter", function()
--- a/util/async.lua	Fri Mar 16 22:19:33 2018 +0000
+++ b/util/async.lua	Fri Mar 16 22:26:15 2018 +0000
@@ -17,13 +17,18 @@
 	end
 	local ok, state, runner = coroutine.resume(thread);
 	if not ok then
+		local err = state;
 		-- Running the coroutine failed, which means we have to find the runner manually,
 		-- in order to inform the error handler
 		local level = 0;
 		while debug.getinfo(thread, level, "") do level = level + 1; end
 		ok, runner = debug.getlocal(thread, level-1, 1);
 		local error_handler = runner.watchers.error;
-		if error_handler then error_handler(runner, debug.traceback(thread, state)); end
+		if error_handler then error_handler(runner, debug.traceback(thread, err)); end
+		local ready_handler = runner.watchers.ready;
+		runner.state, runner.thread = "ready", nil;
+		if ready_handler then ready_handler(runner); end
+		runner.notified_state = "ready";
 	elseif state == "ready" then
 		-- 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