aboutsummaryrefslogblamecommitdiffstats
path: root/activerecord/test/locking_test.rb
blob: 8ddfb2289c26fd9a011e0a7b16a21a69ef4e02b1 (plain) (tree)
1
2
3
4
5
6
7
8
9
10

                         
                               
 
                                                  
                                  



                        

                                   
 


                                   
 
                                                              


                   



                                           
                           







                                                              
     
 


                                    













                                                                        
 


                                                                        



















                                                                            
 



















                                                        











                                                            
                                                         










                                                           
                         


























                                                             
   
require 'abstract_unit'
require 'fixtures/person'
require 'fixtures/legacy_thing'

class OptimisticLockingTest < Test::Unit::TestCase
  fixtures :people, :legacy_things

  def test_lock_existing
    p1 = Person.find(1)
    p2 = Person.find(1)
    assert_equal 0, p1.lock_version
    assert_equal 0, p2.lock_version

    p1.save!
    assert_equal 1, p1.lock_version
    assert_equal 0, p2.lock_version

    assert_raises(ActiveRecord::StaleObjectError) { p2.save! }
  end

  def test_lock_new
    p1 = Person.new(:first_name => 'anika')
    assert_equal 0, p1.lock_version

    p1.save!
    p2 = Person.find(p1.id)
    assert_equal 0, p1.lock_version
    assert_equal 0, p2.lock_version

    p1.save!
    assert_equal 1, p1.lock_version
    assert_equal 0, p2.lock_version

    assert_raises(ActiveRecord::StaleObjectError) { p2.save! }
  end

  def test_lock_column_name_existing
    t1 = LegacyThing.find(1)
    t2 = LegacyThing.find(1)
    assert_equal 0, t1.version
    assert_equal 0, t2.version

    t1.save!
    assert_equal 1, t1.version
    assert_equal 0, t2.version

    assert_raises(ActiveRecord::StaleObjectError) { t2.save! }
  end

  def test_lock_column_is_mass_assignable
    p1 = Person.create(:first_name => 'bianca')
    assert_equal 0, p1.lock_version
    assert_equal p1.lock_version, Person.new(p1.attributes).lock_version

    p1.save!
    assert_equal 1, p1.lock_version
    assert_equal p1.lock_version, Person.new(p1.attributes).lock_version
  end
end


# TODO: test against the generated SQL since testing locking behavior itself
# is so cumbersome. Will deadlock Ruby threads if the underlying db.execute
# blocks, so separate script called by Kernel#system is needed.
# (See exec vs. async_exec in the PostgreSQL adapter.)
class PessimisticLockingTest < Test::Unit::TestCase
  self.use_transactional_fixtures = false
  fixtures :people

  def setup
    @allow_concurrency = ActiveRecord::Base.allow_concurrency
    ActiveRecord::Base.allow_concurrency = true
  end

  def teardown
    ActiveRecord::Base.allow_concurrency = @allow_concurrency
  end

  # Test that the adapter doesn't blow up on add_lock!
  def test_sane_find_with_lock
    assert_nothing_raised do
      Person.transaction do
        Person.find 1, :lock => true
      end
    end
  end

  # Test no-blowup for scoped lock.
  def test_sane_find_with_lock
    assert_nothing_raised do
      Person.transaction do
        Person.with_scope(:find => { :lock => true }) do
          Person.find 1
        end
      end
    end
  end

  # Locking a record reloads it.
  def test_sane_lock_method
    assert_nothing_raised do
      Person.transaction do
        person = Person.find 1
        old, person.first_name = person.first_name, 'fooman'
        person.lock!
        assert_equal old, person.first_name
      end
    end
  end

  if current_adapter?(:PostgreSQLAdapter, :OracleAdapter)
    def test_no_locks_no_wait
      first, second = duel { Person.find 1 }
      assert first.end > second.end
    end

    def test_second_lock_waits
      first, second = duel { Person.find 1, :lock => true }
      assert second.end > first.end
    end

    protected
      def duel(zzz = 1.0)
        t0, t1, t2, t3 = nil, nil, nil, nil

        a = Thread.new do
          t0 = Time.now
          Person.transaction do
            yield
            sleep zzz       # block thread 2 for zzz seconds
          end
          t1 = Time.now
        end

        b = Thread.new do
          sleep zzz / 2.0   # ensure thread 1 tx starts first
          t2 = Time.now
          Person.transaction { yield }
          t3 = Time.now
        end

        a.join
        b.join

        assert t1 > t0 + zzz
        assert t2 > t0
        assert t3 > t2
        [t0.to_f..t1.to_f, t2.to_f..t3.to_f]
      end
  end
end