Ruby on Rails中的竞争条件

时间:2014-03-13 19:44:57

标签: ruby-on-rails ruby postgresql heroku race-condition

我遇到了一个关于Heroku的奇怪错误,我认为这可能是一种竞争条件,我正在寻找任何解决它的建议。

我的应用程序有一个模型,它在创建后调用外部API(Twilio,如果你很好奇)。在此调用中,它会在第三方完成其工作后传递要回叫的URL。像这样:

class TextMessage < ActiveRecord::Base
   after_create :send_sms

   def send_sms
     call.external.third.party.api(
       :callback => sent_text_message_path(self)
     )
   end
end

然后我有一个控制器来处理回调:

class TextMessagesController < ActiveController::Base
  def sent
    @textmessage = TextMessage.find(params[:id])
    @textmessage.sent = true
    @textmessage.save
  end
end

问题是第三方报告他们在回调时遇到404错误,因为尚未创建模型。我们自己的日志证实了这一点:

2014-03-13T18:12:10.291730+00:00 app[web.1]: ActiveRecord::RecordNotFound (Couldn't find TextMessage with id=32)

我们检查过并且ID正确无误。甚至更疯狂的是我们在模型创建时投入puts来记录,这就是我们得到的:

2014-03-13T18:15:22.192733+00:00 app[web.1]: TextMessage created with ID 35.
2014-03-13T18:15:22.192791+00:00 app[web.1]: ActiveRecord::RecordNotFound (Couldn't find TextMessage with id=35)

注意时间戳。这些事情似乎相隔58毫秒发生,所以我认为这是竞争条件。我们正在使用Heroku(至少用于分期),所以我认为可能是他们的虚拟Postgres数据库是问题所在。

之前有没有人遇到过这类问题?如果是这样,你是如何解决的?有什么建议吗?

2 个答案:

答案 0 :(得分:2)

在数据库事务中处理

after_create以保存文本消息。因此,命中另一个控制器的回调无法读取文本消息。在数据库事务中进行外部调用并不是一个好主意,因为事务会在外部请求缓慢的整个过程中阻塞部分数据库。

简单的解决方案是将after_save替换为after_commit(请参阅:http://apidock.com/rails/ActiveRecord/Transactions/ClassMethods/after_commit

由于回调往往难以理解(并且在测试时可能会导致问题),我宁愿通过调用另一个方法来使调用显式化。也许是这样的:

# use instead of .save 
def save_and_sent_sms
  save and sent_sms
end

也许你想在后台发送短信,所以它不会减慢用户的网络请求。搜索gems delayed_job或resque以获取更多信息。

答案 1 :(得分:0)

你有主/从数据库,你总是写到主数据库,但从奴隶读取?这听起来像db复制滞后。

我们通过强制在特定控制器操作中从master数据库进行读取来解决此类问题。

另一种方法是在复制完成后调用send_sms