mod_websocket: Fire pre-session-close event (fixes #1800)
This event was added in a7c183bb4e64 and is required to make mod_smacks know
that a session was intentionally closed and shouldn't be hibernated (see
fcea4d9e7502).
Because this was missing from mod_websocket's session.close(), mod_smacks
would always attempt to hibernate websocket sessions even if they closed
cleanly.
That mod_websocket has its own copy of session.close() is something to fix
another day (probably not in the stable branch). So for now this commit makes
the minimal change to get things working again.
Thanks to Damian and the Jitsi team for reporting.
local promise_methods = {};
local promise_mt = { __name = "promise", __index = promise_methods };
local xpcall = require "util.xpcall".xpcall;
local unpack = table.unpack or unpack; --luacheck: ignore 113
function promise_mt:__tostring()
return "promise (" .. (self._state or "invalid") .. ")";
end
local function is_promise(o)
local mt = getmetatable(o);
return mt == promise_mt;
end
local function wrap_handler(f, resolve, reject, default)
if not f then
return default;
end
return function (param)
local ok, ret = xpcall(f, debug.traceback, param);
if ok then
resolve(ret);
else
reject(ret);
end
return true;
end;
end
local function next_pending(self, on_fulfilled, on_rejected, resolve, reject)
table.insert(self._pending_on_fulfilled, wrap_handler(on_fulfilled, resolve, reject, resolve));
table.insert(self._pending_on_rejected, wrap_handler(on_rejected, resolve, reject, reject));
end
local function next_fulfilled(promise, on_fulfilled, on_rejected, resolve, reject) -- luacheck: ignore 212/on_rejected
wrap_handler(on_fulfilled, resolve, reject, resolve)(promise.value);
end
local function next_rejected(promise, on_fulfilled, on_rejected, resolve, reject) -- luacheck: ignore 212/on_fulfilled
wrap_handler(on_rejected, resolve, reject, reject)(promise.reason);
end
local function promise_settle(promise, new_state, new_next, cbs, value)
if promise._state ~= "pending" then
return;
end
promise._state = new_state;
promise._next = new_next;
for _, cb in ipairs(cbs) do
cb(value);
end
-- No need to keep references to callbacks
promise._pending_on_fulfilled = nil;
promise._pending_on_rejected = nil;
return true;
end
local function new_resolve_functions(p)
local resolved = false;
local function _resolve(v)
if resolved then return; end
resolved = true;
if is_promise(v) then
v:next(new_resolve_functions(p));
elseif promise_settle(p, "fulfilled", next_fulfilled, p._pending_on_fulfilled, v) then
p.value = v;
end
end
local function _reject(e)
if resolved then return; end
resolved = true;
if promise_settle(p, "rejected", next_rejected, p._pending_on_rejected, e) then
p.reason = e;
end
end
return _resolve, _reject;
end
local next_tick = function (f)
f();
end
local function new(f)
local p = setmetatable({ _state = "pending", _next = next_pending, _pending_on_fulfilled = {}, _pending_on_rejected = {} }, promise_mt);
if f then
next_tick(function()
local resolve, reject = new_resolve_functions(p);
local ok, ret = xpcall(f, debug.traceback, resolve, reject);
if not ok and p._state == "pending" then
reject(ret);
end
end);
end
return p;
end
local function all(promises)
return new(function (resolve, reject)
local settled, results, loop_finished = 0, {}, false;
local total = 0;
for k, v in pairs(promises) do
if is_promise(v) then
total = total + 1;
v:next(function (value)
results[k] = value;
settled = settled + 1;
if settled == total and loop_finished then
resolve(results);
end
end, reject);
else
results[k] = v;
end
end
loop_finished = true;
if settled == total then
resolve(results);
end
end);
end
local function all_settled(promises)
return new(function (resolve)
local settled, results, loop_finished = 0, {}, false;
local total = 0;
for k, v in pairs(promises) do
if is_promise(v) then
total = total + 1;
v:next(function (value)
results[k] = { status = "fulfilled", value = value };
settled = settled + 1;
if settled == total and loop_finished then
resolve(results);
end
end, function (e)
results[k] = { status = "rejected", reason = e };
settled = settled + 1;
if settled == total and loop_finished then
resolve(results);
end
end);
else
results[k] = v;
end
end
loop_finished = true;
if settled == total then
resolve(results);
end
end);
end
local function join(handler, ...)
local promises, n = { ... }, select("#", ...);
return all(promises):next(function (results)
return handler(unpack(results, 1, n));
end);
end
local function race(promises)
return new(function (resolve, reject)
for i = 1, #promises do
promises[i]:next(resolve, reject);
end
end);
end
local function resolve(v)
return new(function (_resolve)
_resolve(v);
end);
end
local function reject(v)
return new(function (_, _reject)
_reject(v);
end);
end
local function try(f)
return resolve():next(function () return f(); end);
end
function promise_methods:next(on_fulfilled, on_rejected)
return new(function (resolve, reject) --luacheck: ignore 431/resolve 431/reject
self:_next(on_fulfilled, on_rejected, resolve, reject);
end);
end
function promise_methods:catch(on_rejected)
return self:next(nil, on_rejected);
end
function promise_methods:finally(on_finally)
local function _on_finally(value) on_finally(); return value; end
local function _on_catch_finally(err) on_finally(); return reject(err); end
return self:next(_on_finally, _on_catch_finally);
end
return {
new = new;
resolve = resolve;
join = join;
reject = reject;
all = all;
all_settled = all_settled;
race = race;
try = try;
is_promise = is_promise;
set_nexttick = function(new_next_tick) next_tick = new_next_tick; end;
}