diff --git a/kong/global.lua b/kong/global.lua index b3d98cccc1e8..1cceac7b9388 100644 --- a/kong/global.lua +++ b/kong/global.lua @@ -51,11 +51,36 @@ function _GLOBAL.set_named_ctx(self, name, key) error("name cannot be an empty string", 2) end + if key == nil then + error("key cannot be nil", 2) + end + if not self.ctx then error("ctx PDK module not initialized", 2) end - self.ctx.keys[name] = key + self.ctx.__set_namespace(name, key) +end + + +function _GLOBAL.del_named_ctx(self, name) + if not self then + error("arg #1 cannot be nil", 2) + end + + if type(name) ~= "string" then + error("name must be a string", 2) + end + + if #name == 0 then + error("name cannot be an empty string", 2) + end + + if not self.ctx then + error("ctx PDK module not initialized", 2) + end + + self.ctx.__del_namespace(name) end @@ -66,7 +91,7 @@ function _GLOBAL.set_phase(self, phase) local kctx = self.ctx if not kctx then - error("ctx SDK module not initialized", 2) + error("ctx PDK module not initialized", 2) end kctx.core.phase = phase @@ -74,10 +99,7 @@ end do - local log_facilities = { - core = nil, - namespaced = setmetatable({}, { __index = "k" }), - } + local log_facilities = setmetatable({}, { __index = "k" }) function _GLOBAL.set_namespaced_log(self, namespace) @@ -89,13 +111,17 @@ do error("namespace (arg #2) must be a string", 2) end - local log = log_facilities.namespaced[namespace] + if not self.ctx then + error("ctx PDK module not initialized", 2) + end + + local log = log_facilities[namespace] if not log then - log = self.log.new(namespace) -- use default namespaced format - log_facilities.namespaced[namespace] = log + log = self.core_log.new(namespace) -- use default namespaced format + log_facilities[namespace] = log end - self.log = log + self.ctx.core.log = log end @@ -104,98 +130,100 @@ do error("arg #1 cannot be nil", 2) end - self.log = log_facilities.core - end - - - function _GLOBAL.init_pdk(self, kong_config, pdk_major_version) - if not self then - error("arg #1 cannot be nil", 2) + if not self.ctx then + error("ctx PDK module not initialized", 2) end - PDK.new(kong_config, pdk_major_version, self) + self.ctx.core.log = self.core_log + end +end - log_facilities.core = self.log + +function _GLOBAL.init_pdk(self, kong_config, pdk_major_version) + if not self then + error("arg #1 cannot be nil", 2) end + PDK.new(kong_config, pdk_major_version, self) +end - function _GLOBAL.init_worker_events() - -- Note: worker_events will not work correctly if required at the top of the file. - -- It must be required right here, inside the init function - local worker_events = require "resty.worker.events" - local ok, err = worker_events.configure { - shm = "kong_process_events", -- defined by "lua_shared_dict" - timeout = 5, -- life time of event data in shm - interval = 1, -- poll interval (seconds) +function _GLOBAL.init_worker_events() + -- Note: worker_events will not work correctly if required at the top of the file. + -- It must be required right here, inside the init function + local worker_events = require "resty.worker.events" - wait_interval = 0.010, -- wait before retry fetching event data - wait_max = 0.5, -- max wait time before discarding event - } - if not ok then - return nil, err - end + local ok, err = worker_events.configure { + shm = "kong_process_events", -- defined by "lua_shared_dict" + timeout = 5, -- life time of event data in shm + interval = 1, -- poll interval (seconds) - return worker_events + wait_interval = 0.010, -- wait before retry fetching event data + wait_max = 0.5, -- max wait time before discarding event + } + if not ok then + return nil, err end + return worker_events +end - function _GLOBAL.init_cluster_events(kong_config, db) - return kong_cluster_events.new({ - db = db, - poll_interval = kong_config.db_update_frequency, - poll_offset = kong_config.db_update_propagation, - poll_delay = kong_config.db_update_propagation, - }) - end +function _GLOBAL.init_cluster_events(kong_config, db) + return kong_cluster_events.new({ + db = db, + poll_interval = kong_config.db_update_frequency, + poll_offset = kong_config.db_update_propagation, + poll_delay = kong_config.db_update_propagation, + }) +end - function _GLOBAL.init_cache(kong_config, cluster_events, worker_events) - local db_cache_ttl = kong_config.db_cache_ttl - local cache_pages = 1 - if kong_config.database == "off" then - db_cache_ttl = 0 - cache_pages = 2 - end - return kong_cache.new { - shm_name = "kong_db_cache", - cluster_events = cluster_events, - worker_events = worker_events, - ttl = db_cache_ttl, - neg_ttl = db_cache_ttl, - resurrect_ttl = kong_config.resurrect_ttl, - cache_pages = cache_pages, - resty_lock_opts = { - exptime = 10, - timeout = 5, - }, - } - end - - - function _GLOBAL.init_core_cache(kong_config, cluster_events, worker_events) - local db_cache_ttl = kong_config.db_cache_ttl - local cache_pages = 1 - if kong_config.database == "off" then - db_cache_ttl = 0 - cache_pages = 2 - end +function _GLOBAL.init_cache(kong_config, cluster_events, worker_events) + local db_cache_ttl = kong_config.db_cache_ttl + local cache_pages = 1 + if kong_config.database == "off" then + db_cache_ttl = 0 + cache_pages = 2 + end - return kong_cache.new { - shm_name = "kong_core_db_cache", - cluster_events = cluster_events, - worker_events = worker_events, - ttl = db_cache_ttl, - neg_ttl = db_cache_ttl, - resurrect_ttl = kong_config.resurrect_ttl, - cache_pages = cache_pages, - resty_lock_opts = { - exptime = 10, - timeout = 5, - }, - } + return kong_cache.new { + shm_name = "kong_db_cache", + cluster_events = cluster_events, + worker_events = worker_events, + ttl = db_cache_ttl, + neg_ttl = db_cache_ttl, + resurrect_ttl = kong_config.resurrect_ttl, + cache_pages = cache_pages, + resty_lock_opts = { + exptime = 10, + timeout = 5, + }, + } +end + + +function _GLOBAL.init_core_cache(kong_config, cluster_events, worker_events) + local db_cache_ttl = kong_config.db_cache_ttl + local cache_pages = 1 + if kong_config.database == "off" then + db_cache_ttl = 0 + cache_pages = 2 end + + return kong_cache.new { + shm_name = "kong_core_db_cache", + cluster_events = cluster_events, + worker_events = worker_events, + ttl = db_cache_ttl, + neg_ttl = db_cache_ttl, + resurrect_ttl = kong_config.resurrect_ttl, + cache_pages = cache_pages, + resty_lock_opts = { + exptime = 10, + timeout = 5, + }, + } end diff --git a/kong/pdk/ctx.lua b/kong/pdk/ctx.lua index 353164ace197..cbc9918cbb35 100644 --- a/kong/pdk/ctx.lua +++ b/kong/pdk/ctx.lua @@ -4,6 +4,7 @@ local ngx = ngx +local ngx_get_phase = ngx.get_phase -- shared between all global instances @@ -11,6 +12,10 @@ local _CTX_SHARED_KEY = {} local _CTX_CORE_KEY = {} +-- dynamic namespaces, also shared between global instances +local _CTX_NAMESPACES_KEY = {} + + --- -- A table that has the lifetime of the current request and is shared between -- all plugins. It can be used to share data between several plugins in a given @@ -86,19 +91,58 @@ local _CTX_CORE_KEY = {} -- -- kong.log(value) -- "hello world" -- end -local function new(self) - local _CTX = { - -- those would be visible on the *.ctx namespace for now - -- TODO: hide them in a private table shared between this - -- module and the global.lua one - keys = setmetatable({}, { __mode = "v" }), - } +local function new(self) + local _CTX = {} local _ctx_mt = {} + local _ns_mt = { __mode = "v" } + + + local function get_namespaces(nctx) + local namespaces = nctx[_CTX_NAMESPACES_KEY] + if not namespaces then + -- 4 namespaces for request, i.e. ~4 plugins + namespaces = self.table.new(0, 4) + nctx[_CTX_NAMESPACES_KEY] = setmetatable(namespaces, _ns_mt) + end + + return namespaces + end + + + local function set_namespace(namespace, namespace_key) + local nctx = ngx.ctx + local namespaces = get_namespaces(nctx) + + local ns = namespaces[namespace] + if ns and ns == namespace_key then + return + end + + namespaces[namespace] = namespace_key + end + + + local function del_namespace(namespace) + local nctx = ngx.ctx + local namespaces = get_namespaces(nctx) + namespaces[namespace] = nil + end function _ctx_mt.__index(t, k) + if k == "__set_namespace" then + return set_namespace + + elseif k == "__del_namespace" then + return del_namespace + end + + if ngx_get_phase() == "init" then + return + end + local nctx = ngx.ctx local key @@ -109,7 +153,8 @@ local function new(self) key = _CTX_SHARED_KEY else - key = t.keys[k] + local namespaces = get_namespaces(nctx) + key = namespaces[k] end if key then diff --git a/kong/pdk/init.lua b/kong/pdk/init.lua index dc5c49c5dbbe..34ed92206a19 100644 --- a/kong/pdk/init.lua +++ b/kong/pdk/init.lua @@ -211,7 +211,7 @@ assert(package.loaded["resty.core"]) local MAJOR_VERSIONS = { [1] = { - version = "1.3.0", + version = "1.3.1", modules = { "table", "node", @@ -294,7 +294,24 @@ function _PDK.new(kong_config, major_version, self) parent[child] = mod.new(self) end - return self + self._log = self.log + self.log = nil + + return setmetatable(self, { + __index = function(t, k) + if k == "core_log" then + return rawget(t, "_log") + end + + if k == "log" then + if t.ctx.core and t.ctx.core.log then + return t.ctx.core.log + end + + return rawget(t, "_log") + end + end + }) end diff --git a/kong/pdk/log.lua b/kong/pdk/log.lua index e3643ab8c87e..86e88a22974f 100644 --- a/kong/pdk/log.lua +++ b/kong/pdk/log.lua @@ -442,8 +442,10 @@ do self.print = nop end + self.on() + return setmetatable(self, _inspect_mt) end end diff --git a/t/01-pdk/05-client/09-load-consumer.t b/t/01-pdk/05-client/09-load-consumer.t index d092475a68c0..acc240ad403f 100644 --- a/t/01-pdk/05-client/09-load-consumer.t +++ b/t/01-pdk/05-client/09-load-consumer.t @@ -126,4 +126,3 @@ GET /t cannot load a consumer with an id that is not a uuid --- no_error_log [error] - diff --git a/t/02-global/02-set-named-ctx.t b/t/02-global/02-set-named-ctx.t index 7e2478190287..6cbed4df406f 100644 --- a/t/02-global/02-set-named-ctx.t +++ b/t/02-global/02-set-named-ctx.t @@ -80,7 +80,7 @@ marry -=== TEST 3: set_named_ctx() arbitrary namespaces can be discarded +=== TEST 3: set_named_ctx() arbitrary namespaces can be discarded via del_named_ctx() --- http_config eval: $t::Util::HttpConfig --- config location = /t { @@ -93,7 +93,7 @@ marry kong.ctx.custom.cats = "marry" ngx.say(kong.ctx.custom.cats) - kong_global.set_named_ctx(kong, "custom", nil) + kong_global.del_named_ctx(kong, "custom") ngx.say(kong.ctx.custom) } } @@ -181,3 +181,64 @@ GET /t ctx PDK module not initialized --- no_error_log [error] + + + +=== TEST 7: set_named_ctx() arbitrary namespaces are light-thread safe +--- http_config + init_by_lua_block { + kong_global = require "kong.global" + kong = kong_global.new() + kong_global.init_pdk(kong) + } +--- config + location = /lua { + content_by_lua_block { + local args = assert(ngx.req.get_uri_args()) + local thread_name = args.name + local value = args.val + + kong_global.set_named_ctx(kong, "thread", thread_name) + kong.ctx.thread.val = value + + ngx.sleep(0.1) + + ngx.say("thread ", thread_name, ": ", kong.ctx.thread.val) + } + } + + location = /t { + echo_subrequest_async GET '/lua' -q 'name=A&val=hello'; + echo_subrequest_async GET '/lua' -q 'name=B&val=world'; + } +--- request +GET /t +--- response_body +thread A: hello +thread B: world +--- no_error_log +[error] + + + +=== TEST 8: set_named_ctx() arbitrary namespaces invalid argument 'key' +--- http_config eval: $t::Util::HttpConfig +--- config + location = /t { + content_by_lua_block { + local kong_global = require "kong.global" + local kong = kong_global.new() + kong_global.init_pdk(kong) + + local pok, perr = pcall(kong_global.set_named_ctx, kong, "custom", nil) + if not pok then + ngx.say(perr) + end + } + } +--- request +GET /t +--- response_body +key cannot be nil +--- no_error_log +[error] diff --git a/t/02-global/03-namespaced_log.t b/t/02-global/03-namespaced_log.t index d7cd1a4b3bf6..606af7565152 100644 --- a/t/02-global/03-namespaced_log.t +++ b/t/02-global/03-namespaced_log.t @@ -5,7 +5,7 @@ use t::Util; no_long_string(); -plan tests => repeat_each() * (blocks() * 3 + 3); +plan tests => repeat_each() * (blocks() * 3 + 4); run_tests(); @@ -145,3 +145,41 @@ qr/\[notice\] .*? \[kong\] content_by_lua\(nginx\.conf:\d+\):\d+ \[my-other-plug ] --- no_error_log [error] + + + +=== TEST 6: set_namespaced_log() produces light-thread safe namespaces +--- http_config + init_by_lua_block { + kong_global = require "kong.global" + kong = kong_global.new() + kong_global.init_pdk(kong) + } +--- config + location = /lua { + content_by_lua_block { + local args = assert(ngx.req.get_uri_args()) + local thread_name = args.name + + kong_global.set_namespaced_log(kong, thread_name) + + ngx.sleep(0.1) + + kong.log.notice(args.msg) + } + } + + location = /t { + echo_subrequest_async GET '/lua' -q 'name=A&msg=hello'; + echo_subrequest_async GET '/lua' -q 'name=B&msg=world'; + } +--- request +GET /t +--- ignore_response_body +--- error_log eval +[ +qr/\[notice\] .*? \[kong\] content_by_lua\(nginx\.conf:\d+\):\d+ \[A\] hello/, +qr/\[notice\] .*? \[kong\] content_by_lua\(nginx\.conf:\d+\):\d+ \[B\] world/, +] +--- no_error_log +[error]