Skip to content

Race condition when inserting records #182

@fschwahn

Description

@fschwahn

When inserting several records simultaneously the following error is sometimes raised:

NoMethodError: undefined method `rank' for nil:NilClass
/app/vendor/bundle/ruby/3.0.0/gems/ranked-model-0.4.7/lib/ranked-model/ranker.rb line 194 in rearrange_ranks
/app/vendor/bundle/ruby/3.0.0/gems/ranked-model-0.4.7/lib/ranked-model/ranker.rb line 185 in assure_unique_position
/app/vendor/bundle/ruby/3.0.0/gems/ranked-model-0.4.7/lib/ranked-model/ranker.rb line 61 in handle_ranking
/app/vendor/bundle/ruby/3.0.0/gems/ranked-model-0.4.7/lib/ranked-model.rb line 33 in block in handle_ranking
/app/vendor/bundle/ruby/3.0.0/gems/ranked-model-0.4.7/lib/ranked-model.rb line 32 in each
/app/vendor/bundle/ruby/3.0.0/gems/ranked-model-0.4.7/lib/ranked-model.rb line 32 in handle_ranking 

I was able to somewhat reliably reproduce this error with the following test:

begin
  require "bundler/inline"
rescue LoadError => e
  $stderr.puts "Bundler version 1.10 or later is required. Please update your Bundler"
  raise e
end

gemfile(true) do
  source "https://rubygems.org"
  gem "activerecord", "~> 6.1"
  gem "ranked-model"
  gem "pg"
end

require "active_record"
require "minitest/autorun"
require "logger"

ActiveRecord::Base.establish_connection(adapter: "postgresql", database: "ranked_model_issue", url: "postgres://postgres:@localhost:5434")
ActiveRecord::Base.logger = Logger.new(STDOUT)

ActiveRecord::Schema.define do
  create_table :ducks, force: true do |t|
    t.string :name
    t.integer :row_order
    t.timestamps
  end
end

class Duck < ActiveRecord::Base
  include RankedModel

  ranks :row_order
end

class BugTest < Minitest::Test
  def test_error_during_rebalance
    threads = 5.times.map do
      Thread.new do
        ActiveRecord::Base.connection_pool.with_connection do
          Duck.create!
        end
      end
    end

    threads.each(&:join)

    assert_equal Duck.count, 5
    # Secondary issue
    # assert_equal Duck.distinct.pluck(:row_order).size, 5
  end
end
  • This does not work with sqlite, as sqlite raises an error due to the database being locked.
  • The test does not always fail, as is the nature with race conditions. It might take a few tries.
  • This test also exhibits a secondary issue, namely that the same row_order-value is taken several times, even if it does not outright fail.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions