可能标题不是不言自明的,情况是:
# user.points: 0
user.update!(points: 1000)
UserMailer.notify(user).deliver_later. # user.points = 0 => Error !!!!
``` `user` 示例进行更新,然后使用 `user` 作为参数,并且在电子邮件中更改不存在:user.points=0而不是1000
但是,有一个 `sleep 1` 就在那之后 `user_update` 发送电子邮件时会更新更改,因此电子邮件作业似乎比将数据更新到数据库要快。
user.points: 0
user.update!(points: 1000)
sleep 1
UserMailer.notify(user).deliver_later. # user.points = 1000 => OK
避免这两种可能的解决方案的最佳方法是什么?
一种解决办法是打电话 `UserMailer.notify` 不是用 `user` 示例,但具有用户值
另一个解决方案,可能是在 `user` 回拨 `after_commit` 那么,有没有另一种方法来解决这个问题,即保持用户示例作为参数,并避免 `after_commit` 回拨?
谢谢
2条答案
按热度按时间j0pj023g1#
记住,sidekiq使用redis作为媒介,在单独的进程中运行rails应用程序的副本。当你打电话的时候
deliver_later
,它实际上并没有“通过”user
去做 Postman 的工作。它生成一个线程,该线程在redis中将作业排队,并传递一个user
属性,包括id。当邮件程序作业在sidekiq进程中运行时,它会从数据库中加载用户的新副本。如果交易包含您的
update!
在主rails应用程序尚未完成提交的情况下,sidekiq从数据库中获取旧记录。所以,这是一个比赛条件。(
update!
如果没有隐式事务,则已将其自身 Package 起来,因此在您自己的事务中 Package 它是多余的,并且无助于竞争条件,因为嵌套的activerecord事务仅在最外层事务提交时提交。)在紧要关头,你可以用一些类似黑客的东西来拖延工作
.deliver_later(wait_until: 10.seconds.from_now)
,但最好的办法是将邮件通知放入after_commit
回叫你的模型。模特的
after_commit
回调保证在提交最终事务后运行,因此,就像从轨道上使用核武器一样,这是唯一可以确定的方法。smdnsysy2#
你没有提到,但我猜你在用activerecord?如果是这样,您可能需要确保在安排sidekiq作业之前刷新数据库事务。
https://api.rubyonrails.org/v6.1.4/classes/activerecord/transactions/classmethods.html