active_record_mutex 2.5.1 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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"