with_transactional_lock 2.3.0 → 3.0.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 +4 -4
- data/README.md +7 -25
- data/lib/with_transactional_lock/mixin.rb +1 -12
- data/lib/with_transactional_lock/version.rb +1 -1
- data/lib/with_transactional_lock.rb +0 -1
- metadata +10 -31
- data/lib/generators/with_transactional_lock/install/install_generator.rb +0 -36
- data/lib/generators/with_transactional_lock/install/templates/README +0 -8
- data/lib/generators/with_transactional_lock/install/templates/db/migrate/create_transactional_advisory_locks.rb +0 -10
- data/lib/with_transactional_lock/my_sql_helper.rb +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5a37a429d4501cab5013a8adda04f58543f9dfa1ce272669fb3e77b50ba41ba1
|
4
|
+
data.tar.gz: 21c127ebf79c22c6e6b658b0bd3084553af9f0f0a414171438912d6f3f32b63a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6f177f997f70e2b22c426ca79281a537ef4a058e6baaf31bbe34730700b451c06fc5c2e81b3f809d5c525feab6c3e5427cc6650051f08ff5ca63c26d9242d5b4
|
7
|
+
data.tar.gz: fef780d0e0805babed56e1e6f2f64642c13afcc27ac1ada1b71a2e4b9ed9b24aa523ca571f02b5c711d6ac47159569240a853fa8aedbac4d0eb06507c6690dbd
|
data/README.md
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
# with_transactional_lock
|
2
2
|
|
3
3
|
A simple extension to ActiveRecord for performing advisory locking on
|
4
|
-
|
4
|
+
PostgreSQL.
|
5
5
|
|
6
6
|
An advisory lock is a database-level mutex that can be used to prevent
|
7
7
|
concurrent access to a shared resource or to prevent two workers from
|
8
8
|
performing the same process concurrently.
|
9
9
|
|
10
10
|
This gem is different from other advisory locking gems because it
|
11
|
-
uses advisory transaction locks instead of advisory session locks.
|
11
|
+
uses advisory transaction locks instead of advisory session locks.
|
12
12
|
|
13
13
|
## Why transactional?
|
14
14
|
|
@@ -31,7 +31,7 @@ preventing leaks.
|
|
31
31
|
Additionally, application developers tend to think about discrete units
|
32
32
|
of database work in terms of transactions. By leveraging the transaction
|
33
33
|
boundary, we ensure the advisory lock is released at the earliest
|
34
|
-
possible moment that it can be and no sooner.
|
34
|
+
possible moment that it can be and no sooner.
|
35
35
|
|
36
36
|
## Lock acquisition efficiency & fairness
|
37
37
|
|
@@ -60,25 +60,16 @@ waiting on I/O (possibly allowing another thread to use the CPU).
|
|
60
60
|
|
61
61
|
Add this line to your application's Gemfile:
|
62
62
|
|
63
|
-
```
|
63
|
+
```ruby
|
64
64
|
gem 'with_transactional_lock'
|
65
65
|
```
|
66
66
|
|
67
67
|
And then bundle install:
|
68
68
|
|
69
69
|
```
|
70
|
-
|
71
|
-
```
|
72
|
-
|
73
|
-
And then if you're using MySQL, you will need to run the installer:
|
74
|
-
|
75
|
-
```
|
76
|
-
$ rails g with_transactional_lock:install
|
70
|
+
bundle install
|
77
71
|
```
|
78
72
|
|
79
|
-
This will create a migration that will add an
|
80
|
-
`transactional_advisory_locks` table to your database.
|
81
|
-
|
82
73
|
## Usage
|
83
74
|
|
84
75
|
Because transactional locks are meaningless outside of the context of a
|
@@ -92,12 +83,12 @@ ActiveRecord::Base.with_transactional_lock('name_of_a_resource') do
|
|
92
83
|
end
|
93
84
|
```
|
94
85
|
|
95
|
-
This call will attempt to acquire an exclusive lock using the provided
|
86
|
+
This call will attempt to acquire an exclusive lock using the provided
|
96
87
|
lock name. It will wait indefinitely for that lock -- or at least as
|
97
88
|
long as your database connection timeout is willing to allow. Once the
|
98
89
|
lock is acquired you will have exclusive ownership of the advisory lock
|
99
90
|
with the name that you provided. Your block is free to execute its
|
100
|
-
critical work. Upon completion of your transaction, the lock will be
|
91
|
+
critical work. Upon completion of your transaction, the lock will be
|
101
92
|
released.
|
102
93
|
|
103
94
|
## Supported databases
|
@@ -109,15 +100,6 @@ PostgreSQL has first-class support for transactional advisory locks via
|
|
109
100
|
duration of a given transaction and automatically released upon
|
110
101
|
transaction commit.
|
111
102
|
|
112
|
-
### MySQL
|
113
|
-
|
114
|
-
MySQL does not have built-in support for transactional advisory locks.
|
115
|
-
So, MySQL gets a special treatment. We emulate the behavior of PostgreSQL
|
116
|
-
using a special `transactional_advisory_locks` table with a unique index
|
117
|
-
on the `lock_id` column. This allows us to provide the same transactional
|
118
|
-
and mutual exclusivity guarantees as PostgreSQL. The trade-off is that
|
119
|
-
you need to add another table to your database.
|
120
|
-
|
121
103
|
## License
|
122
104
|
|
123
105
|
Any contributions made to this project are covered under the MIT License, found [here](LICENSE)
|
@@ -23,8 +23,6 @@ module WithTransactionalLock
|
|
23
23
|
def self.locate(connection)
|
24
24
|
adapter = connection.adapter_name.downcase.to_sym
|
25
25
|
case adapter
|
26
|
-
when :mysql, :mysql2
|
27
|
-
MySqlAdvisoryLock
|
28
26
|
when :postgresql
|
29
27
|
PostgresAdvisoryLock
|
30
28
|
else
|
@@ -55,20 +53,11 @@ module WithTransactionalLock
|
|
55
53
|
end
|
56
54
|
end
|
57
55
|
|
58
|
-
class MySqlAdvisoryLock < AdvisoryLockBase
|
59
|
-
private
|
60
|
-
|
61
|
-
def acquire_lock
|
62
|
-
connection.execute("insert into transactional_advisory_locks values (#{connection.quote(db_lock_name)}) \
|
63
|
-
on duplicate key update lock_id = lock_id")
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
56
|
class PostgresAdvisoryLock < AdvisoryLockBase
|
68
57
|
private
|
69
58
|
|
70
59
|
def acquire_lock
|
71
|
-
connection.execute("
|
60
|
+
connection.execute("/* lock:#{lock_name} */ SELECT pg_advisory_xact_lock(#{connection.quote(db_lock_name)})")
|
72
61
|
end
|
73
62
|
end
|
74
63
|
end
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: with_transactional_lock
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sam Moore
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: activerecord
|
@@ -19,7 +18,7 @@ dependencies:
|
|
19
18
|
version: '7.0'
|
20
19
|
- - "<"
|
21
20
|
- !ruby/object:Gem::Version
|
22
|
-
version: '
|
21
|
+
version: '8.1'
|
23
22
|
type: :runtime
|
24
23
|
prerelease: false
|
25
24
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -29,7 +28,7 @@ dependencies:
|
|
29
28
|
version: '7.0'
|
30
29
|
- - "<"
|
31
30
|
- !ruby/object:Gem::Version
|
32
|
-
version: '
|
31
|
+
version: '8.1'
|
33
32
|
- !ruby/object:Gem::Dependency
|
34
33
|
name: railties
|
35
34
|
requirement: !ruby/object:Gem::Requirement
|
@@ -39,7 +38,7 @@ dependencies:
|
|
39
38
|
version: '7.0'
|
40
39
|
- - "<"
|
41
40
|
- !ruby/object:Gem::Version
|
42
|
-
version: '
|
41
|
+
version: '8.1'
|
43
42
|
type: :runtime
|
44
43
|
prerelease: false
|
45
44
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -49,23 +48,9 @@ dependencies:
|
|
49
48
|
version: '7.0'
|
50
49
|
- - "<"
|
51
50
|
- !ruby/object:Gem::Version
|
52
|
-
version: '
|
51
|
+
version: '8.1'
|
53
52
|
- !ruby/object:Gem::Dependency
|
54
53
|
name: appraisal
|
55
|
-
requirement: !ruby/object:Gem::Requirement
|
56
|
-
requirements:
|
57
|
-
- - "~>"
|
58
|
-
- !ruby/object:Gem::Version
|
59
|
-
version: 2.2.0
|
60
|
-
type: :development
|
61
|
-
prerelease: false
|
62
|
-
version_requirements: !ruby/object:Gem::Requirement
|
63
|
-
requirements:
|
64
|
-
- - "~>"
|
65
|
-
- !ruby/object:Gem::Version
|
66
|
-
version: 2.2.0
|
67
|
-
- !ruby/object:Gem::Dependency
|
68
|
-
name: betterlint
|
69
54
|
requirement: !ruby/object:Gem::Requirement
|
70
55
|
requirements:
|
71
56
|
- - ">="
|
@@ -79,7 +64,7 @@ dependencies:
|
|
79
64
|
- !ruby/object:Gem::Version
|
80
65
|
version: '0'
|
81
66
|
- !ruby/object:Gem::Dependency
|
82
|
-
name:
|
67
|
+
name: betterlint
|
83
68
|
requirement: !ruby/object:Gem::Requirement
|
84
69
|
requirements:
|
85
70
|
- - ">="
|
@@ -93,7 +78,7 @@ dependencies:
|
|
93
78
|
- !ruby/object:Gem::Version
|
94
79
|
version: '0'
|
95
80
|
- !ruby/object:Gem::Dependency
|
96
|
-
name:
|
81
|
+
name: database_cleaner
|
97
82
|
requirement: !ruby/object:Gem::Requirement
|
98
83
|
requirements:
|
99
84
|
- - ">="
|
@@ -148,7 +133,7 @@ dependencies:
|
|
148
133
|
- - ">="
|
149
134
|
- !ruby/object:Gem::Version
|
150
135
|
version: '0'
|
151
|
-
description: Advisory locking support for
|
136
|
+
description: Advisory locking support for Postgresql done right.
|
152
137
|
email:
|
153
138
|
- sam@betterment.com
|
154
139
|
executables: []
|
@@ -158,13 +143,9 @@ files:
|
|
158
143
|
- LICENSE
|
159
144
|
- README.md
|
160
145
|
- Rakefile
|
161
|
-
- lib/generators/with_transactional_lock/install/install_generator.rb
|
162
|
-
- lib/generators/with_transactional_lock/install/templates/README
|
163
|
-
- lib/generators/with_transactional_lock/install/templates/db/migrate/create_transactional_advisory_locks.rb
|
164
146
|
- lib/with_transactional_lock.rb
|
165
147
|
- lib/with_transactional_lock/engine.rb
|
166
148
|
- lib/with_transactional_lock/mixin.rb
|
167
|
-
- lib/with_transactional_lock/my_sql_helper.rb
|
168
149
|
- lib/with_transactional_lock/version.rb
|
169
150
|
homepage: https://github.com/Betterment/with_transactional_lock
|
170
151
|
licenses:
|
@@ -172,7 +153,6 @@ licenses:
|
|
172
153
|
metadata:
|
173
154
|
allowed_push_host: https://rubygems.org
|
174
155
|
rubygems_mfa_required: 'true'
|
175
|
-
post_install_message:
|
176
156
|
rdoc_options: []
|
177
157
|
require_paths:
|
178
158
|
- lib
|
@@ -187,8 +167,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
187
167
|
- !ruby/object:Gem::Version
|
188
168
|
version: '0'
|
189
169
|
requirements: []
|
190
|
-
rubygems_version: 3.
|
191
|
-
signing_key:
|
170
|
+
rubygems_version: 3.7.0
|
192
171
|
specification_version: 4
|
193
172
|
summary: Transactional advisory locks for ActiveRecord
|
194
173
|
test_files: []
|
@@ -1,36 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'rails/generators/base'
|
4
|
-
require 'rails/generators/active_record'
|
5
|
-
|
6
|
-
module WithTransactionalLock
|
7
|
-
module Generators
|
8
|
-
class InstallGenerator < Rails::Generators::Base
|
9
|
-
include Rails::Generators::Migration
|
10
|
-
source_root File.expand_path('templates', __dir__)
|
11
|
-
|
12
|
-
def show_readme
|
13
|
-
readme 'README'
|
14
|
-
end
|
15
|
-
|
16
|
-
def create_with_transactional_lock_migration
|
17
|
-
if mysql?
|
18
|
-
migration_template(
|
19
|
-
'db/migrate/create_transactional_advisory_locks.rb',
|
20
|
-
'db/migrate/create_transactional_advisory_locks.rb',
|
21
|
-
)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def self.next_migration_number(dir)
|
26
|
-
ActiveRecord::Generators::Base.next_migration_number(dir)
|
27
|
-
end
|
28
|
-
|
29
|
-
private
|
30
|
-
|
31
|
-
def mysql?
|
32
|
-
ActiveRecord::Base.connection.adapter_name.downcase.include?('mysql')
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
@@ -1,8 +0,0 @@
|
|
1
|
-
*******************************************************************************
|
2
|
-
|
3
|
-
If you're using MySql, then run `rake db:migrate` to setup the `transactional_advisory_locks`
|
4
|
-
table.
|
5
|
-
|
6
|
-
If you're using Postgresql, then you're all set!
|
7
|
-
|
8
|
-
*******************************************************************************
|
@@ -1,10 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class CreateTransactionalAdvisoryLocks < ActiveRecord::Migration
|
4
|
-
def change
|
5
|
-
create_table :transactional_advisory_locks, id: false do |t|
|
6
|
-
t.integer :lock_id, null: false, limit: 8
|
7
|
-
end
|
8
|
-
add_index(:transactional_advisory_locks, :lock_id, unique: true, name: 'index_transactional_advisory_locks_on_lock_id')
|
9
|
-
end
|
10
|
-
end
|
@@ -1,13 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module WithTransactionalLock
|
4
|
-
class MySqlHelper
|
5
|
-
def self.cleanup(klass = ActiveRecord::Base)
|
6
|
-
klass.connection_pool.with_connection do |conn|
|
7
|
-
target_count = conn.select_value('select count(1) from transactional_advisory_locks')
|
8
|
-
count = 0
|
9
|
-
count += conn.delete('delete from transactional_advisory_locks limit 1000') until count >= target_count
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|