Skip to content

Instantly share code, notes, and snippets.

@tommeier
Last active August 29, 2015 13:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tommeier/9360840 to your computer and use it in GitHub Desktop.
Save tommeier/9360840 to your computer and use it in GitHub Desktop.
Deadlock specs
require 'spec_helper'
describe 'deadlocks' do
describe "on login" do
it 'should allow multiple requests to login at same time' do
# Check original is ok
::Customer.find_by(id: customer_id).should be_blank
forks = []
1.upto(4).each do |index|
forks << fork_with_new_connection do
# Trigger any code that deadlocks like login
end
end
forks.each do |pid|
_, fork_status = Process.waitpid2(pid)
# Fork should not have raised an error (as a result of deadlock)
fork_status.exitstatus.should eq(0)
end
# Check customer is created even with deadlocks
customer = ::Customer.find_by(id: customer_id)
customer.should be_present
end
end
end
# Sub forks within specs must wrap the connection to ensure cleanup occurs correctly
def fork_with_new_connection
ActiveRecord::Base.remove_connection
fork_pid = fork do
# Ensure coverage is unique to forked process
SimpleCov.command_name("forked-#{Process.pid}-#{SimpleCov.command_name}")
#Skip full format, but merge coverage results before final
# .format! is called in outer process
SimpleCov.at_exit { SimpleCov.result }
begin
ActiveRecord::Base.establish_connection
DatabaseCleaner.start unless it_should_truncate_data?(example.metadata)
yield
ensure
DatabaseCleaner.clean unless it_should_truncate_data?(example.metadata)
ActiveRecord::Base.remove_connection
end
end
ActiveRecord::Base.establish_connection
fork_pid
end
SimpleCov.at_exit do
# Ensure multiple processes or forks lock coverage file during result merging
# (and ensure one process result isn't clobbered)
SimpleCov.with_lock { SimpleCov.result.format! }
end
SimpleCov.class_eval do
# Run any command with a lockfile
# Useful for when generating coverage across forks/threads
def self.with_lock
parallel_lock = File.join(self.coverage_path, '.lockfile')
File.open(parallel_lock, "w+") do |f|
f.flock(File::LOCK_EX)
yield
end
end
# Allow a specific command name for a block of code executing
# and run simple merge of results without full coverage generated
def self.with_command_name(new_command_name)
original_command_name = command_name
command_name(new_command_name)
yield
with_lock do
self.result #Merge results without full report
end
command_name(original_command_name)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment