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

Use database mutex locking #325

Closed
wants to merge 6 commits into from
Closed
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
34 changes: 0 additions & 34 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ before_script:
- mysql -e 'create database acts_as_list;'
- psql -c 'create database acts_as_list;' -U postgres
rvm:
- 1.9.3
- 2.0.0
- 2.1.10
- 2.2.6
- 2.3.6
- 2.4.3
- 2.5.0
Expand All @@ -22,37 +18,7 @@ env:
- DB=mysql
- DB=postgresql
gemfile:
- gemfiles/rails_3_2.gemfile
- gemfiles/rails_4_1.gemfile
- gemfiles/rails_4_2.gemfile
- gemfiles/rails_5_0.gemfile
- gemfiles/rails_5_1.gemfile
- gemfiles/rails_5_2.gemfile
matrix:
exclude:
- rvm: 1.9.3
gemfile: gemfiles/rails_5_0.gemfile
- rvm: 1.9.3
gemfile: gemfiles/rails_5_1.gemfile
- rvm: 1.9.3
gemfile: gemfiles/rails_5_2.gemfile
- rvm: 2.0.0
gemfile: gemfiles/rails_5_0.gemfile
- rvm: 2.0.0
gemfile: gemfiles/rails_5_1.gemfile
- rvm: 2.0.0
gemfile: gemfiles/rails_5_2.gemfile
- rvm: 2.1.10
gemfile: gemfiles/rails_5_0.gemfile
- rvm: 2.1.10
gemfile: gemfiles/rails_5_1.gemfile
- rvm: 2.1.10
gemfile: gemfiles/rails_5_2.gemfile
- rvm: 2.4.3
gemfile: gemfiles/rails_3_2.gemfile
- rvm: 2.4.3
gemfile: gemfiles/rails_4_1.gemfile
- rvm: 2.5.0
gemfile: gemfiles/rails_3_2.gemfile
- rvm: 2.5.0
gemfile: gemfiles/rails_4_1.gemfile
20 changes: 0 additions & 20 deletions Appraisals
Original file line number Diff line number Diff line change
@@ -1,23 +1,3 @@
appraise "rails-3-2" do
group :mysql do
gem "mysql2", "~> 0.3.21", platforms: [:ruby]
end
gem "activerecord", "~> 3.2.22.2"
group :test do
gem "after_commit_exception_notification"
end
end

appraise "rails-4-1" do
group :mysql do
gem "mysql2", "~> 0.3.21", platforms: [:ruby]
end
gem "activerecord", "~> 4.1.16"
group :test do
gem "after_commit_exception_notification"
end
end

appraise "rails-4-2" do
group :mysql do
gem "mysql2", "~> 0.4.10", platforms: [:ruby]
Expand Down
2 changes: 0 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
source "http://rubygems.org"

gem "rack", "~> 1", platforms: [:ruby_19, :ruby_20, :ruby_21]
Silex marked this conversation as resolved.
Show resolved Hide resolved

gemspec

gem "rake"
Expand Down
5 changes: 3 additions & 2 deletions acts_as_list.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Gem::Specification.new do |s|
s.description = 'This "acts_as" extension provides the capabilities for sorting and reordering a number of objects in a list. The class that has this specified needs to have a "position" column defined as an integer on the mapped database table.'
s.license = "MIT"
s.rubyforge_project = "acts_as_list"
s.required_ruby_version = ">= 1.9.2"
s.required_ruby_version = ">= 2.3.0"

# Load Paths...
s.files = `git ls-files`.split("\n")
Expand All @@ -24,6 +24,7 @@ Gem::Specification.new do |s|


# Dependencies (installed via "bundle install")
s.add_dependency "activerecord", ">= 3.0"
s.add_dependency "activerecord", ">= 4.2"
s.add_dependency "with_advisory_lock", "~> 4.0"
s.add_development_dependency "bundler", ">= 1.0.0"
end
34 changes: 0 additions & 34 deletions gemfiles/rails_3_2.gemfile

This file was deleted.

34 changes: 0 additions & 34 deletions gemfiles/rails_4_1.gemfile

This file was deleted.

1 change: 0 additions & 1 deletion gemfiles/rails_4_2.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

source "http://rubygems.org"

gem "rack", "~> 1", platforms: [:ruby_19, :ruby_20, :ruby_21]
gem "rake"
gem "appraisal"
gem "activerecord", "~> 4.2.10"
Expand Down
1 change: 0 additions & 1 deletion gemfiles/rails_5_0.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

source "http://rubygems.org"

gem "rack", "~> 1", platforms: [:ruby_19, :ruby_20, :ruby_21]
gem "rake"
gem "appraisal"
gem "activerecord", "~> 5.0.6"
Expand Down
1 change: 0 additions & 1 deletion gemfiles/rails_5_1.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

source "http://rubygems.org"

gem "rack", "~> 1", platforms: [:ruby_19, :ruby_20, :ruby_21]
gem "rake"
gem "appraisal"
gem "activerecord", "~> 5.1.4"
Expand Down
3 changes: 1 addition & 2 deletions gemfiles/rails_5_2.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@

source "http://rubygems.org"

gem "rack", "~> 1", platforms: [:ruby_19, :ruby_20, :ruby_21]
gem "rake"
gem "appraisal"
gem "activerecord", "~> 5.2.0.rc1"
gem "activerecord", "~> 5.2.1"

group :development do
gem "github_changelog_generator", "1.9.0"
Expand Down
1 change: 1 addition & 0 deletions lib/acts_as_list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@
require "acts_as_list/active_record/acts/no_update"
require "acts_as_list/active_record/acts/sequential_updates_method_definer"
require "acts_as_list/active_record/acts/active_record"
require "acts_as_list/active_record/acts/advisory_lock"
43 changes: 43 additions & 0 deletions lib/acts_as_list/active_record/acts/advisory_lock.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# frozen_string_literal: true

require 'with_advisory_lock'

module ActiveRecord
module Acts #:nodoc:
module List #:nodoc:
module AdvisoryLock #:nodoc:
def self.included(base)
base.prepend(InstanceMethods)
end

def self.acts_as_list_methods
ActiveRecord::Acts::List::InstanceMethods.public_instance_methods
end

def self.acts_as_list_lockable_methods
acts_as_list_methods.grep(/^(insert_|move_|remove_|set_|(dec|inc)rement_)/)
end

module InstanceMethods
AdvisoryLock.acts_as_list_lockable_methods.each do |m|
define_method(m) do |*args|
with_table_lock do
super(*args)
end
end
end

def advisory_lock_name
format('lock-%s', acts_as_list_class.to_s.downcase)
end

def with_table_lock
acts_as_list_class.with_advisory_lock(advisory_lock_name) do
yield
end
end
end
end
end
end
end
1 change: 1 addition & 0 deletions lib/acts_as_list/active_record/acts/list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def acts_as_list(options = {})

include ActiveRecord::Acts::List::InstanceMethods
include ActiveRecord::Acts::List::NoUpdate
include ActiveRecord::Acts::List::AdvisoryLock if configuration[:advisory_lock]
end

# This +acts_as+ extension provides the capabilities for sorting and reordering a number of objects in a list.
Expand Down
2 changes: 1 addition & 1 deletion test/helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
end
require "active_record"
require "minitest/autorun"
require "mocha/mini_test"
require "mocha/minitest"
require "#{File.dirname(__FILE__)}/../init"

if defined?(ActiveRecord::VERSION) &&
Expand Down
61 changes: 61 additions & 0 deletions test/test_list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,26 @@ class ListMixin < Mixin
acts_as_list column: "pos", scope: :parent
end

class SlowListMixin < Mixin
acts_as_list column: "pos", scope: :parent

before_create :slow_operation

def slow_operation
sleep 0.1
end
end

class SlowListMixinWithAdvisoryLock < Mixin
acts_as_list column: "pos", scope: :parent, advisory_lock: true

before_create :slow_operation

def slow_operation
sleep 0.1
end
end

class ListMixinSub1 < ListMixin
end

Expand Down Expand Up @@ -256,6 +276,47 @@ def test_insert_race_condition
end
end

class ListAdvisoryLockTest < ActsAsListTestCase
def setup
setup_db
end

def test_errors_without_lock
range = 1..10
threads = range.map do
Thread.new do
begin
SlowListMixin.create!(parent_id: 5)
rescue
# Retry ActiveRecord::ConnectionTimeoutError, etc
retry
end
end
end
threads.each(&:join)

assert(range.to_a != SlowListMixin.order(:pos).pluck(:pos))
end


def test_no_errors_with_lock
range = 1..10
threads = range.map do
Thread.new do
begin
SlowListMixinWithAdvisoryLock.create!(parent_id: 5)
rescue
# Retry ActiveRecord::ConnectionTimeoutError, etc
retry
end
end
end
threads.each(&:join)

assert_equal(range.to_a, SlowListMixinWithAdvisoryLock.order(:pos).pluck(:pos))
end
end

class ListWithCallbackTest < ActsAsListTestCase

include Shared::List
Expand Down