使用rails会话的竞争条件

时间:2013-05-15 00:23:06

标签: ruby-on-rails ruby session conditional-statements race-condition

会话哈希中有一个数组,我正在向它添加内容。问题是,有时会同时处理多个请求(因为ajax),然后请求对数组所做的更改将替换为第二个请求所做的更改。

示例,数组首先如下所示:

  

[63,73,92]

然后第一个请求添加了一些内容:

  

[63,73,92,84]

第二个请求做同样的事情(但显然适用于旧版本):

  

[63,73,92,102]

所以最后数组看起来不应该。有没有办法避免这种情况? 我试图使用缓存存储,活动记录存储和cookie存储。所有这些都存在同样的问题。

感谢。

4 个答案:

答案 0 :(得分:1)

在Rails中确实没有一个很好的解决方案。我的第一个建议是检查你的用例,看看你是否可以避免这种情况开始。如果这样做是安全的,会话数据通常最好保留在客户端上,因为在处理服务器端会话存储时可能会遇到许多挑战。另一方面,如果这是跨多个页面请求(可能是多个会话)长期有用的数据,也许它应该放在数据库中。

当然,有一些数据确实属于会话(一个很好的例子就是当前登录的用户)。如果是这种情况,请查看http://paulbutcher.com/2007/05/01/race-conditions-in-rails-sessions-and-how-to-fix-them/,特别是https://github.com/fcheung/smart_session_store,它会尝试处理您所描述的情况。

答案 1 :(得分:1)

会话竞争条件在Rails中非常常见。 Redis会话存储也无济于事! 原因是Rails仅在接收到请求时读取并创建会话对象,并在请求完成并即将返回给用户时将其写回到会话存储。

Java为此提供了一种称为会话复制的解决方案。我们可以建立自己的基于并发Redis的会话存储。以下是差不多的内容。除了更新方法缺少锁。但这几乎从来不会碰到种族问题。

要获取会话哈希,只需使用并发会话即可返回哈希。 要在其中设置一些值,请使用update_concurrent_session方法。它将新的哈希值深度合并为旧值:

class ApplicationController < ActionController::Base
  def concurrent_session
    @concurrent_session ||= get_concurrent_session
  end

  def update_concurrent_session h
    return unless session.id.present?
    @concurrent_session = get_concurrent_session.deep_merge(h)
    redis.set session_key, @concurrent_session.to_json
    redis.expire session_key, session_timeout
  end

  private

  def redis
    Rails.cache.redis
  end

  def session_timeout
    2.weeks.to_i
  end

  def session_key
    'SESSION_' + session.id.to_s if session.id.present?
  end

  def get_concurrent_session
    return {} unless session.id.present?
    redis.expire session_key, session_timeout
    redis.get(session_key).yield_self do |v|
      if v
        JSON.parse v, symbolize_names: true
      else
        {}
      end
    end
  end
end

用法示例:

my_roles = concurrent_session[:my_roles]
update_concurrent_session({my_roles: ['admin', 'vip']})

答案 2 :(得分:0)

  
      
  1. 加载当前会话,或在必要时创建新会话
  2.   
  3. 保存未修改会话的副本以供将来参考
  4.   
  5. 运行操作代码
  6.   
  7. 将修改后的会话与之前保存的副本进行比较,以确定更改内容
  8.   
  9. 如果会话已更改:   
        
    1. 锁定会话
    2.   
    3. 重新加载会话
    4.   
    5. 应用对此会话所做的更改并保存
    6.   
    7. 解锁会话
    8.   
  10.   

来自Race conditions in Rails sessions and how to fix them

答案 3 :(得分:0)

这是一个简单的竞争条件,只需使用任何锁定机制锁定请求 redis locker

RedisLocker.new('my_ajax').run! { session[:whatever] << number }