active_record_mutex 2.5.1 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: c06234eeb9430aa24c776af33c606bd631ca1dac
4
- data.tar.gz: dbf5f5346968fa6918f8bb4aa9d46bdfb973754f
2
+ SHA256:
3
+ metadata.gz: 337ab2cfb637dc992dda05b67bc12098b0d8d7266591dc741d4918dbb3238aee
4
+ data.tar.gz: e812c787de8cb82696027803eafa426793af334d4c1d847c9956473a7d1faf20
5
5
  SHA512:
6
- metadata.gz: 54758225bd9d0e17e8f8eafada346686df7023f3d152da0e5af5bf233d0cc167cee453350c1f2fb85d12b7b422de690137e6c2f037b47470bf4abc3e0140939c
7
- data.tar.gz: 869b14192f5ea6b7ded849e22d54fa1dc6eed56d15fed0de0ffedb18a8ff871f195bbf11c6e11cf6fc87880a443e2bdf05c8b2062fe46074ccff1cf9d309f20a
6
+ metadata.gz: f8cb37ec99b0a52267f964b032b8f061801bef3000a11d355af5cddef3d5f075c471bd51e3fa79310136cbc53b3cd0376c293a501363b4f48f979fc774f03efc
7
+ data.tar.gz: 1418b58e067e151bb33e92cdd7beb215cb3fdce4ea9f7439e34ae6315b452c80d9e0cafa5f368f399ec2abb7d48c0479a1a233b1fc01f4ff364043042647faa1
data/.envrc ADDED
@@ -0,0 +1 @@
1
+ export DATABASE_URL="mysql2://root@127.0.0.1:3336/test"
data/CHANGES.md ADDED
@@ -0,0 +1,151 @@
1
+ # Changes
2
+
3
+ ## 2024-10-16 v3.1.0
4
+
5
+ * Changes for **3.1.0**:
6
+ * Implemented `all_mutexes` method in ActiveRecord::DatabaseMutex
7
+ * Added `MutexInfo` class as a subclass of OpenStruct
8
+ * Updated `active_record_mutex.gemspec` to use **0.6** version of `ostruct`
9
+ * Removed `tins` dependency
10
+
11
+ ## 2024-10-15 v3.0.0
12
+
13
+ ### Major Enhancements
14
+
15
+ * **Renamed method names**: Updated `acquired_lock?` to `owned?` and
16
+ `not_aquired_lock?` to `not_owned?` to better reflect their functionality.
17
+ * **Enhanced documentation**: Improved documentation for several methods,
18
+ providing clearer explanations of their purpose and behavior.
19
+ * **Synchronize method updates**: The synchronize method now accepts a `:block`
20
+ option instead of `:nonblock`, allowing for more flexibility in locking and
21
+ unlocking mutexes. Additionally, the `lock` method has been updated to handle
22
+ this new option.
23
+
24
+ ### New Features
25
+
26
+ * **Timeout handling**: Introduced the `:raise` option to the lock method,
27
+ which defaults to true. If set to false, no MutexLocked exception is raised
28
+ when the timeout expires.
29
+ * **Internal name generation**: Added the `internal_name` method for generating
30
+ internal mutex names, replacing encoded names in counter logic.
31
+ * **Improved unlock behavior**: Modified the unlock method to raise
32
+ MutexUnlockFailed if the lock doesn't belong to this connection or return
33
+ false if `:raise` is false.
34
+
35
+ ### Refactorings
36
+
37
+ * **Simplified database setup**: Updated test helper to use a more
38
+ straightforward approach for setting up databases.
39
+ * **Ensured counter length**: Added checks to ensure counter names are within
40
+ the allowed range, preventing potential issues with MySQL variable name
41
+ lengths.
42
+ * **Refactored support for DATABASE_URL**: Improved support for the
43
+ `DATABASE_URL` environment variable in tests.
44
+
45
+ ### Other Changes
46
+
47
+ * **Updated documentation**: Reflected current gem installation methods and API
48
+ changes in README.md.
49
+ * **Improved testing**: Added test cases for lock acquisition, release, and
50
+ timeout handling, as well as multiple threads scenarios.
51
+ * **Removed Travis CI config**: Removed configuration files related to Ruby
52
+ versioning and CodeClimate reporting.
53
+
54
+ ### Documentation Updates
55
+
56
+ * **Added CHANGES.md file**: Included a changelog file to keep track of changes
57
+ across releases.
58
+ * **Refactored database mutex documentation**: Improved documentation for
59
+ methods and exception classes.
60
+ * **Updated test cases**: Updated example usage, test updates, and refactorings
61
+ related to the `DATABASE_URL` environment variable.
62
+
63
+ ## 2016-12-07 v2.5.1
64
+
65
+ * **Specify correct version of activerecord**
66
+
67
+ ## 2016-12-07 v2.5.0
68
+
69
+ * Be compatible with rails ~>5.0 versions
70
+ + Updated dependencies to support Rails **5.0**
71
+ * Start simplecov
72
+ + Added SimpleCov integration using `simplecov`
73
+ * use command to push reports
74
+ + Updated CodeClimate reporting to use a custom command
75
+ * Use new way to use codeclimate
76
+ + Switched to the latest CodeClimate API usage method
77
+
78
+ ## 2016-11-23 v2.4.0
79
+
80
+ * Revert changes made in later **2.3** versions
81
+ * Only check size for `mysql` version `>= 5.7`
82
+
83
+ ## 2016-09-07 v2.3.8
84
+
85
+ * **Significant Changes**
86
+ + Test more rubies
87
+ + Use a normal table for counters instead of temporary tables to avoid issues
88
+ on replicating setups with enforced GTID consistency.
89
+
90
+
91
+
92
+ ## 2016-08-24 v2.3.3
93
+
94
+ * **Only check size for MySQL version >= 5.7**
95
+ + Changed logic to only consider size checks for MySQL versions greater than
96
+ or equal to **5.7**.
97
+
98
+ ## 2016-08-23 v2.3.2
99
+
100
+ * **Restrictions on variable name length**:
101
+ + Added check to prevent long variable names in MySQL 5.7
102
+
103
+ ## 2016-08-23 v2.3.1
104
+
105
+ * **Encode counter name to conform to MySQL rules**
106
+ + Changed `counter_name` to use
107
+ `code:mysql_real_escape_string(code:counter_name)` in the code.
108
+
109
+ ## 2016-08-23 v2.3.0
110
+
111
+ * Use shorter method names
112
+ * Implement counter logic to allow nesting of locks (using `code`: `counter_logic`)
113
+ * Work with mysql 5.5 and 5.6 semantics
114
+
115
+ ## 2016-08-19 v2.2.1
116
+
117
+ * **Added support for `Rails.env`**
118
+ + Now uses `Rails.env` instead of hardcoded environment variable names.
119
+
120
+ ## 2016-08-19 v2.2.0
121
+
122
+ * Make locks rails environments independent
123
+ * Disable workaround for wonky mysql behavior
124
+ + Use `simplecov` for code coverage instead of a workaround
125
+ * Update to work with Rails environments independently
126
+
127
+ ## 2014-12-12 v2.0.0
128
+
129
+ #### Changes
130
+
131
+ * Change semantic of `ActiveRecord::Base#mutex`
132
+
133
+ #### Documentation
134
+
135
+ * Improve and reflect changes
136
+ * Add license information
137
+ * Add codeclimate token
138
+
139
+ ## 2013-11-18 v1.0.1
140
+
141
+ * **Avoid annoying rubygems warning**
142
+ + Added a fix to prevent the RubyGems warning from appearing.
143
+
144
+ ## 2012-05-07 v1.0.0
145
+
146
+ * **Avoid conflicts with top level `Mutex` class**
147
+ + Changes to avoid naming conflict with Ruby's built-in `Mutex` class.
148
+
149
+ ## 2011-07-18 v0.0.1
150
+
151
+ * Start
data/Gemfile CHANGED
@@ -3,5 +3,3 @@
3
3
  source 'https://rubygems.org'
4
4
 
5
5
  gemspec
6
-
7
- gem "codeclimate-test-reporter", group: :test, require: nil
data/README.md CHANGED
@@ -12,33 +12,123 @@ You can use rubygems to fetch the gem and install it for you:
12
12
 
13
13
  # gem install active_record_mutex
14
14
 
15
- You can also put this line into your Rails environment.rb file
15
+ You can also put this line into your Gemfile
16
16
 
17
- config.gem 'active_record_mutex'
18
-
19
- and install the gem via
20
-
21
- $ rake gems:install
17
+ gem 'active_record_mutex'
22
18
 
23
19
  ## Usage
24
20
 
25
- If you want to synchronize method calls to your model's methods you can easily
26
- do this by passing a mutex instance to ActiveRecord's synchronize class method.
27
- This mutex instance will be named Foo like the ActiveRecord was named:
21
+ ### Using synchronize for critical sections
22
+
23
+ To synchronize on a specific ActiveRecord instance you can do this:
28
24
 
29
- class Foo < ActiveRecord::Base
30
- def foo
25
+ class Foo < ActiveRecord::Base
31
26
  end
32
27
 
33
- synchronize :foo, :with => :mutex
34
- end
28
+ foo = Foo.find(666)
29
+ foo.mutex.synchronize do
30
+ # Critical section of code here
31
+ end
35
32
 
36
33
  If you want more control over the mutex and/or give it a special name you can
37
34
  create Mutex instance like this:
38
35
 
39
- my_mutex = ActiveRecord::Mutex::Mutex.new(:name => 'my_mutex')
36
+ my_mutex = ActiveRecord::DatabaseMutex.for('my_mutex')
37
+
38
+ Now you can send all messages directly to the Mutex instance or use the custom
39
+ mutex instance to `synchronize` method calls or other operations:
40
+
41
+ my_mutex.synchronize do
42
+ # Critical section of code here
43
+ end
44
+
45
+ ### Low-Level Demonstration: Multiple Process Example
46
+
47
+ The following example demonstrates how the Mutex works at a lower level, using
48
+ direct MySQL connections. This is not intended as a real-world use case, but
49
+ rather to illustrate the underlying behavior.
50
+
51
+ If two processes are connected to the same database, configured via e.g.
52
+ `DATABASE_URL=mysql2://root@127.0.0.1:3336/test` and this is process 1:
53
+
54
+ # process1.rb
55
+
56
+ mutex = ActiveRecord::DatabaseMutex.for('my_mutex')
57
+
58
+ lock_result1 = mutex.lock(timeout: 5)
59
+ puts "Process 1: Lock acquired (first): #{lock_result1}"
60
+
61
+ puts "Process 1: Waiting for 10s"
62
+ sleep(10)
63
+
64
+ lock_result2 = mutex.lock(timeout: 5)
65
+ puts "Process 1: Lock acquired (second): #{lock_result2}"
66
+
67
+ mutex.unlock # first
68
+ mutex.unlock # second
69
+
70
+ puts "Process 1: Unlocked the mutex twice"
71
+
72
+ puts "Process 1: Waiting for 10s"
73
+ sleep(10)
74
+
75
+ puts "Process 1: Exiting"
76
+
77
+ and this process 2:
78
+
79
+ # process2.rb
80
+
81
+ mutex = ActiveRecord::DatabaseMutex.for('my_mutex')
82
+
83
+ begin
84
+ lock_result3 = mutex.lock(timeout: 5)
85
+ puts "Process 2: Lock acquired (first): #{lock_result3}"
86
+ rescue ActiveRecord::DatabaseMutex::MutexLocked
87
+ puts "Process 2: Mutex locked by another process, waiting..."
88
+ end
89
+
90
+ puts "Process 2: Waiting for 10s"
91
+ sleep(10)
92
+
93
+ puts "Process 2: Trying to lock again"
94
+ lock_result4 = mutex.lock(timeout: 5)
95
+ puts "Process 2: Lock acquired (second): #{lock_result4}"
96
+
97
+ puts "Process 2: Exiting"
98
+
99
+ Running these processes in parallel will output the following:
100
+
101
+ Process 1: Lock acquired (first): true
102
+ Process 1: Waiting for 10s
103
+ Process 2: Mutex locked by another process, waiting...
104
+ Process 2: Waiting for 10s
105
+ Process 1: Lock acquired (second): true
106
+ Process 1: Unlocked the mutex twice
107
+ Process 1: Waiting for 10s
108
+ Process 2: Trying to lock again
109
+ Process 2: Lock acquired (second): true
110
+ Process 2: Exiting
111
+ Process 1: Exiting
112
+
113
+ The two ruby files can be found in the examples subdirectory as
114
+ `examples/process1.rb` and `examples/process2.rb`. The necessary configuration
115
+ files, `.envrc` (`direnv allow`) and `docker-compose.yml`
116
+ (`docker compose up -d`), are also located in the root of this repository.
117
+
118
+ ## Running the tests
119
+
120
+ First start mysql in docker via `docker compose up -d` and configure your
121
+ environment via `direnv allow` as before and then run
122
+
123
+ rake test
124
+
125
+ or with coverage:
126
+
127
+ rake test START_SIMPLECOV=1
128
+
129
+ To test for different ruby versions in docker, run:
40
130
 
41
- Now you can send all messages directly to the Mutex instance.
131
+ all_images
42
132
 
43
133
  ## Download
44
134
 
data/Rakefile CHANGED
@@ -14,13 +14,16 @@ GemHadar do
14
14
  test_dir 'test'
15
15
  ignore '.*.sw[pon]', 'pkg', 'Gemfile.lock', '.DS_Store', 'coverage',
16
16
  '.byebug_history'
17
+ package_ignore '.all_images.yml', '.utilsrc', '.gitignore', 'VERSION',
18
+ *Dir.glob('.github/**/*', File::FNM_DOTMATCH)
17
19
  readme 'README.md'
18
20
  licenses << 'GPL-2'
19
21
 
20
- dependency 'mysql2', '~>0.3.0'
21
- dependency 'activerecord', '>= 4.0', '<6'
22
- dependency 'tins', '~> 1.12'
23
- development_dependency 'test-unit', '~>3.0'
24
- development_dependency 'byebug'
22
+ dependency 'mysql2', '~> 0.3'
23
+ dependency 'activerecord', '>= 4.0'
24
+ dependency 'ostruct', '~> 0.6'
25
+ development_dependency 'all_images', '~> 0.6'
26
+ development_dependency 'test-unit', '~> 3.0'
27
+ development_dependency 'debug'
25
28
  development_dependency 'simplecov'
26
29
  end
@@ -1,52 +1,33 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: active_record_mutex 2.5.1 ruby lib
2
+ # stub: active_record_mutex 3.1.0 ruby lib
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "active_record_mutex".freeze
6
- s.version = "2.5.1"
6
+ s.version = "3.1.0".freeze
7
7
 
8
8
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
9
9
  s.require_paths = ["lib".freeze]
10
10
  s.authors = ["Florian Frank".freeze]
11
- s.date = "2016-12-07"
11
+ s.date = "2024-10-16"
12
12
  s.description = "Mutex that can be used to synchronise ruby processes via an ActiveRecord datababase connection. (Only Mysql is supported at the moment.)".freeze
13
13
  s.email = "flori@ping.de".freeze
14
14
  s.extra_rdoc_files = ["README.md".freeze, "lib/active_record/database_mutex.rb".freeze, "lib/active_record/database_mutex/implementation.rb".freeze, "lib/active_record/database_mutex/version.rb".freeze, "lib/active_record/mutex.rb".freeze, "lib/active_record_mutex.rb".freeze]
15
- s.files = [".gitignore".freeze, ".travis.yml".freeze, "COPYING".freeze, "Gemfile".freeze, "README.md".freeze, "Rakefile".freeze, "VERSION".freeze, "active_record_mutex.gemspec".freeze, "lib/active_record/database_mutex.rb".freeze, "lib/active_record/database_mutex/implementation.rb".freeze, "lib/active_record/database_mutex/version.rb".freeze, "lib/active_record/mutex.rb".freeze, "lib/active_record_mutex.rb".freeze, "test/database_mutex_test.rb".freeze, "test/test_helper.rb".freeze]
15
+ s.files = [".envrc".freeze, "CHANGES.md".freeze, "COPYING".freeze, "Gemfile".freeze, "README.md".freeze, "Rakefile".freeze, "active_record_mutex.gemspec".freeze, "docker-compose.yml".freeze, "examples/process1.rb".freeze, "examples/process2.rb".freeze, "lib/active_record/database_mutex.rb".freeze, "lib/active_record/database_mutex/implementation.rb".freeze, "lib/active_record/database_mutex/version.rb".freeze, "lib/active_record/mutex.rb".freeze, "lib/active_record_mutex.rb".freeze, "test/database_mutex_test.rb".freeze, "test/test_helper.rb".freeze]
16
16
  s.homepage = "http://github.com/flori/active_record_mutex".freeze
17
17
  s.licenses = ["GPL-2".freeze]
18
18
  s.rdoc_options = ["--title".freeze, "ActiveRecordMutex - Implementation of a Mutex for Active Record".freeze, "--main".freeze, "README.md".freeze]
19
- s.rubygems_version = "2.6.8".freeze
19
+ s.rubygems_version = "3.5.18".freeze
20
20
  s.summary = "Implementation of a Mutex for Active Record".freeze
21
21
  s.test_files = ["test/database_mutex_test.rb".freeze, "test/test_helper.rb".freeze]
22
22
 
23
- if s.respond_to? :specification_version then
24
- s.specification_version = 4
23
+ s.specification_version = 4
25
24
 
26
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
27
- s.add_development_dependency(%q<gem_hadar>.freeze, ["~> 1.9.1"])
28
- s.add_development_dependency(%q<test-unit>.freeze, ["~> 3.0"])
29
- s.add_development_dependency(%q<byebug>.freeze, [">= 0"])
30
- s.add_development_dependency(%q<simplecov>.freeze, [">= 0"])
31
- s.add_runtime_dependency(%q<mysql2>.freeze, ["~> 0.3.0"])
32
- s.add_runtime_dependency(%q<activerecord>.freeze, ["< 6", ">= 4.0"])
33
- s.add_runtime_dependency(%q<tins>.freeze, ["~> 1.12"])
34
- else
35
- s.add_dependency(%q<gem_hadar>.freeze, ["~> 1.9.1"])
36
- s.add_dependency(%q<test-unit>.freeze, ["~> 3.0"])
37
- s.add_dependency(%q<byebug>.freeze, [">= 0"])
38
- s.add_dependency(%q<simplecov>.freeze, [">= 0"])
39
- s.add_dependency(%q<mysql2>.freeze, ["~> 0.3.0"])
40
- s.add_dependency(%q<activerecord>.freeze, ["< 6", ">= 4.0"])
41
- s.add_dependency(%q<tins>.freeze, ["~> 1.12"])
42
- end
43
- else
44
- s.add_dependency(%q<gem_hadar>.freeze, ["~> 1.9.1"])
45
- s.add_dependency(%q<test-unit>.freeze, ["~> 3.0"])
46
- s.add_dependency(%q<byebug>.freeze, [">= 0"])
47
- s.add_dependency(%q<simplecov>.freeze, [">= 0"])
48
- s.add_dependency(%q<mysql2>.freeze, ["~> 0.3.0"])
49
- s.add_dependency(%q<activerecord>.freeze, ["< 6", ">= 4.0"])
50
- s.add_dependency(%q<tins>.freeze, ["~> 1.12"])
51
- end
25
+ s.add_development_dependency(%q<gem_hadar>.freeze, ["~> 1.19".freeze])
26
+ s.add_development_dependency(%q<all_images>.freeze, ["~> 0.6".freeze])
27
+ s.add_development_dependency(%q<test-unit>.freeze, ["~> 3.0".freeze])
28
+ s.add_development_dependency(%q<debug>.freeze, [">= 0".freeze])
29
+ s.add_development_dependency(%q<simplecov>.freeze, [">= 0".freeze])
30
+ s.add_runtime_dependency(%q<mysql2>.freeze, ["~> 0.3".freeze])
31
+ s.add_runtime_dependency(%q<activerecord>.freeze, [">= 4.0".freeze])
32
+ s.add_runtime_dependency(%q<ostruct>.freeze, ["~> 0.6".freeze])
52
33
  end
@@ -0,0 +1,13 @@
1
+ services:
2
+ mysql:
3
+ image: mysql:8.0
4
+ restart: unless-stopped
5
+ ports:
6
+ - "127.0.0.1:3336:3306"
7
+ volumes:
8
+ - "mysql-data:/var/lib/mysql:delegated"
9
+ environment:
10
+ - "MYSQL_ALLOW_EMPTY_PASSWORD=1"
11
+
12
+ volumes:
13
+ mysql-data:
@@ -0,0 +1,31 @@
1
+ # process1.rb
2
+
3
+ require 'active_record_mutex'
4
+
5
+ database_url = URI.parse(ENV.fetch('DATABASE_URL'))
6
+ database = File.basename(database_url.path)
7
+ database_url_without_db = database_url.dup.tap { _1.path = '' }
8
+ ch = ActiveRecord::Base.establish_connection(database_url_without_db.to_s)
9
+ ch.with_connection { _1.execute %{ CREATE DATABASE IF NOT EXISTS #{database} } }
10
+ ActiveRecord::Base.establish_connection(database_url.to_s)
11
+
12
+ mutex = ActiveRecord::DatabaseMutex.for('my_mutex')
13
+
14
+ lock_result1 = mutex.lock(timeout: 5)
15
+ puts "Process 1: Lock acquired (first): #{lock_result1}"
16
+
17
+ puts "Process 1: Waiting for 10s"
18
+ sleep(10)
19
+
20
+ lock_result2 = mutex.lock(timeout: 5)
21
+ puts "Process 1: Lock acquired (second): #{lock_result2}"
22
+
23
+ mutex.unlock # first
24
+ mutex.unlock # second
25
+
26
+ puts "Process 1: Unlocked the mutex twice"
27
+
28
+ puts "Process 1: Waiting for 10s"
29
+ sleep(10)
30
+
31
+ puts "Process 1: Exiting"
@@ -0,0 +1,28 @@
1
+ # process2.rb
2
+
3
+ require 'active_record_mutex'
4
+
5
+ database_url = URI.parse(ENV.fetch('DATABASE_URL'))
6
+ database = File.basename(database_url.path)
7
+ database_url_without_db = database_url.dup.tap { _1.path = '' }
8
+ ch = ActiveRecord::Base.establish_connection(database_url_without_db.to_s)
9
+ ch.with_connection { _1.execute %{ CREATE DATABASE IF NOT EXISTS #{database} } }
10
+ ActiveRecord::Base.establish_connection(database_url.to_s)
11
+
12
+ mutex = ActiveRecord::DatabaseMutex.for('my_mutex')
13
+
14
+ begin
15
+ lock_result3 = mutex.lock(timeout: 5)
16
+ puts "Process 2: Lock acquired (first): #{lock_result3}"
17
+ rescue ActiveRecord::DatabaseMutex::MutexLocked
18
+ puts "Process 2: Mutex locked by another process, waiting..."
19
+ end
20
+
21
+ puts "Process 2: Waiting for 10s"
22
+ sleep(10)
23
+
24
+ puts "Process 2: Trying to lock again"
25
+ lock_result4 = mutex.lock(timeout: 5)
26
+ puts "Process 2: Lock acquired (second): #{lock_result4}"
27
+
28
+ puts "Process 2: Exiting"