aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--app/errors/table_lock_timeout_error.rb1
-rw-r--r--app/models/referential.rb12
-rw-r--r--spec/models/referential/referential_lock_during_creation_spec.rb59
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