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 |
