diff options
| -rw-r--r-- | app/errors/table_lock_timeout_error.rb | 1 | ||||
| -rw-r--r-- | app/models/referential.rb | 12 | ||||
| -rw-r--r-- | spec/models/referential/referential_lock_during_creation_spec.rb | 59 | 
3 files changed, 70 insertions, 2 deletions
| diff --git a/app/errors/table_lock_timeout_error.rb b/app/errors/table_lock_timeout_error.rb new file mode 100644 index 000000000..102f3a4a0 --- /dev/null +++ b/app/errors/table_lock_timeout_error.rb @@ -0,0 +1 @@ +class TableLockTimeoutError < ActiveRecord::StatementInvalid; end diff --git a/app/models/referential.rb b/app/models/referential.rb index b14ab3827..b44bb15c6 100644 --- a/app/models/referential.rb +++ b/app/models/referential.rb @@ -61,6 +61,18 @@ class Referential < ActiveRecord::Base    scope :order_by_validity_period, ->(dir) { joins(:metadatas).order("unnest(periodes) #{dir}") }    scope :order_by_lines, ->(dir) { joins(:metadatas).group("referentials.id").order("sum(array_length(referential_metadata.line_ids,1)) #{dir}") } +  def save_with_table_lock_timeout +    save_without_table_lock_timeout +  rescue ActiveRecord::StatementInvalid => e +    if e.message.include?('PG::LockNotAvailable') +      raise TableLockTimeoutError.new(e) +    else +      raise +    end +  end + +  alias_method_chain :save, :table_lock_timeout +    def lines      if metadatas.blank?        workbench ? workbench.lines : associated_lines diff --git a/spec/models/referential/referential_lock_during_creation_spec.rb b/spec/models/referential/referential_lock_during_creation_spec.rb index 0002af549..d17327d39 100644 --- a/spec/models/referential/referential_lock_during_creation_spec.rb +++ b/spec/models/referential/referential_lock_during_creation_spec.rb @@ -1,8 +1,7 @@  RSpec.describe Referential, type: :model do +  let (:workbench) { create(:workbench) }    context "when two identical Referentials are created, only one is saved" do -    let( :workbench ){ create :workbench } -      it "works synchronously" do        referential_1 = build(          :referential, @@ -76,5 +75,61 @@ RSpec.describe Referential, type: :model do          end        end      end + +  context "when two Referentials are created at the same time" do +    it "raises an error when the DB lock timeout is reached", truncation: true do +      begin +        referential_1 = build( +          :referential, +          workbench: workbench, +          organisation: workbench.organisation +        ) +        referential_2 = referential_1.dup +        referential_2.slug = "#{referential_1.slug}_different" +        referential_3 = nil + +        metadata_1 = build(:referential_metadata, referential: nil) +        metadata_2 = metadata_1.dup + +        referential_1.metadatas << metadata_1 +        referential_2.metadatas << metadata_2 + +        thread_1 = Thread.new do +          ActiveRecord::Base.transaction do +            ActiveRecord::Base.connection.execute("SET LOCAL lock_timeout = '1s'") + +            # seize LOCK +            referential_1.save +            sleep 10 +            # release LOCK +          end +        end + +        thread_2 = Thread.new do +          sleep 5 +          ActiveRecord::Base.transaction do +            ActiveRecord::Base.connection.execute("SET LOCAL lock_timeout = '1s'") +            # waits for LOCK, (because of sleep 5) +            referential_2.save +            # when lock was eventually obtained validation failed +            referential_3 = create(:referential) +          end +        end + +        thread_1.join +        expect do +          thread_2.join +        end.to raise_error(TableLockTimeoutError) + +        expect(referential_1).to be_persisted +      ensure +        Apartment::Tenant.drop(referential_1.slug) if referential_1.persisted? +        Apartment::Tenant.drop(referential_2.slug) if referential_2.persisted? + +        if referential_3.try(:persisted?) +          Apartment::Tenant.drop(referential_3.slug) +        end +      end +    end    end  end | 
