Skip to content

fix: prevent concurrent validations from sharing converters - #254

Merged
adzap merged 1 commit into
adzap:masterfrom
ydah:fix/thread-safe-validation
Oct 3, 2026
Merged

adzap merged 1 commit into
adzap:masterfrom
ydah:fix/thread-safe-validation

Conversation

@ydah

@ydah ydah commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

Concurrent validations can overwrite the shared validator's @converter, causing restrictions to be parsed in another attribute's time zone.

This reproduction forces another thread's UTC validation to run between the local value conversion and restriction check:

require 'active_model'
require 'validates_timeliness'

class Event
  include ActiveModel::Model
  attr_accessor :local_datetime, :utc_datetime

  def self.skip_time_zone_conversion_for_attributes
    [:utc_datetime]
  end

  validates_datetime :local_datetime, :utc_datetime,
    before: '2010-01-01 12:00:00', allow_nil: true
end

Time.zone = 'Australia/Melbourne'
local_record = Event.new(local_datetime: Time.utc(2010, 1, 1, 11))
utc_record = Event.new(utc_datetime: Time.utc(2010, 1, 1, 11))

validator = Event.validators.first
validator.define_singleton_method(:validate_restrictions) do |record, *args|
  Thread.new { utc_record.valid? }.value if record.equal?(local_record)
  super(record, *args)
end

p local_record.valid? # Before: true; after: false

The local value is 22:00 in Melbourne and should violate the 12:00 restriction. Before the fix, the UTC converter incorrectly accepts it.

Keep the converter local to validate_each and pass it to validate_restrictions so each validation uses its own converter.

@adzap adzap left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@adzap
adzap merged commit e5db284 into adzap:master Oct 3, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants