Thu, 23 Mar 2023 12:14:53 +0000
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; }