Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix locking #1238

Merged
merged 3 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion app/services/pfmp_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ def update!(params)
params = params.to_h.with_indifferent_access

Pfmp.transaction do
pfmp.schooling&.lock! if # prevent race condition using pessimistic locking (blocks r+w)

pfmp.update!(params)
recalculate_amounts! if params[:day_count].present?
transition!
Expand Down Expand Up @@ -83,7 +85,6 @@ def calculate_amount(target_pfmp)
def recalculate_amounts!
raise PfmpNotModifiableError unless pfmp.can_be_modified?

pfmp.all_pfmps_for_mef.lock!
pfmp.update!(amount: calculate_amount(pfmp))
rebalance_other_pfmps!
end
Expand Down
35 changes: 35 additions & 0 deletions spec/services/pfmp_manager_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,41 @@
let(:student) { create(:student, :with_all_asp_info) }
let(:schooling) { create(:schooling, student: student, classe: classe) }

describe "concurrent updates" do
let(:pfmp1) { create(:pfmp, schooling: schooling, day_count: 2) } # rubocop:disable RSpec/IndexedLet
let(:pfmp2) { create(:pfmp, schooling: schooling, day_count: 3) } # rubocop:disable RSpec/IndexedLet
let(:manager1) { described_class.new(pfmp1) } # rubocop:disable RSpec/IndexedLet
let(:manager2) { described_class.new(pfmp2) } # rubocop:disable RSpec/IndexedLet

it "prevents concurrent updates on the same schooling" do # rubocop:disable RSpec/ExampleLength,RSpec/MultipleExpectations
execution_order = []

thread1 = Thread.new do
Pfmp.transaction do
execution_order << 1
manager1.update!(day_count: 5)
sleep(0.2)
execution_order << 2
end
end

sleep(0.1)
execution_order << 3

thread2 = Thread.new do
manager2.update!(day_count: 7)
execution_order << 4
end

thread1.join
thread2.join

expect(execution_order).to eq [1, 3, 2, 4]
expect(pfmp1.reload.day_count).to eq 5
expect(pfmp2.reload.day_count).to eq 7
end
end

describe "#create_new_payment_request!" do
context "when previous payment requests are inactive" do
let(:pfmp) { create(:asp_payment_request, :rejected).pfmp }
Expand Down