Rails 验证线程安全吗
Is this Rails validation thread-safe
这是 运行 同时在多个 Sidekiq 实例和 worker 中,似乎已经产生了几个问题,比如实例在不应该被分配 "It was alerted recently" 错误的时候和相反。
这种情况很少见,但正在发生,这是问题所在还是其他原因?
class BrokenModel < ActiveRecord::Base
validates_with BrokenValidator
end
class BrokenValidator < ActiveModel::Validator
def validate record
@record = record
check_alerted
end
private
def check_alerted
if AtomicGlobalAlerted.new(@record).valid?
@record.errors[:base] << "It was alerted recently"
end
p "check_alerted: #{@record.errors[:base]}"
end
end
class AtomicGlobalAlerted
include Redis::Objects
attr_accessor :id
def initialize id
@id = id
@fredis = nil
Sidekiq.redis do |redis|
@fredis = FreshRedis.new(redis, freshness: 7.days, granularity: 4.hours)
end
end
def valid?
@fredis.smembers.includes?(@id)
end
end
我认为 rails 中存在一些线程安全问题,但我们可以通过采取必要的预防措施来克服这些问题。
局部变量(例如您的局部变量)对于方法块的每个特定调用都是局部的。如果两个线程同时调用这个块,那么每个调用都将获得自己的局部上下文变量,除非涉及共享资源,否则它们不会重叠:实例变量如 (@global_var)
、静态变量 (@@static_var)
, globals ($global_var)
会导致并发问题。
您正在使用实例变量,每次使用 validate_record
方法时只需实例化它,希望您的问题会像这样消失:
def validate record
@record.errors[:base] = []
@record = record
check_alerted
end
更多详情可以访问这个详细的link
或尝试在此处研究 rails 配置:link
我们在工作中遇到了类似的事情,经过大量挖掘终于弄清楚发生了什么。
class 方法 validates_with
使用验证器 (BrokenValidator
) 的一个实例来验证您尝试验证的 class 的所有实例 (BrokenModel
).通常这很好,但是您正在分配一个变量 (@record
) 并在另一个方法 (check_alerted
) 中访问该变量,因此其他线程正在分配 @record
而其他线程仍在尝试 check_alerted
.
有两种方法可以解决此问题:
1) 将 record
传递给 check_alerted
:
class BrokenValidator < ActiveModel::Validator
def validate(record)
check_alerted(record)
end
private
def check_alerted(record)
if AtomicGlobalAlerted.new(record).valid?
record.errors[:base] << "It was alerted recently"
end
p "check_alerted: #{record.errors[:base]}"
end
end
2) 使用 validates_with
的实例版本,它为您要验证的每个模型实例创建一个新的验证器实例:
class BrokenModel < ActiveRecord::Base
validate :instance_validators
def instance_validators
validates_with BrokenValidator
end
end
任一解决方案都应该有效并解决并发问题。如果您遇到任何其他问题,请告诉我。
这是 运行 同时在多个 Sidekiq 实例和 worker 中,似乎已经产生了几个问题,比如实例在不应该被分配 "It was alerted recently" 错误的时候和相反。
这种情况很少见,但正在发生,这是问题所在还是其他原因?
class BrokenModel < ActiveRecord::Base
validates_with BrokenValidator
end
class BrokenValidator < ActiveModel::Validator
def validate record
@record = record
check_alerted
end
private
def check_alerted
if AtomicGlobalAlerted.new(@record).valid?
@record.errors[:base] << "It was alerted recently"
end
p "check_alerted: #{@record.errors[:base]}"
end
end
class AtomicGlobalAlerted
include Redis::Objects
attr_accessor :id
def initialize id
@id = id
@fredis = nil
Sidekiq.redis do |redis|
@fredis = FreshRedis.new(redis, freshness: 7.days, granularity: 4.hours)
end
end
def valid?
@fredis.smembers.includes?(@id)
end
end
我认为 rails 中存在一些线程安全问题,但我们可以通过采取必要的预防措施来克服这些问题。
局部变量(例如您的局部变量)对于方法块的每个特定调用都是局部的。如果两个线程同时调用这个块,那么每个调用都将获得自己的局部上下文变量,除非涉及共享资源,否则它们不会重叠:实例变量如 (@global_var)
、静态变量 (@@static_var)
, globals ($global_var)
会导致并发问题。
您正在使用实例变量,每次使用 validate_record
方法时只需实例化它,希望您的问题会像这样消失:
def validate record
@record.errors[:base] = []
@record = record
check_alerted
end
更多详情可以访问这个详细的link
或尝试在此处研究 rails 配置:link
我们在工作中遇到了类似的事情,经过大量挖掘终于弄清楚发生了什么。
class 方法 validates_with
使用验证器 (BrokenValidator
) 的一个实例来验证您尝试验证的 class 的所有实例 (BrokenModel
).通常这很好,但是您正在分配一个变量 (@record
) 并在另一个方法 (check_alerted
) 中访问该变量,因此其他线程正在分配 @record
而其他线程仍在尝试 check_alerted
.
有两种方法可以解决此问题:
1) 将 record
传递给 check_alerted
:
class BrokenValidator < ActiveModel::Validator
def validate(record)
check_alerted(record)
end
private
def check_alerted(record)
if AtomicGlobalAlerted.new(record).valid?
record.errors[:base] << "It was alerted recently"
end
p "check_alerted: #{record.errors[:base]}"
end
end
2) 使用 validates_with
的实例版本,它为您要验证的每个模型实例创建一个新的验证器实例:
class BrokenModel < ActiveRecord::Base
validate :instance_validators
def instance_validators
validates_with BrokenValidator
end
end
任一解决方案都应该有效并解决并发问题。如果您遇到任何其他问题,请告诉我。