Current section

Files

Jump to
charon priv redis_functions.lua
Raw

priv/redis_functions.lua

#!lua name=charon_redis_store_0.0.0+development
-------------
-- PRIVATE --
-------------
local function map(enumerable, func)
local results = {}
for i=1, #enumerable do
results[i] = func(enumerable[i])
end
return results
end
local function expire_all(keys, at)
return map(keys, function (v) return redis.call("EXPIREAT", v, at) end)
end
-- remove sids of expired sessions from all sets
local function prune_expired(session_set_key, exp_oset_key, lock_set_key, now)
local expired = redis.call("ZRANGE", exp_oset_key, "-inf", "("..now, "BYSCORE")
if #expired == 0 then
return {0, 0, 0}
else
return {
redis.call("HDEL", session_set_key, unpack(expired)),
redis.call("ZREM", exp_oset_key, unpack(expired)),
redis.call("HDEL", lock_set_key, unpack(expired))
}
end
end
-- stupid EXPIREAT GT does not work if the key doesn't currently have an exp :|
-- function assumes all keys exist and have the same exp value
local function incr_exps(keys, new_max)
local first_key = keys[1]
local current_exp = redis.call("EXPIRETIME", first_key)
if new_max > current_exp then
return expire_all(keys, new_max)
else
return {0, 0, 0}
end
end
------------
-- PUBLIC --
------------
-- grab max exp from expiration set and update all relevant set expiration values to it
local function resolve_set_exps(keys, args)
local session_set_key, exp_oset_key, lock_set_key = unpack(keys)
local _, score = unpack(redis.call("ZRANGE", exp_oset_key, "+inf", "-inf", "REV", "BYSCORE", "LIMIT", "0", "1", "WITHSCORES"))
if score then
return expire_all({session_set_key, exp_oset_key, lock_set_key}, score)
else
return {0, 0, 0}
end
end
-- upsert a session if its lock_version is not stale
-- the function expects to be called with an *increased* lock version
local function opt_lock_upsert(keys, args)
local session_set_key, exp_oset_key, lock_set_key = unpack(keys)
local sid, lock_version, session, expires_at = unpack(args)
local current_lock = redis.call("HGET", lock_set_key, sid)
-- if current_lock exists, this is an update, not an insert
if current_lock and current_lock + 1 ~= tonumber(lock_version) then
return "CONFLICT"
else
return {
redis.call("HSET", session_set_key, sid, session),
redis.call("ZADD", exp_oset_key, expires_at, sid),
redis.call("HSET", lock_set_key, sid, lock_version),
unpack(incr_exps({session_set_key, exp_oset_key, lock_set_key}, tonumber(expires_at)))
}
end
end
-- prune_expired wrapped in a once-per-hour-mechanism based on a locking key
local function maybe_prune_expired(keys, args)
local session_set_key, exp_oset_key, lock_set_key, prune_lock_key = unpack(keys)
if redis.call("SET", prune_lock_key, "1", "NX", "GET", "EX", 3600) then
return "SKIPPED"
else
local now = unpack(args)
return prune_expired(session_set_key, exp_oset_key, lock_set_key, now)
end
end
redis.register_function('resolve_set_exps_0.0.0+development', resolve_set_exps)
redis.register_function('opt_lock_upsert_0.0.0+development', opt_lock_upsert)
redis.register_function('maybe_prune_expired_0.0.0+development', maybe_prune_expired)