pub(super) const CLAIM_SCRIPT: &str = r#"
local now = redis.call('TIME')
local claimed_at_ms = (tonumber(now[1]) * 1000) + math.floor(tonumber(now[2]) / 1000)
local due = redis.call('ZRANGEBYSCORE', KEYS[5], '-inf', claimed_at_ms, 'LIMIT', 0, 100)
for _, scheduled_raw in ipairs(due) do
if redis.call('ZREM', KEYS[5], scheduled_raw) == 1 then
redis.call('RPUSH', KEYS[1], scheduled_raw)
end
end
local raw = redis.call('LPOP', KEYS[1])
if not raw then return nil end
local ok, envelope = pcall(cjson.decode, raw)
if ok and type(envelope) == 'table' and type(envelope.attempts) == 'number' then
envelope.attempts = envelope.attempts + 1
raw = cjson.encode(envelope)
end
if ok and type(envelope) == 'table' and type(envelope.id) == 'string' and envelope.id ~= '' then
if redis.call('HEXISTS', KEYS[3], envelope.id) == 1 then
redis.call('RPUSH', KEYS[4], cjson.encode({ raw = raw, error = 'duplicate processing job id' }))
redis.call('LTRIM', KEYS[4], -tonumber(ARGV[1]), -1)
return redis.error_reply('duplicate processing job id')
end
redis.call('HSET', KEYS[3], envelope.id, raw)
end
redis.call('ZADD', KEYS[2], claimed_at_ms, raw)
return raw
"#;
pub(super) const REJECT_SCRIPT: &str = r#"
redis.call('ZREM', KEYS[1], ARGV[1])
local ok, envelope = pcall(cjson.decode, ARGV[1])
if ok and type(envelope) == 'table' and type(envelope.id) == 'string' then
redis.call('HDEL', KEYS[2], envelope.id)
end
redis.call('RPUSH', KEYS[3], cjson.encode({ raw = ARGV[1], error = ARGV[2] }))
redis.call('LTRIM', KEYS[3], -tonumber(ARGV[3]), -1)
return 1
"#;
pub(super) const COMPLETE_SCRIPT: &str = r#"
local raw = redis.call('HGET', KEYS[2], ARGV[1])
if not raw then return 0 end
local expected = ARGV[2]
if expected and expected ~= '' then
local ok, envelope = pcall(cjson.decode, raw)
if not ok or type(envelope) ~= 'table' or envelope.attempts ~= tonumber(expected) then return 0 end
end
redis.call('ZREM', KEYS[1], raw)
redis.call('HDEL', KEYS[2], ARGV[1])
return 1
"#;
pub(super) const FAIL_SCRIPT: &str = r#"
local raw = redis.call('HGET', KEYS[2], ARGV[1])
if not raw then return 0 end
local expected = ARGV[3]
if expected and expected ~= '' then
local ok, envelope = pcall(cjson.decode, raw)
if not ok or type(envelope) ~= 'table' or envelope.attempts ~= tonumber(expected) then return 0 end
end
local now = redis.call('TIME')
local failed_at_ms = (tonumber(now[1]) * 1000) + math.floor(tonumber(now[2]) / 1000)
redis.call('ZREM', KEYS[1], raw)
redis.call('HDEL', KEYS[2], ARGV[1])
redis.call('HSET', KEYS[3], ARGV[1], cjson.encode({ raw = raw, error = ARGV[2] }))
redis.call('ZADD', KEYS[4], failed_at_ms, ARGV[1])
local excess = redis.call('ZCARD', KEYS[4]) - tonumber(ARGV[4])
if excess > 0 then
for _, evicted in ipairs(redis.call('ZRANGE', KEYS[4], 0, excess - 1)) do
redis.call('HDEL', KEYS[3], evicted)
end
redis.call('ZREMRANGEBYRANK', KEYS[4], 0, excess - 1)
end
return 1
"#;
pub(super) const REQUEUE_SCRIPT: &str = r#"
local raw = redis.call('HGET', KEYS[2], ARGV[1])
if not raw then return 0 end
local expected = ARGV[3]
if expected and expected ~= '' then
local ok, envelope = pcall(cjson.decode, raw)
if not ok or type(envelope) ~= 'table' or envelope.attempts ~= tonumber(expected) then return 0 end
end
redis.call('ZREM', KEYS[1], raw)
redis.call('HDEL', KEYS[2], ARGV[1])
redis.call('LPUSH', KEYS[3], raw)
return 1
"#;
pub(super) const REQUEUE_AFTER_SCRIPT: &str = r#"
local raw = redis.call('HGET', KEYS[2], ARGV[1])
if not raw then return 0 end
local expected = ARGV[3]
if expected and expected ~= '' then
local ok, envelope = pcall(cjson.decode, raw)
if not ok or type(envelope) ~= 'table' or envelope.attempts ~= tonumber(expected) then return 0 end
end
local now = redis.call('TIME')
local due_ms = (tonumber(now[1]) * 1000) + math.floor(tonumber(now[2]) / 1000) + tonumber(ARGV[2])
redis.call('ZREM', KEYS[1], raw)
redis.call('HDEL', KEYS[2], ARGV[1])
redis.call('ZADD', KEYS[3], due_ms, raw)
return 1
"#;
pub(super) const RECOVER_SCRIPT: &str = r#"
local stalled = redis.call('ZRANGEBYSCORE', KEYS[1], '-inf', ARGV[1])
local now = redis.call('TIME')
local failed_at_ms = (tonumber(now[1]) * 1000) + math.floor(tonumber(now[2]) / 1000)
local recovered = 0
for _, raw in ipairs(stalled) do
redis.call('ZREM', KEYS[1], raw)
local ok, envelope = pcall(cjson.decode, raw)
if ok and type(envelope) == 'table' and type(envelope.id) == 'string' then
redis.call('HDEL', KEYS[2], envelope.id)
local stalls = (tonumber(envelope.stalled_recoveries) or 0) + 1
if stalls >= tonumber(ARGV[3]) then
redis.call('HSET', KEYS[5], envelope.id, cjson.encode({ raw = raw, error = ARGV[4] }))
redis.call('ZADD', KEYS[6], failed_at_ms, envelope.id)
local excess = redis.call('ZCARD', KEYS[6]) - tonumber(ARGV[5])
if excess > 0 then
for _, evicted in ipairs(redis.call('ZRANGE', KEYS[6], 0, excess - 1)) do
redis.call('HDEL', KEYS[5], evicted)
end
redis.call('ZREMRANGEBYRANK', KEYS[6], 0, excess - 1)
end
else
envelope.stalled_recoveries = stalls
redis.call('RPUSH', KEYS[3], cjson.encode(envelope))
end
recovered = recovered + 1
else
redis.call('RPUSH', KEYS[4], cjson.encode({ raw = raw, error = 'invalid stalled job envelope' }))
end
end
redis.call('LTRIM', KEYS[4], -tonumber(ARGV[2]), -1)
return recovered
"#;
pub(super) const PENDING_COUNT_SCRIPT: &str = r#"
return redis.call('LLEN', KEYS[1]) + redis.call('ZCARD', KEYS[2])
"#;