diff --git a/lualib/resty/events/compat/callback.lua b/lualib/resty/events/compat/callback.lua deleted file mode 100644 index 10cb4076..00000000 --- a/lualib/resty/events/compat/callback.lua +++ /dev/null @@ -1,260 +0,0 @@ -local cjson = require "cjson.safe" - -local xpcall = xpcall -local type = type -local ipairs = ipairs -local setmetatable = setmetatable -local getmetatable = getmetatable -local next = next -local assert = assert -local tostring = tostring -local traceback = debug.traceback - -local ngx = ngx -local log = ngx.log -local ERR = ngx.ERR -local DEBUG = ngx.DEBUG - -local encode = cjson.encode - --- creates a new level structure for the callback tree -local new_struct = function() - return { - weak_count = 0, - weak_list = setmetatable({},{ __mode = "v"}), - strong_count = 0, - strong_list = {}, - subs = {} -- nested sub tables; source based, and event based - -- (initial one is global) - } -end --- metatable that auto creates sub tables if a key is not found --- __index function to do the auto table magic -local autotable__index = function(self, key) - local mt = getmetatable(self) - local t = new_struct() - if mt.depth ~= 1 then - setmetatable(t.subs, { - __index = mt.__index, - depth = mt.depth - 1, - }) - end - self[key] = t - return t -end - ---- Creates a new auto-table. --- @param depth (optional, default 1) how deep to auto-generate tables. --- The last table in the chain generated will itself not be an auto-table. --- If `depth == 0` then there is no limit. --- @param mode (optional) set the weak table behavior --- @return new auto-table -local function autotable(depth) - - local at = new_struct() - setmetatable(at.subs, { - __index = autotable__index, - depth = depth, - }) - return at -end - --- callbacks -local _callbacks = autotable(2) --- strong/weak; array = global handlers called on every event --- strong/weak; hash = subtables for a specific eventsource --- eventsource-sub-table has the same structure, except the hash part contains --- not 'eventsource', but 'event' specific handlers, no more sub tables - -local _M = { - _VERSION = '0.1.0', -} - -local function do_handlerlist(handler_list, source, event, data, pid) - local err, success - - local count_key = "weak_count" - local list_key = "weak_list" - while true do - local i = 1 - local list = handler_list[list_key] - while i <= handler_list[count_key] do - local handler = list[i] - if type(handler) ~= "function" then - -- handler was removed, unregistered, or GC'ed, cleanup. - -- Entry is nil, but recreated as a table due to the auto-table - list[i] = list[handler_list[count_key]] - list[handler_list[count_key]] = nil - handler_list[count_key] = handler_list[count_key] - 1 - else - success, err = xpcall(handler, traceback, data, event, source, pid) - if not success then - local d, e - if type(data) == "table" then - d, e = encode(data) - if not d then d = tostring(e) end - else - d = tostring(data) - end - log(ERR, "worker-events: event callback failed; source=",source, - ", event=", event,", pid=",pid, " error='", tostring(err), - "', data=", d) - end - i = i + 1 - end - end - if list_key == "strong_list" then - return - end - count_key = "strong_count" - list_key = "strong_list" - end -end - - --- Handle incoming table based event -function _M.do_event(d) - local source = d.source - local event = d.event - local data = d.data - local pid = d.pid - - log(DEBUG, "worker-events: handling event; source=", source, - ", event=", event, ", pid=", pid) --,", data=",tostring(data)) - -- do not log potentially private data, hence skip 'data' - - local list = _callbacks - do_handlerlist(list, source, event, data, pid) - list = list.subs[source] - do_handlerlist(list, source, event, data, pid) - list = list.subs[event] - do_handlerlist(list, source, event, data, pid) -end - --- @param mode either "weak" or "strong" -local register = function(callback, mode, source, ...) - assert(type(callback) == "function", "expected function, got: ".. - type(callback)) - - local count_key, list_key - if mode == "weak" then - count_key = "weak_count" - list_key = "weak_list" - else - count_key = "strong_count" - list_key = "strong_list" - end - - if not source then - -- register as global event handler - local list = _callbacks - local n = list[count_key] + 1 - list[count_key] = n - list[list_key][n] = callback - else - local events = {...} - if #events == 0 then - -- register as an eventsource handler - local list = _callbacks.subs[source] - local n = list[count_key] + 1 - list[count_key] = n - list[list_key][n] = callback - else - -- register as an event specific handler, for multiple events - for _, event in ipairs(events) do - local list = _callbacks.subs[source].subs[event] - local n = list[count_key] + 1 - list[count_key] = n - list[list_key][n] = callback - end - end - end - return true -end - - --- registers an event handler callback. --- signature; callback(source, event, data, originating_pid) --- @param callback the eventhandler callback to add --- @param source (optional) if given only this source is being called for --- @param ... (optional) event names (0 or more) to register for --- @return true -_M.register = function(callback, source, ...) - register(callback, "strong", source, ...) -end - --- registers a weak-event handler callback. --- Workerevents will maintain a weak reference to the handler. --- signature; callback(source, event, data, originating_pid) --- @param callback the eventhandler callback to add --- @param source (optional) if given only this source is being called for --- @param ... (optional) event names (0 or more) to register for --- @return true -_M.register_weak = function(callback, source, ...) - register(callback, "weak", source, ...) -end - --- unregisters an event handler callback. --- Will remove both the weak and strong references. --- @param callback the eventhandler callback to remove --- @return `true` if it was removed, `false` if it was not in the list. --- If multiple eventnames have been specified, `true` means at least 1 --- occurrence was removed -_M.unregister = function(callback, source, ...) - assert(type(callback) == "function", "expected function, got: ".. - type(callback)) - - local success - local count_key = "weak_count" - local list_key = "weak_list" - -- NOTE: we only set entries to `nil`, the event runner will - -- cleanup and remove those entries to 'heal' the lists - while true do - local list = _callbacks - if not source then - -- remove as global event handler - for i = 1, list[count_key] do - local cb = list[list_key][i] - if cb == callback then - list[list_key][i] = nil - success = true - end - end - else - local events = {...} - if not next(events) then - -- remove as an eventsource handler - local target = list.subs[source] - for i = 1, target[count_key] do - local cb = target[list_key][i] - if cb == callback then - target[list_key][i] = nil - success = true - end - end - else - -- remove as an event specific handler, for multiple events - for _, event in ipairs(events) do - local target = list.subs[source].subs[event] - for i = 1, target[count_key] do - local cb = target[list_key][i] - if cb == callback then - target[list_key][i] = nil - success = true - end - end - end - end - end - if list_key == "strong_list" then - break - end - count_key = "strong_count" - list_key = "strong_list" - end - - return (success == true) -end - -return _M - diff --git a/t/callback-compat.t b/t/callback-compat.t deleted file mode 100644 index bad7c8c6..00000000 --- a/t/callback-compat.t +++ /dev/null @@ -1,275 +0,0 @@ -# vim:set ft= ts=4 sw=4 et fdm=marker: -use Test::Nginx::Socket::Lua; - -#worker_connections(1014); -#master_process_enabled(1); -#log_level('warn'); - -#repeat_each(2); - -plan tests => repeat_each() * (blocks() * 6) + 2; - -$ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); - -#no_diff(); -#no_long_string(); -#master_on(); -#workers(2); -run_tests(); - -__DATA__ - -=== TEST 1: registering and unregistering event handlers at different levels ---- http_config - lua_package_path "../lua-resty-core/lib/?.lua;lualib/?.lua;;"; - init_worker_by_lua_block { - local ec = require "resty.events.compat.callback" - local cb = function(extra, data, event, source, pid) - ngx.log(ngx.DEBUG, "worker-events: handler event; ","source=",source,", event=",event, ", pid=", pid, - ", data=", data, ", callback=",extra) - end - - ngx.cb_global = function(...) return cb("global", ...) end - ngx.cb_source = function(...) return cb("source", ...) end - ngx.cb_event12 = function(...) return cb("event12", ...) end - ngx.cb_event3 = function(...) return cb("event3", ...) end - - ec.register(ngx.cb_global) - ec.register(ngx.cb_source, "content_by_lua") - ec.register(ngx.cb_event12, "content_by_lua", "request1", "request2") - ec.register(ngx.cb_event3, "content_by_lua", "request3") - } ---- config - location = /test { - content_by_lua_block { - local ec = require "resty.events.compat.callback" - local pid = ngx.worker.pid() - - local post = function(s, e, d) - ec.do_event({source = s, event = e, data = d, pid = pid}) - end - - post("content_by_lua","request1","123") - post("content_by_lua","request2","123") - post("content_by_lua","request3","123") - - ec.unregister(ngx.cb_global) - - post("content_by_lua","request1","124") - post("content_by_lua","request2","124") - post("content_by_lua","request3","124") - - ec.unregister(ngx.cb_source, "content_by_lua") - - post("content_by_lua","request1","125") - post("content_by_lua","request2","125") - post("content_by_lua","request3","125") - - ec.unregister(ngx.cb_event12, "content_by_lua", "request1", "request2") - post("content_by_lua","request1","126") - post("content_by_lua","request2","126") - post("content_by_lua","request3","126") - - ec.unregister(ngx.cb_event3, "content_by_lua", "request3") - post("content_by_lua","request1","127") - post("content_by_lua","request2","127") - post("content_by_lua","request3","127") - - ngx.say("ok") - } - } ---- request -GET /test ---- response_body -ok ---- no_error_log -[error] -[crit] -[alert] ---- grep_error_log eval: qr/worker-events: .*/ ---- grep_error_log_out eval -qr/^worker-events: handling event; source=content_by_lua, event=request1, pid=\d+ -worker-events: handler event; source=content_by_lua, event=request1, pid=\d+, data=123, callback=global -worker-events: handler event; source=content_by_lua, event=request1, pid=\d+, data=123, callback=source -worker-events: handler event; source=content_by_lua, event=request1, pid=\d+, data=123, callback=event12 -worker-events: handling event; source=content_by_lua, event=request2, pid=\d+ -worker-events: handler event; source=content_by_lua, event=request2, pid=\d+, data=123, callback=global -worker-events: handler event; source=content_by_lua, event=request2, pid=\d+, data=123, callback=source -worker-events: handler event; source=content_by_lua, event=request2, pid=\d+, data=123, callback=event12 -worker-events: handling event; source=content_by_lua, event=request3, pid=\d+ -worker-events: handler event; source=content_by_lua, event=request3, pid=\d+, data=123, callback=global -worker-events: handler event; source=content_by_lua, event=request3, pid=\d+, data=123, callback=source -worker-events: handler event; source=content_by_lua, event=request3, pid=\d+, data=123, callback=event3 -worker-events: handling event; source=content_by_lua, event=request1, pid=\d+ -worker-events: handler event; source=content_by_lua, event=request1, pid=\d+, data=124, callback=source -worker-events: handler event; source=content_by_lua, event=request1, pid=\d+, data=124, callback=event12 -worker-events: handling event; source=content_by_lua, event=request2, pid=\d+ -worker-events: handler event; source=content_by_lua, event=request2, pid=\d+, data=124, callback=source -worker-events: handler event; source=content_by_lua, event=request2, pid=\d+, data=124, callback=event12 -worker-events: handling event; source=content_by_lua, event=request3, pid=\d+ -worker-events: handler event; source=content_by_lua, event=request3, pid=\d+, data=124, callback=source -worker-events: handler event; source=content_by_lua, event=request3, pid=\d+, data=124, callback=event3 -worker-events: handling event; source=content_by_lua, event=request1, pid=\d+ -worker-events: handler event; source=content_by_lua, event=request1, pid=\d+, data=125, callback=event12 -worker-events: handling event; source=content_by_lua, event=request2, pid=\d+ -worker-events: handler event; source=content_by_lua, event=request2, pid=\d+, data=125, callback=event12 -worker-events: handling event; source=content_by_lua, event=request3, pid=\d+ -worker-events: handler event; source=content_by_lua, event=request3, pid=\d+, data=125, callback=event3 -worker-events: handling event; source=content_by_lua, event=request1, pid=\d+ -worker-events: handling event; source=content_by_lua, event=request2, pid=\d+ -worker-events: handling event; source=content_by_lua, event=request3, pid=\d+ -worker-events: handler event; source=content_by_lua, event=request3, pid=\d+, data=126, callback=event3 -worker-events: handling event; source=content_by_lua, event=request1, pid=\d+ -worker-events: handling event; source=content_by_lua, event=request2, pid=\d+ -worker-events: handling event; source=content_by_lua, event=request3, pid=\d+$/ - - -=== TEST 2: registering and GC'ing weak event handlers at different levels ---- http_config - lua_package_path "../lua-resty-core/lib/?.lua;lualib/?.lua;;"; ---- config - location = /test { - content_by_lua_block { - local ec = require "resty.events.compat.callback" - - local post = function(s, e, d) - ec.do_event({source = s, event = e, data = d, pid = pid}) - end - - local count = 0 - - local cb = { - global = function(source, event) - ngx.log(ngx.DEBUG, "global handler: ", source, ", ", event) - count = count + 1 - end, - source = function(source, event) - ngx.log(ngx.DEBUG, "global source: ", source, ", ", event) - count = count + 1 - end, - event12 = function(source, event) - ngx.log(ngx.DEBUG, "global event12: ", source, ", ", event) - count = count + 1 - end, - event3 = function(source, event) - ngx.log(ngx.DEBUG, "global event3: ", source, ", ", event) - count = count + 1 - end, - } - - ec.register_weak(cb.global) - ec.register_weak(cb.source, "content_by_lua") - ec.register_weak(cb.event12, "content_by_lua", "request1", "request2") - ec.register_weak(cb.event3, "content_by_lua", "request3") - - post("content_by_lua","request1","123") - post("content_by_lua","request2","123") - post("content_by_lua","request3","123") - - ngx.say("before GC:", count) - - cb = nil - collectgarbage() - collectgarbage() - count = 0 - - post("content_by_lua","request1","123") - post("content_by_lua","request2","123") - post("content_by_lua","request3","123") - - ngx.say("after GC:", count) -- 0 - } - } ---- request -GET /test ---- response_body -before GC:9 -after GC:0 ---- no_error_log -[error] -[crit] -[alert] -[emerg] - - -=== TEST 3: callback error handling ---- http_config - lua_package_path "../lua-resty-core/lib/?.lua;lualib/?.lua;;"; ---- config - location = /test { - content_by_lua_block { - local ec = require "resty.events.compat.callback" - - local post = function(s, e, d) - ec.do_event({source = s, event = e, data = d, pid = pid}) - end - - local error_func = function() - error("something went wrong here!") - end - local test_callback = function(source, event, data, pid) - error_func() -- nested call to check stack trace - end - ec.register(test_callback) - - -- non-serializable test data containing a function value - -- use "nil" as data, reproducing issue #5 - post("content_by_lua","test_event", nil) - - ngx.say("ok") - } - } ---- request -GET /test ---- response_body -ok ---- error_log -something went wrong here! ---- no_error_log -[crit] -[alert] -[emerg] - - -=== TEST 4: callback error stacktrace ---- http_config - lua_package_path "../lua-resty-core/lib/?.lua;lualib/?.lua;;"; ---- config - location = /test { - content_by_lua_block { - local ec = require "resty.events.compat.callback" - - local post = function(s, e, d) - ec.do_event({source = s, event = e, data = d, pid = pid}) - end - - local error_func = function() - error("something went wrong here!") - end - local in_between = function() - error_func() -- nested call to check stack trace - end - local test_callback = function(source, event, data, pid) - in_between() -- nested call to check stack trace - end - - ec.register(test_callback) - post("content_by_lua","test_event") - - ngx.say("ok") - } - } ---- request -GET /test ---- response_body -ok ---- error_log -something went wrong here! -in function 'error_func' -in function 'in_between' ---- no_error_log -[crit] -[alert] -[emerg] - -