diff options
Diffstat (limited to 'openresty-ext/src/assembly/resources/openresty/nginx/luaext/vendor')
-rw-r--r-- | openresty-ext/src/assembly/resources/openresty/nginx/luaext/vendor/classic.lua | 68 | ||||
-rw-r--r-- | openresty-ext/src/assembly/resources/openresty/nginx/luaext/vendor/shcache.lua | 440 |
2 files changed, 508 insertions, 0 deletions
diff --git a/openresty-ext/src/assembly/resources/openresty/nginx/luaext/vendor/classic.lua b/openresty-ext/src/assembly/resources/openresty/nginx/luaext/vendor/classic.lua new file mode 100644 index 0000000..d9402f9 --- /dev/null +++ b/openresty-ext/src/assembly/resources/openresty/nginx/luaext/vendor/classic.lua @@ -0,0 +1,68 @@ +-- +-- classic, object model. +-- +-- Copyright (c) 2014, rxi +-- +-- This module is free software; you can redistribute it and/or modify it under +-- the terms of the MIT license. See LICENSE for details. +-- +-- Base object model used with Kong, see [classic github repo](https://github.com/rxi/classic) for usage information + +local Object = {} +Object.__index = Object + + +function Object:new() +end + + +function Object:extend() + local cls = {} + for k, v in pairs(self) do + if k:find("__") == 1 then + cls[k] = v + end + end + cls.__index = cls + cls.super = self + setmetatable(cls, self) + return cls +end + + +function Object:implement(...) + for _, cls in pairs({...}) do + for k, v in pairs(cls) do + if self[k] == nil and type(v) == "function" then + self[k] = v + end + end + end +end + + +function Object:is(T) + local mt = getmetatable(self) + while mt do + if mt == T then + return true + end + mt = getmetatable(mt) + end + return false +end + + +function Object:__tostring() + return "Object" +end + + +function Object:__call(...) + local obj = setmetatable({}, self) + obj:new(...) + return obj +end + + +return Object diff --git a/openresty-ext/src/assembly/resources/openresty/nginx/luaext/vendor/shcache.lua b/openresty-ext/src/assembly/resources/openresty/nginx/luaext/vendor/shcache.lua new file mode 100644 index 0000000..32a9b6c --- /dev/null +++ b/openresty-ext/src/assembly/resources/openresty/nginx/luaext/vendor/shcache.lua @@ -0,0 +1,440 @@ +-- Copyright (C) 2013 Matthieu Tourne +-- @author Matthieu Tourne <matthieu@cloudflare.com> + +-- small overlay over shdict, smart cache load mechanism + +local M = {} + +local resty_lock = require("resty.lock") + +local DEBUG = false + +-- defaults in secs +local DEFAULT_POSITIVE_TTL = 10 -- cache for, successful lookup +local DEFAULT_NEGATIVE_TTL = 2 -- cache for, failed lookup +local DEFAULT_ACTUALIZE_TTL = 2 -- stale data, actualize data for + +-- default lock options, in secs +local function _get_default_lock_options() + return { + exptime = 1, -- max wait if failing to call unlock() + timeout = 0.5, -- max waiting time of lock() + max_step = 0.1, -- max sleeping interval + } +end + +local function prequire(m) + local ok, err_or_module = pcall(require, m) + if not ok then + return nil, err_or_module + end + return err_or_module +end + +local conf = prequire("conf") +if conf then + DEFAULT_NEGATIVE_TTL = conf.DEFAULT_NEGATIVE_TTL or DEFAULT_NEGATIVE_TTL + DEFAULT_ACTUALIZE_TTL = conf.DEFAULT_ACTUALIZE_TTL or DEFAULT_ACTUALIZE_TTL +end + +local band = bit.band +local bor = bit.bor +local st_format = string.format + +-- there are only really 5 states total + -- is_stale is_neg is_from_cache +local MISS_STATE = 0 -- 0 0 0 +local HIT_POSITIVE_STATE = 1 -- 0 0 1 +local HIT_NEGATIVE_STATE = 3 -- 0 1 1 +local STALE_POSITIVE_STATE = 5 -- 1 0 1 + +-- stale negative doesn't really make sense, use HIT_NEGATIVE instead +-- local STALE_NEGATIVE_STATE = 7 -- 1 1 1 + +-- xor to set +local NEGATIVE_FLAG = 2 +local STALE_FLAG = 4 + +local STATES = { + [MISS_STATE] = 'MISS', + [HIT_POSITIVE_STATE] = 'HIT', + [HIT_NEGATIVE_STATE] = 'HIT_NEGATIVE', + [STALE_POSITIVE_STATE] = 'STALE', + -- [STALE_NEGATIVE_STATE] = 'STALE_NEGATIVE', +} + +local function get_status(flags) + return STATES[flags] or st_format('UNDEF (0x%x)', flags) +end + +local EMPTY_DATA = '_EMPTY_' + +-- install debug functions +if DEBUG then + local resty_lock_lock = resty_lock.lock + + resty_lock.lock = function (...) + local _, key = unpack({...}) + print("lock key: ", tostring(key)) + return resty_lock_lock(...) + end + + local resty_lock_unlock = resty_lock.unlock + + resty_lock.unlock = function (...) + print("unlock") + return resty_lock_unlock(...) + end +end + + +-- store the object in the context +-- useful for debugging and tracking cache status +local function _store_object(self, name) + if DEBUG then + print('storing shcache: ', name, ' into ngx.ctx') + end + + local ngx_ctx = ngx.ctx + + if not ngx_ctx.shcache then + ngx_ctx.shcache = {} + end + ngx_ctx.shcache[name] = self +end + +local obj_mt = { + __index = M, +} + +-- default function for callbacks.encode / decode. +local function _identity(data) + return data +end + +-- shdict: ngx.shared.DICT, created by the lua_shared_dict directive +-- callbacks: see shcache state machine for user defined functions +-- * callbacks.external_lookup is required +-- * callbacks.encode : optional encoding before saving to shmem +-- * callbacks.decode : optional decoding when retreiving from shmem +-- opts: +-- * opts.positive_ttl : save a valid external loookup for, in seconds +-- * opts.positive_ttl : save a invalid loookup for, in seconds +-- * opts.actualize_ttl : re-actualize a stale record for, in seconds +-- * opts.lock_options : set option to lock see : http://github.com/agentzh/lua-resty-lock +-- for more details. +-- * opts.locks_shdict : specificy the name of the shdict containing the locks +-- (useful if you might have locks key collisions) +-- uses "locks" by default. +-- * opts.name : if shcache object is named, it will automatically +-- register itself in ngx.ctx.shcache (useful for logging). +local function new(self, shdict, callbacks, opts) + if not shdict then + return nil, "shdict does not exist" + end + + -- check that callbacks.external_lookup is set + if not callbacks or not callbacks.external_lookup then + return nil, "no external_lookup function defined" + end + + if not callbacks.encode then + callbacks.encode = _identity + end + + if not callbacks.decode then + callbacks.decode = _identity + end + + local opts = opts or {} + + -- merge default lock options with the ones passed to new() + local lock_options = _get_default_lock_options() + if opts.lock_options then + for k, v in pairs(opts.lock_options) do + lock_options[k] = v + end + end + + local name = opts.name + + local obj = { + shdict = shdict, + callbacks = callbacks, + + positive_ttl = opts.positive_ttl or DEFAULT_POSITIVE_TTL, + negative_ttl = opts.negative_ttl or DEFAULT_NEGATIVE_TTL, + + -- ttl to actualize stale data to + actualize_ttl = opts.actualize_ttl or DEFAULT_ACTUALIZE_TTL, + + lock_options = lock_options, + + locks_shdict = opts.lock_shdict or "locks", + + -- STATUS -- + + from_cache = false, + cache_status = 'UNDEF', + cache_state = MISS_STATE, + lock_status = 'NO_LOCK', + + -- shdict:set() pushed out another value + forcible_set = false, + + -- cache hit on second attempt (post lock) + hit2 = false, + + name = name, + } + + local locks = ngx.shared[obj.locks_shdict] + + -- check for existence, locks is not directly used + if not locks then + ngx.log(ngx.CRIT, 'shared mem locks is missing.\n', + '## add to you lua conf: lua_shared_dict locks 5M; ##') + return nil + end + + local self = setmetatable(obj, obj_mt) + + -- if the shcache object is named + -- keep track of the object in the context + -- (useful for gathering stats at log phase) + if name then + _store_object(self, name) + end + + return self +end +M.new = new + +-- acquire a lock +local function _get_lock(self) + local lock = self.lock + if not lock then + lock = resty_lock:new(self.locks_shdict, self.lock_options) + self.lock = lock + end + return lock +end + +-- remove the lock if there is any +local function _unlock(self) + local lock = self.lock + if lock then + local ok, err = lock:unlock() + if not ok then + ngx.log(ngx.ERR, "failed to unlock :" , err) + end + self.lock = nil + end +end + +local function _return(self, data, flags) + -- make sure we remove the locks if any before returning data + _unlock(self) + + -- set cache status + local cache_status = get_status(self.cache_state) + + if cache_status == 'MISS' and not data then + cache_status = 'NO_DATA' + end + + self.cache_status = cache_status + + return data, self.from_cache +end + +local function _set(self, ...) + if DEBUG then + local key, data, ttl, flags = unpack({...}) + print("saving key: ", key, ", for: ", ttl) + end + + local ok, err, forcible = self.shdict:set(...) + + self.forcible_set = forcible + + if not ok then + local key, data, ttl, flags = unpack({...}) + ngx.log(ngx.ERR, 'failed to set key: ', key, ', err: ', err) + end + + return ok +end + +-- check if the data returned by :get() is considered empty +local function _is_empty(data, flags) + return flags and band(flags, NEGATIVE_FLAG) and data == EMPTY_DATA +end + +-- save positive, encode the data if needed before :set() +local function _save_positive(self, key, data) + if DEBUG then + print("key: ", key, ". save positive, ttl: ", self.positive_ttl) + end + data = self.callbacks.encode(data) + return _set(self, key, data, self.positive_ttl, HIT_POSITIVE_STATE) +end + +-- save negative, no encoding required (no data actually saved) +local function _save_negative(self, key) + if DEBUG then + print("key: ", key, ". save negative, ttl: ", self.negative_ttl) + end + return _set(self, key, EMPTY_DATA, self.negative_ttl, HIT_NEGATIVE_STATE) +end + +-- save actualize, will boost a stale record to a live one +local function _save_actualize(self, key, data, flags) + local new_flags = bor(flags, STALE_FLAG) + + if DEBUG then + print("key: ", key, ". save actualize, ttl: ", self.actualize_ttl, + ". new state: ", get_status(new_flags)) + end + + _set(self, key, data, self.actualize_ttl, new_flags) + return new_flags +end + +local function _process_cached_data(self, data, flags) + if DEBUG then + print("data: ", data, st_format(", flags: %x", flags)) + end + + self.cache_state = flags + self.from_cache = true + + if _is_empty(data, flags) then + -- empty cached data + return nil + else + return self.callbacks.decode(data) + end +end + +-- wrapper to get data from the shdict +local function _get(self, key) + -- always call get_stale() as it does not free element + -- like get does on each call + local data, flags, stale = self.shdict:get_stale(key) + + if data and stale then + if DEBUG then + print("found stale data for key : ", key) + end + + self.stale_data = { data, flags } + + return nil, nil + end + + return data, flags +end + +local function _get_stale(self) + local stale_data = self.stale_data + if stale_data then + return unpack(stale_data) + end + + return nil, nil +end + +local function load(self, key) + -- start: check for existing cache + local data, flags = _get(self, key) + + -- hit: process_cache_hit + if data then + data = _process_cached_data(self, data, flags) + return _return(self, data) + end + + -- miss: set lock + + -- lock: set a lock before performing external lookup + local lock = _get_lock(self) + local elapsed, err = lock:lock(key) + + if not elapsed then + -- failed to acquire lock, still proceed normally to external_lookup + -- unlock() might fail. + ngx.log(ngx.ERR, "failed to acquire the lock: ", err) + self.lock_status = 'ERROR' + -- _unlock won't try to unlock() without a valid lock + self.lock = nil + else + -- lock acquired successfuly + + if elapsed > 0 then + + -- elapsed > 0 => waited lock (other thread might have :set() the data) + -- (more likely to get a HIT on cache_load 2) + self.lock_status = 'WAITED' + + else + + -- elapsed == 0 => immediate lock + -- it is less likely to get a HIT on cache_load 2 + -- but still perform it (race condition cases) + self.lock_status = 'IMMEDIATE' + end + + -- perform cache_load 2 + data, flags = _get(self, key) + if data then + -- hit2 : process cache hit + + self.hit2 = true + + -- unlock before de-serializing cached data + _unlock(self) + data = _process_cached_data(self, data, flags) + return _return(self, data) + end + + -- continue to external lookup + end + + -- perform external lookup + data, err = self.callbacks.external_lookup() + + if data then + -- succ: save positive and return the data + + _save_positive(self, key, data) + return _return(self, data) + else + ngx.log(ngx.WARN, 'external lookup failed: ', err) + end + + -- external lookup failed + -- attempt to load stale data + data, flags = _get_stale(self) + if data and not _is_empty(data, flags) then + -- hit_stale + valid (positive) data + + flags = _save_actualize(self, key, data, flags) + -- unlock before de-serializing data + _unlock(self) + data = _process_cached_data(self, data, flags) + return _return(self, data) + end + + if DEBUG and data then + -- there is data, but it failed _is_empty() => stale negative data + print('STALE_NEGATIVE data => cache as a new HIT_NEGATIVE') + end + + -- nothing has worked, save negative and return empty + _save_negative(self, key) + return _return(self, nil) +end +M.load = load + +return M
\ No newline at end of file |