From dd837c451e8a09051fb042d7eded02a124dfb75e Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 8 Jul 2019 01:17:34 +0200 Subject: net.server_epoll: Backport timer optimization 6c2370f17027 from trunk (see #1388) The previous timer handling did not scale well and led to high CPU usage with many connections (each with at least an read timeout). --- net/server_epoll.lua | 77 ++++++++++++++++++++-------------------------------- 1 file changed, 29 insertions(+), 48 deletions(-) diff --git a/net/server_epoll.lua b/net/server_epoll.lua index c279c579..0c03ae15 100644 --- a/net/server_epoll.lua +++ b/net/server_epoll.lua @@ -6,9 +6,7 @@ -- -local t_sort = table.sort; local t_insert = table.insert; -local t_remove = table.remove; local t_concat = table.concat; local setmetatable = setmetatable; local tostring = tostring; @@ -20,6 +18,7 @@ local log = require "util.logger".init("server_epoll"); local socket = require "socket"; local luasec = require "ssl"; local gettime = require "util.time".now; +local indexedbheap = require "util.indexedbheap"; local createtable = require "util.table".create; local inet = require "util.net"; local inet_pton = inet.pton; @@ -66,22 +65,24 @@ local fds = createtable(10, 0); -- FD -> conn -- Timer and scheduling -- -local timers = {}; +local timers = indexedbheap.create(); local function noop() end local function closetimer(t) t[1] = 0; t[2] = noop; + timers:remove(t.id); end --- Set to true when timers have changed -local resort_timers = false; +local function reschedule(t, time) + t[1] = time; + timers:reprioritize(t.id, time); +end -- Add absolute timer local function at(time, f) - local timer = { time, f, close = closetimer }; - t_insert(timers, timer); - resort_timers = true; + local timer = { time, f, close = closetimer, reschedule = reschedule, id = nil }; + timer.id = timers:insert(timer, time); return timer; end @@ -94,50 +95,32 @@ end -- Return time until next timeout local function runtimers(next_delay, min_wait) -- Any timers at all? - if not timers[1] then - return next_delay; + local now = gettime(); + local peek = timers:peek(); + while peek do + + if peek > now then + next_delay = peek - now; + break; end - if resort_timers then - -- Sort earliest timers to the end - t_sort(timers, function (a, b) return a[1] > b[1]; end); - resort_timers = false; + local _, timer, id = timers:pop(); + local ok, ret = pcall(timer[2], now); + if ok and type(ret) == "number" then + local next_time = now+ret; + timer[1] = next_time; + timers:insert(timer, next_time); end - -- Iterate from the end and remove completed timers - for i = #timers, 1, -1 do - local timer = timers[i]; - local t, f = timer[1], timer[2]; - -- Get time for every iteration to increase accuracy - local now = gettime(); - if t > now then - -- This timer should not fire yet - local diff = t - now; - if diff < next_delay then - next_delay = diff; + peek = timers:peek(); end - break; - end - local new_timeout = f(now); - if new_timeout then - -- Schedule for 'delay' from the time actually scheduled, - -- not from now, in order to prevent timer drift. - timer[1] = t + new_timeout; - resort_timers = true; - else - t_remove(timers, i); - end + if peek == nil then + return next_delay; end - if resort_timers or next_delay < min_wait then - -- Timers may be added from within a timer callback. - -- Those would not be considered for next_delay, - -- and we might sleep for too long, so instead - -- we return a shorter timeout so we can - -- properly sort all new timers. - next_delay = min_wait; + if next_delay < min_wait then + return min_wait; end - return next_delay; end @@ -243,8 +226,7 @@ function interface:setreadtimeout(t) end t = t or cfg.read_timeout; if self._readtimeout then - self._readtimeout[1] = gettime() + t; - resort_timers = true; + self._readtimeout:reschedule(gettime() + t); else self._readtimeout = addtimer(t, function () if self:on("readtimeout") then @@ -268,8 +250,7 @@ function interface:setwritetimeout(t) end t = t or cfg.send_timeout; if self._writetimeout then - self._writetimeout[1] = gettime() + t; - resort_timers = true; + self._writetimeout:reschedule(gettime() + t); else self._writetimeout = addtimer(t, function () self:on("disconnect", "write timeout"); -- cgit v1.2.3 From 5399b9b9057f8202ceb25e933d10e1825383bc99 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Mon, 8 Jul 2019 02:46:27 +0200 Subject: util.serialization: Cache default serialization instance (fixes #1389) Most serialization uses still use the default serialize() and thus duplicate much of the setup, which negates some of the performance improvements of the rewrite. --- util/serialization.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/util/serialization.lua b/util/serialization.lua index dd6a2a2b..5121a9f9 100644 --- a/util/serialization.lua +++ b/util/serialization.lua @@ -272,10 +272,15 @@ local function deserialize(str) return ret; end +local default = new(); return { new = new; serialize = function (x, opt) - return new(opt)(x); + if opt == nil then + return default(x); + else + return new(opt)(x); + end end; deserialize = deserialize; }; -- cgit v1.2.3 From 022198143dcc0d586030d183ac8ca3368ff6172b Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Sat, 20 Jul 2019 04:19:58 +0200 Subject: mod_websocket: Clone stanza before mutating (fixes #1398) Checking for `stanza.attr.xmlns == nil` to determine if the stanza object is an actual stanza (``, `` or `` in the `jabber:client` or `jabbber:server` namespace) or some other stream element. Since this mutation is not reverted, it may leak to other places and cause them to mistreat stanzas as stream elements. Especially in cases like MUC where a single stanza is broadcast to many recipients. --- plugins/mod_websocket.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/mod_websocket.lua b/plugins/mod_websocket.lua index b4aba338..686a8981 100644 --- a/plugins/mod_websocket.lua +++ b/plugins/mod_websocket.lua @@ -285,6 +285,7 @@ function handle_request(event) end); add_filter(session, "stanzas/out", function(stanza) + stanza = st.clone(stanza); local attr = stanza.attr; attr.xmlns = attr.xmlns or xmlns_client; if stanza.name:find("^stream:") then -- cgit v1.2.3 From 12431d51d7746c4c1a70fccb2a6ec081b13d1b93 Mon Sep 17 00:00:00 2001 From: Kim Alvefur Date: Tue, 30 Jul 2019 08:01:22 +0200 Subject: MUC: Advertise XEP-0410 support Unsure if the feature was in the XEP at the time of 7c1cdf5f9f83 --- plugins/muc/muc.lib.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/muc/muc.lib.lua b/plugins/muc/muc.lib.lua index 9648ea78..a942182d 100644 --- a/plugins/muc/muc.lib.lua +++ b/plugins/muc/muc.lib.lua @@ -358,6 +358,7 @@ end module:hook("muc-disco#info", function(event) event.reply:tag("feature", {var = "http://jabber.org/protocol/muc"}):up(); event.reply:tag("feature", {var = "http://jabber.org/protocol/muc#stable_id"}):up(); + event.reply:tag("feature", {var = "http://jabber.org/protocol/muc#self-ping-optimization"}):up(); end); module:hook("muc-disco#info", function(event) table.insert(event.form, { name = "muc#roominfo_occupants", label = "Number of occupants" }); -- cgit v1.2.3