From a1bcbc604e948487e357951d7569f06c2f75949d Mon Sep 17 00:00:00 2001 From: thefosk Date: Thu, 7 Jul 2016 16:56:06 -0700 Subject: [PATCH] Closes #264 --- kong/templates/nginx_kong.lua | 1 + kong/tools/07-cache | 0 kong/tools/database_cache.lua | 32 +++++-- ...che_old.lua => 14-database_cache_spec.lua} | 0 .../07-cache/database_cache_spec.lua | 84 +++++++++++++++++++ .../kong/plugins/database-cache/handler.lua | 35 ++++++++ .../kong/plugins/database-cache/schema.lua | 3 + 7 files changed, 150 insertions(+), 5 deletions(-) create mode 100644 kong/tools/07-cache rename spec/01-unit/{14-database_cache_old.lua => 14-database_cache_spec.lua} (100%) create mode 100644 spec/02-integration/07-cache/database_cache_spec.lua create mode 100644 spec/fixtures/kong/plugins/database-cache/handler.lua create mode 100644 spec/fixtures/kong/plugins/database-cache/schema.lua diff --git a/kong/templates/nginx_kong.lua b/kong/templates/nginx_kong.lua index 202c57b8948..6f5137af9c9 100644 --- a/kong/templates/nginx_kong.lua +++ b/kong/templates/nginx_kong.lua @@ -34,6 +34,7 @@ lua_shared_dict cache ${{MEM_CACHE_SIZE}}; lua_shared_dict reports_locks 100k; lua_shared_dict cluster_locks 100k; lua_shared_dict cluster_autojoin_locks 100k; +lua_shared_dict cache_locks 100k; lua_shared_dict cassandra 1m; lua_shared_dict cassandra_prepared 5m; lua_socket_log_errors off; diff --git a/kong/tools/07-cache b/kong/tools/07-cache new file mode 100644 index 00000000000..e69de29bb2d diff --git a/kong/tools/database_cache.lua b/kong/tools/database_cache.lua index 7e77d238e3d..b4bf8c3bfbc 100644 --- a/kong/tools/database_cache.lua +++ b/kong/tools/database_cache.lua @@ -1,3 +1,4 @@ +local resty_lock = require "resty.lock" local cjson = require "cjson" local cache = ngx.shared.cache @@ -120,21 +121,42 @@ function _M.all_apis_by_dict_key() end function _M.get_or_set(key, cb) + local lock = resty_lock:new("cache_locks", { + exptime = 10, + timeout = 10 + }) + local value, err - -- Try to get + + -- Try to get the value from the cache + value = _M.get(key) + if value then return value end + + -- The value is missing, acquire a lock + local elapsed, err = lock:lock(key) + if not elapsed then + ngx.log(ngx.ERR, "failed to acquire cache lock: "..err) + end + + -- Lock acquired. Since in the meantime another worker may have + -- populated the value we have to check again value = _M.get(key) if not value then -- Get from closure value, err = cb() - if err then - return nil, err - elseif value then + if value then local ok, err = _M.set(key, value) - if not ok then + if err then ngx.log(ngx.ERR, err) end end end + + local ok, err = lock:unlock() + if not ok then + ngx.log(ngx.ERR, "failed to unlock: "..err) + end + return value end diff --git a/spec/01-unit/14-database_cache_old.lua b/spec/01-unit/14-database_cache_spec.lua similarity index 100% rename from spec/01-unit/14-database_cache_old.lua rename to spec/01-unit/14-database_cache_spec.lua diff --git a/spec/02-integration/07-cache/database_cache_spec.lua b/spec/02-integration/07-cache/database_cache_spec.lua new file mode 100644 index 00000000000..2a1d1b4c3fe --- /dev/null +++ b/spec/02-integration/07-cache/database_cache_spec.lua @@ -0,0 +1,84 @@ +local helpers = require "spec.helpers" +local cjson = require "cjson" +local meta = require "kong.meta" + +describe("Resolver", function() + local client + setup(function() + helpers.kill_all() + + assert(helpers.dao.apis:insert { + request_host = "mockbin.com", + upstream_url = "http://mockbin.com" + }) + + assert(helpers.start_kong({ + ["custom_plugins"] = "database-cache", + lua_package_path = "?/init.lua;./kong/?.lua;./spec/fixtures/?.lua" + })) + + -- Add the plugin + local admin_client = helpers.admin_client() + local res = assert(admin_client:send { + method = "POST", + path = "/apis/mockbin.com/plugins/", + body = { + name = "database-cache" + }, + headers = { + ["Content-Type"] = "application/json" + } + }) + assert.res_status(201, res) + admin_client:close() + end) + + teardown(function() + helpers.stop_kong() + end) + + it("avoids dog-pile effect", function() + local function make_request(premature, sleep_time) + local client = helpers.proxy_client() + local res = assert(client:send { + method = "GET", + path = "/status/200?sleep="..sleep_time, + headers = { + ["Host"] = "mockbin.com" + } + }) + res:read_body() + client:close() + end + + local ok, err = ngx.timer.at(0, make_request, 2) + assert.truthy(ok) + + local ok, err = ngx.timer.at(0, make_request, 5) + assert.truthy(ok) + + local ok, err = ngx.timer.at(0, make_request, 1) + assert.truthy(ok) + + helpers.wait_until(function() + local admin_client = helpers.admin_client() + local res = assert(admin_client:send { + method = "GET", + path = "/cache/invocations" + }) + local body = res:read_body() + admin_client:close() + return cjson.decode(body).message == 3 + end, 10) + + -- Invocation are 3, but lookups should be 1 + local admin_client = helpers.admin_client() + local res = assert(admin_client:send { + method = "GET", + path = "/cache/lookups" + }) + local body = res:read_body() + admin_client:close() + assert.equal(1, cjson.decode(body).message) + end) +end) diff --git a/spec/fixtures/kong/plugins/database-cache/handler.lua b/spec/fixtures/kong/plugins/database-cache/handler.lua new file mode 100644 index 00000000000..cb393d9cd18 --- /dev/null +++ b/spec/fixtures/kong/plugins/database-cache/handler.lua @@ -0,0 +1,35 @@ +local BasePlugin = require "kong.plugins.base_plugin" +local cache = require "kong.tools.database_cache" + +local INVOCATIONS = "invocations" +local LOOKUPS = "lookups" + +local DatabaseCacheHandler = BasePlugin:extend() + +DatabaseCacheHandler.PRIORITY = 1000 + +function DatabaseCacheHandler:new() + DatabaseCacheHandler.super.new(self, "database-cache") +end + +function DatabaseCacheHandler:init_worker() + DatabaseCacheHandler.super.init_worker(self) + + cache.rawset(INVOCATIONS, 0) + cache.rawset(LOOKUPS, 0) +end + +function DatabaseCacheHandler:access(conf) + DatabaseCacheHandler.super.access(self) + + cache.get_or_set("pile_effect", function() + cache.incr(LOOKUPS, 1) + -- Adds some delay + ngx.sleep(tonumber(ngx.req.get_uri_args().sleep)) + return true + end) + + cache.incr(INVOCATIONS, 1) +end + +return DatabaseCacheHandler diff --git a/spec/fixtures/kong/plugins/database-cache/schema.lua b/spec/fixtures/kong/plugins/database-cache/schema.lua new file mode 100644 index 00000000000..d02ea8dc964 --- /dev/null +++ b/spec/fixtures/kong/plugins/database-cache/schema.lua @@ -0,0 +1,3 @@ +return { + fields = {} +} \ No newline at end of file