Skip to content

Commit

Permalink
Merge pull request #8 from abicky/fix-flaky-tests
Browse files Browse the repository at this point in the history
Fix flaky test
  • Loading branch information
abicky authored Sep 30, 2024
2 parents f342efa + 0d57afd commit b0e6176
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require "spec_helper"
require "support/cyclic_barrier"

RSpec.describe ActiveRecord::DebugErrors::DisplayConnectionOwners do
let(:log) { StringIO.new }
Expand All @@ -19,12 +20,14 @@
describe "#execute" do
context "when ActiveRecord::Deadlocked occurs" do
def cause_deadlock(role:)
barrier = CyclicBarrier.new(2)

ths = []
ths << Thread.new do
ActiveRecord::Base.connected_to(role: role) do
User.transaction do
User.lock.find_by!(name: 'foo')
sleep 0.1
barrier.await(1)
User.lock.find_by!(name: 'bar')
end
end
Expand All @@ -34,7 +37,7 @@ def cause_deadlock(role:)
ActiveRecord::Base.connected_to(role: role) do
User.transaction do
User.lock.find_by!(name: 'bar')
sleep 0.1
barrier.await(1)
User.lock.find_by!(name: 'foo')
end
end
Expand Down Expand Up @@ -67,18 +70,15 @@ def cause_deadlock(role:)

context "when ActiveRecord::LockWaitTimeout occurs" do
it "displays transactions and processlist" do
ths = []
ths << Thread.new do
User.transaction do
User.lock.find_by!(name: 'foo')
sleep 2
end
end
barrier = CyclicBarrier.new(2)

ths << Thread.new do
User.transaction do
User.lock.find_by!(name: 'foo')
sleep 2
ths = Array.new(2) do
Thread.new do
User.transaction do
barrier.await(1)
User.lock.find_by!(name: 'foo')
sleep 2
end
end
end

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require "spec_helper"
require "support/cyclic_barrier"

RSpec.describe ActiveRecord::DebugErrors::DisplayConnectionOwners do
let(:log) { StringIO.new }
Expand All @@ -20,20 +21,18 @@
it "displays connection owners and other threads" do
Thread.new { sleep 10 } # another thread

mutex = Mutex.new
cv = ConditionVariable.new
barrier = CyclicBarrier.new(ActiveRecord::Base.connection_pool.size)

expect {
ActiveRecord::Base.connection # Ensure to acquire a connection
Array.new(ActiveRecord::Base.connection_pool.size) do
Thread.new do
mutex.synchronize do
ActiveRecord::Base.connection_pool.checkout(0.1)
cv.wait(mutex, 1)
rescue
cv.broadcast
raise
end
ActiveRecord::Base.connection_pool.checkout(0.1)
barrier.await(1)
rescue Timeout::Error
# CyclicBarrier#await is expected to raise Timeout::Error
# because it is not called ActiveRecord::Base.connection_pool.size times
# due to ActiveRecord::ConnectionTimeoutError
end
end.each(&:join)
}.to raise_error(ActiveRecord::ConnectionTimeoutError)
Expand Down
21 changes: 21 additions & 0 deletions spec/support/cyclic_barrier.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# This class is a simple implementation of CyclicBarrier in Java
class CyclicBarrier
def initialize(parties)
@cv = ConditionVariable.new
@mutex = Mutex.new
@parties = parties
@number_waiting = 0
end

def await(timeout = nil)
@mutex.synchronize do
@number_waiting += 1
if @number_waiting == @parties
@cv.broadcast
else
@cv.wait(@mutex, timeout)
raise Timeout::Error if @number_waiting != @parties
end
end
end
end

0 comments on commit b0e6176

Please sign in to comment.