gpt4 book ai didi

ruby-on-rails - Ruby with_advisory_lock 多线程测试间歇性失败

转载 作者:行者123 更新时间:2023-12-03 12:54:06 25 4
gpt4 key购买 nike

我正在使用 with_advisory_lock gem 来尝试确保一条记录只创建一次。这是 github url到 gem 。

我有以下代码,它位于我编写的用于处理创建用户订阅的操作类中:

def create_subscription_for user
subscription = UserSubscription.with_advisory_lock("lock_%d" % user.id) do
UserSubscription.where({ user_id: user.id }).first_or_create
end

# do more stuff on that subscription
end

和伴随的测试:

threads = []
user = FactoryBot.create(:user)

rand(5..10).times do
threads << Thread.new do
subject.create_subscription_for(user)
end
end

threads.each(&:join)

expect(UserSubscription.count).to eq(1)

我期望发生的事情:

  • 第一个到达 block 的线程获取锁并创建记录。
  • 在 block 被另一个线程占用时进入 block 的任何其他线程 waits indefinitely until the lock is released (根据文档)
  • 一旦创建记录的第一个线程释放锁,另一个线程就会获取锁并立即找到记录,因为它已经由第一个线程创建。

实际发生了什么:

  • 第一个到达 block 的线程获取锁并创建记录。
  • 在 block 被另一个线程占用时进入 block 的任何其他线程无论如何都会执行 block 中的代码,因此,在运行测试时,它有时会失败并显示 ActiveRecord::RecordNotUnique 错误(我在表上有一个唯一索引,允许单个 user_subscription 具有相同的 user_id)

更奇怪的是,如果我在我的方法中的 find_or_create 方法之前添加一个几百毫秒的 sleep,测试永远不会失败:

def create_subscription_for user
subscription = UserSubscription.with_advisory_lock("lock_%d" % user.id) do
sleep 0.2
UserSubscription.where({ user_id: user.id }).first_or_create
end

# do more stuff on that subscription
end

我的问题是:“为什么添加 sleep 0.2 让测试总是通过?”和“我应该在哪里调试它?”

谢谢!

更新:稍微调整测试会导致它们总是失败:

threads = []
user = FactoryBot.create(:user)

rand(5..10).times do
threads << Thread.new do
sleep
subject.create_subscription_for(user)
end
end

until threads.all? { |t| t.status == 'sleep' }
sleep 0.1
end

threads.each(&:wakeup)
threads.each(&:join)

expect(UserSubscription.count).to eq(1)

我还在事务中包装了 first_or_create,这使得测试通过并且一切都按预期工作:

def create_subscription_for user
subscription = UserSubscription.with_advisory_lock("lock_%d" % user.id) do
UserSubscription.transaction do
UserSubscription.where({ user_id: user.id }).first_or_create
end
end

# do more stuff on that subscription
end

那么为什么将 first_or_create 包装在事务中才能使事情正常进行?

最佳答案

您要关闭此测试用例的事务测试吗?我正在研究类似的东西,事实证明这对实际模拟并发很重要。

参见uses_transaction https://api.rubyonrails.org/classes/ActiveRecord/TestFixtures/ClassMethods.html

如果未关闭事务,Rails 会将整个测试包装在一个事务中,这将导致所有线程共享一个数据库连接。此外,在 Postgres 中, session 级咨询锁总是可以在同一 session 中重新获取。来自文档:

If a session already holds a given advisory lock, additional requests by it will always succeed, even if other sessions are awaiting the lock; this statement is true regardless of whether the existing lock hold and new request are at session level or transaction level.

基于此,我怀疑您的锁始终能够获取,因此始终执行 .first_or_create 调用,这会导致间歇性的 RecordNotUnique 异常。

关于ruby-on-rails - Ruby with_advisory_lock 多线程测试间歇性失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56146171/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com