Files
kong--kong/spec/02-integration/03-db/04-db_cluster_mutex_spec.lua
2026-07-13 12:32:21 +08:00

131 lines
3.4 KiB
Lua

local helpers = require "spec.helpers"
for _, strategy in helpers.each_strategy() do
describe("kong.db [#" .. strategy .. "]", function()
local db
lazy_setup(function()
local _
_, db, _ = helpers.get_db_utils(strategy, {})
assert(db.connector:setup_locks(60))
end)
before_each(function()
ngx.shared.kong_locks:flush_all()
ngx.shared.kong_locks:flush_expired()
end)
describe("db:cluster_mutex()", function()
it("returns 'true' when mutex ran and 'false' otherwise", function()
local ok1, err1, ok2, err2
local t1 = ngx.thread.spawn(function()
ok1, err1 = db:cluster_mutex("my_key", nil, function()
ngx.sleep(0.1)
end)
end)
local t2 = ngx.thread.spawn(function()
ok2, err2 = db:cluster_mutex("my_key", nil, function() end)
end)
ngx.thread.wait(t1)
ngx.thread.wait(t2)
assert.is_nil(err1)
assert.equal(true, ok1)
assert.is_nil(err2)
assert.equal(false, ok2)
end)
it("mutex ensures only one callback gets called", function()
local cb1 = spy.new(function() end)
local cb2 = spy.new(function() ngx.sleep(0.3) end)
local err1, err2
local t1 = ngx.thread.spawn(function()
ngx.sleep(0.2)
_, err1 = db:cluster_mutex("my_key_2", { owner = "1" }, cb1)
end)
local t2 = ngx.thread.spawn(function()
_, err2 = db:cluster_mutex("my_key_2", { owner = "2" }, cb2)
end)
ngx.thread.wait(t1)
ngx.thread.wait(t2)
assert.is_nil(err1)
assert.is_nil(err2)
assert.spy(cb2).was_called()
assert.spy(cb1).was_not_called()
end)
it("mutex can be subsequently acquired once released", function()
local cb1 = spy.new(function() end)
local cb2 = spy.new(function() end)
local err1, err2
local t1 = ngx.thread.spawn(function()
_, err1 = db:cluster_mutex("my_key_3", nil, cb1)
end)
-- to make sure `db:cluster_mutex` is called subsequently.
-- even if we didn't call ngx.sleep() explicitly, it will
-- yield in `db:cluster_mutex`
ngx.thread.wait(t1)
local t2 = ngx.thread.spawn(function()
_, err2 = db:cluster_mutex("my_key_3", nil, cb2)
end)
ngx.thread.wait(t2)
assert.is_nil(err1)
assert.is_nil(err2)
assert.spy(cb1).was_called()
assert.spy(cb2).was_called()
end)
it("mutex cannot be held for longer than opts.ttl across nodes (DB lock)", function()
local ok1, err1, ok2, err2
local cb1 = spy.new(function()
-- make DB lock expire
ngx.sleep(1)
end)
local cb2 = spy.new(function() end)
local t1 = ngx.thread.spawn(function()
ok1, err1 = db:cluster_mutex("my_key_5", { ttl = 0.5 }, cb1)
end)
-- remove worker lock
ngx.shared.kong_locks:delete("my_key_5")
local t2 = ngx.thread.spawn(function()
ok2, err2 = db:cluster_mutex("my_key_5", { ttl = 0.5 }, cb2)
end)
ngx.thread.wait(t1)
ngx.thread.wait(t2)
assert.is_nil(err1)
assert.equal(true, ok1)
assert.is_nil(ok2)
assert.matches("%[%w+ error%] timeout", err2)
assert.spy(cb1).was_called()
assert.spy(cb2).was_not_called()
end)
end)
end)
end