scansion/objects/client.lua

Thu, 23 Mar 2023 12:14:53 +0000

author
Matthew Wild <mwild1@gmail.com>
date
Thu, 23 Mar 2023 12:14:53 +0000
changeset 172
2c17151ed21b
parent 171
433a1f36d0d3
child 173
14ed4cb241f4
permissions
-rw-r--r--

client: Fix timeout handling

Previously, the timeout handler would fire an error that would get caught and
logged by the timer code. However that error never reached the upper levels of
scansion, leading to the whole thing just hanging.

Now we just trigger resumption of the async runner, and throw the error from
there if we haven't received the stanza yet.

With this change, timeouts are now correctly handled and reported as failures.

local async = require "scansion.async";
local new_error = require "scansion.error".new_error;
local verse = require "verse".init("client");

local parse_xml = require "scansion.xml".parse;

local default_stanza_timeout = 3;

local stanzacmp = require "scansion.stanzacmp";

local helpers = require "scansion.helpers";

return {
	_validate = function (client)
		assert(client.jid, "No JID specified");
		client.stream = verse.new(verse.new_logger(client.name));
		client.stream.connect_host = client.connect_host
		client.stream.connect_port = client.connect_port
	
		function client.log(fmt, ...)
			return client.stream:info(fmt, ...);
		end

		-- This one prints all received data
		client.stream:hook("incoming-raw", function (s) client.log("Data in:  %s", s); end, 1000);
		client.stream:hook("outgoing-raw", function (s) client.log("Data out: %s", s); end, 1000);
		-- And incoming, parsed, stanzas
		client.stream:hook("stanza", function (s) client.log("Stanza: %s", s) end);
		-- Handle unexpected disconnects
		client.stream:hook("disconnected", function (s)
			if not (client.disconnect_expected or client.script.finished) or (s.reason and s.reason ~= "stream closed" and s.reason ~= "closed") then
				client.log("Unexpected disconnect!");
				error("Unexpected disconnect"..(s.reason and " ("..tostring(s.reason)..")" or ""));
			end
		end);
	end;

	_finish = function (client)
		if client.stream.connected then
			client.disconnect_expected = true;
			client.stream:close();
		end
	end;

	connects = function (client)
		local wait, done = async.waiter();
		client.stream:hook("ready", function ()
			client.stream.conn:pause()
			client.log"ready"
			done()
			client.log("ready done")
		end);
		client.stream:connect_client(client.jid, client.password);
		wait();
		client.full_jid = client.stream.jid;
		client.host = client.stream.host;
	end;

	sends = function (client, data)
		local stanza =  helpers.fill_vars(client.script, assert(parse_xml((table.concat(data):gsub("\t", "  ")))));
		local wait, done = async.waiter();
		local function handle_drained()
			client.stream:unhook("drained", handle_drained);
			done();
		end
		client.stream:hook("drained", handle_drained);
		client.stream:send(stanza);
		wait();
	end;

	receives = function (client, data)
		local wait, done = async.waiter();
		local expected_stanza = false;
		local have_received_stanza = false;
		data = table.concat(data):gsub("\t", "    "):gsub("^%s+", ""):gsub("%s+$", "");
		if data ~= "nothing" then
			expected_stanza = helpers.fill_vars(client.script, assert(parse_xml(data)));
		end
		local function stanza_handler(received_stanza)
			have_received_stanza = true;
			if not expected_stanza then
				error(new_error("unexpected-stanza", {
					text = "Received unexpected stanza";
					stanza = tostring(received_stanza);
				}));
			elseif not expected_stanza or not stanzacmp.stanzas_match(expected_stanza, received_stanza) then
				if not expected_stanza then
					client.log("Received a stanza when none were expected: %s", received_stanza);
				else
					client.log("Expected: %s", expected_stanza);
					client.log("Received: %s", received_stanza);
				end
				error(new_error("unexpected-stanza", {
					text = "Received unexpected stanza";
					stanza = tostring(received_stanza);
					expected = expected_stanza and tostring(expected_stanza) or nil;
				}));
			else
				client.last_received_id = received_stanza.attr.id;
				client.log("YES! %s", expected_stanza)
			end
			expected_stanza = nil;
			client.stream:unhook("stanza", stanza_handler);
			client.stream.conn:pause();
			client.log("Calling done")
			done();
		end
		client.stream:hook("stanza", stanza_handler, 100);
		verse.add_task(client.stanza_timeout or default_stanza_timeout, function ()
			done();
		end);
		client.stream.conn:resume();
		wait();

		if not have_received_stanza then
			if expected_stanza then
				client.log("TIMEOUT waiting for %s", expected_stanza)
				local e = new_error("stanza-timeout", { text = "Timed out waiting for stanza" });
				error(e);
			end
			if expected_stanza == false then
				client.log("Good - no stanzas were received (expected)");
				done();
			end
		end
	end;

	disconnects = function (client)
		client.disconnect_expected = true;
		client.stream:close();
	end;
}

mercurial