locker 0.3.0 → 0.6.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: 8d4fe6495ead44c7d486b9b92e2e3a1e426f544c
4
- data.tar.gz: 992771712a5be9b554ca67b55c89f7817b3dcd63
2
+ SHA256:
3
+ metadata.gz: 6848498a3a915838b9b0df22e796e21ac1fdf39c36ffa0256d83a2e217c82d99
4
+ data.tar.gz: 816eb78e162302a315944f4863b5c5373a64f69e927af60ed8d4dd8299178f18
5
5
  SHA512:
6
- metadata.gz: a8118488900768eb4350649fd1e03b0ad96dcc16777975163f914b46e6b89ba25a226189e8e4ad864f4b60197d7adccd709402c04384b5ee1885771f455e63c7
7
- data.tar.gz: b225d2c0d59c66ceaf30a194e76b4a58606bac879adf83e74b85cd6f60561630f1def4ae9fdd6c47f5dbc67596356f81909dca3b7d9c761fd2d13ed60f6144b6
6
+ metadata.gz: cc30fcb1130c337230549c235b8f7bb13bb66870a8e3a69e08e56a19cd330e52742cbb553718a6db8da3b48a0a1ddb4a087ff8c99b072ce346e7a2448f87fab1
7
+ data.tar.gz: 5cccbb2b52a857a052145498f9cad6001c05e8d19bb7b1eb3574ff9f1d908ffbb46af72456214814bdd2d9a0e31ad3c94a1ae419b39f7abdf07fafa091cf9769
data/.gitignore CHANGED
@@ -1,6 +1,8 @@
1
1
  *.gem
2
2
  .bundle
3
3
  Gemfile.lock
4
+ Gemfile_rails_3.lock
5
+ Gemfile_rails_4.lock
4
6
  pkg/*
5
7
  /spec/database.yml
6
8
  .rvmrc
@@ -0,0 +1 @@
1
+ ruby 2.5.5
@@ -1,8 +1,39 @@
1
1
  language: ruby
2
2
  gemfile:
3
- - gemfiles/rails3.gemfile
3
+ - Gemfile_rails_3
4
+ - Gemfile_rails_4
5
+ - Gemfile_rails_5
4
6
  rvm:
5
- - 2.1.5
6
- - 2.2.1
7
- - jruby-19mode
7
+ - ruby-2.1.10
8
+ - ruby-2.2.7
9
+ - ruby-2.3.4
10
+ - ruby-2.4.1
11
+ - ruby-2.5.5
12
+ - jruby-9.1.9.0
8
13
  script: bundle exec rspec
14
+ matrix:
15
+ exclude:
16
+ - rvm: ruby-2.4.1
17
+ gemfile: Gemfile_rails_3
18
+ - rvm: ruby-2.1.10
19
+ gemfile: Gemfile_rails_5
20
+ - rvm: ruby-2.2.7
21
+ gemfile: Gemfile_rails_5
22
+ - rvm: ruby-2.3.4
23
+ gemfile: Gemfile_rails_5
24
+ - rvm: ruby-2.4.1
25
+ gemfile: Gemfile_rails_5
26
+ - rvm: ruby-2.5.5
27
+ gemfile: Gemfile_rails_3
28
+ - rvm: ruby-2.5.5
29
+ gemfile: Gemfile_rails_4
30
+ - rvm: jruby-9.1.9.0
31
+ gemfile: Gemfile_rails_5
32
+ services:
33
+ - postgresql
34
+ addons:
35
+ postgresql: "10"
36
+ apt:
37
+ packages:
38
+ - postgresql-10
39
+ - postgresql-client-10
data/Gemfile CHANGED
@@ -1,8 +1,17 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- gem "activerecord", ">=3.2", "<5"
3
+ gem "activerecord", ">=3.2", "<6"
4
4
 
5
5
  group :development, :test do
6
- gem "pg"
7
- gem "rspec", "~> 3.2.0"
6
+ platform :ruby do
7
+ gem "pg", "~> 0.21.0", "< 1.0"
8
+ gem "pry", "~> 0.13.1"
9
+ gem "pry-byebug", "~> 3.9.0"
10
+ end
11
+
12
+ platform :jruby do
13
+ gem 'activerecord-jdbcpostgresql-adapter', '= 1.3.25'
14
+ end
15
+
16
+ gem "rspec", "~> 3.9.0"
8
17
  end
@@ -0,0 +1,17 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "activerecord", ">=3.2", "<4"
4
+
5
+ group :development, :test do
6
+ platform :ruby do
7
+ gem "pg", "< 1.0"
8
+ gem "pry", "~> 0.10.4"
9
+ gem "pry-byebug", "~> 3.4.2"
10
+ end
11
+
12
+ platform :jruby do
13
+ gem 'activerecord-jdbcpostgresql-adapter', '= 1.3.25'
14
+ end
15
+
16
+ gem "rspec", "~> 3.2.0"
17
+ end
@@ -0,0 +1,17 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "activerecord", ">=4", "<5"
4
+
5
+ group :development, :test do
6
+ platform :ruby do
7
+ gem "pg", "< 1.0"
8
+ gem "pry", "~> 0.10.4"
9
+ gem "pry-byebug", "~> 3.4.2"
10
+ end
11
+
12
+ platform :jruby do
13
+ gem 'activerecord-jdbcpostgresql-adapter', '= 1.3.25'
14
+ end
15
+
16
+ gem "rspec", "~> 3.2.0"
17
+ end
@@ -0,0 +1,17 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "activerecord", "~> 5"
4
+
5
+ group :development, :test do
6
+ platform :ruby do
7
+ gem "pg", "~> 0.21.0", "< 1.0"
8
+ gem "pry", "~> 0.13.1"
9
+ gem "pry-byebug", "~> 3.9.0"
10
+ end
11
+
12
+ platform :jruby do
13
+ gem 'activerecord-jdbcpostgresql-adapter', "~> 50"
14
+ end
15
+
16
+ gem "rspec", "~> 3.9.0"
17
+ end
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Locker is a locking mechanism for limiting the concurrency of ruby code using the database.
4
4
 
5
- Locker is dependent on Postgres and the ActiveRecord (>= 3.2.0) gem.
5
+ Locker is dependent on Postgres and the ActiveRecord (>= 3.2.0 and < 6.0) gem.
6
6
 
7
7
  ## Supported Rubies
8
8
 
@@ -27,7 +27,7 @@ Suppose you have a process running on a server that continually performs a task.
27
27
  #### Code (lib/new_feed_checker.rb)
28
28
 
29
29
  ```ruby
30
- while true
30
+ loop do
31
31
  FeedChecker.check_for_new_feeds
32
32
  end
33
33
  ```
@@ -49,7 +49,7 @@ This would work fantastic, so long as `FeedChecker.check_for_new_feeds` is safe
49
49
  ```ruby
50
50
  Locker.run("new-feed-checker") do # One server will get the lock
51
51
  # Only one server will get here
52
- while true
52
+ loop do
53
53
  FeedChecker.check_for_new_feeds
54
54
  end
55
55
  end # Lock is released at this point
@@ -68,7 +68,7 @@ This is great! We've made sure that only one server can run the code at any give
68
68
  ```ruby
69
69
  Locker.run("new-feed-checker", :blocking => true) do
70
70
  # Only one server will get here at a time. The other server will patiently wait.
71
- while true
71
+ loop do
72
72
  FeedChecker.check_for_new_feeds
73
73
  end
74
74
  end # Lock is released at this point
@@ -150,7 +150,7 @@ end
150
150
  In our use we've settled on a common pattern, one that lets us distribute the load of our processes between our application and/or utility servers while making sure we have no single point of failure. This means that no single server going down (except the database) will stop the code from executing. Continuing from the code above, we'll use the example of the RSS/Atom feed checker, `FeedChecker.check_for_new_feeds`. To improve on the previous examples, we'll make the code rotate among our servers, so over a long enough time period each server will have spent an equal amount of time running the task.
151
151
 
152
152
  ```ruby
153
- while true
153
+ loop do
154
154
  Locker.run("new-feed-checker", :blocking => true) do
155
155
  FeedChecker.check_for_new_feeds
156
156
  end
@@ -6,18 +6,24 @@ class Locker
6
6
 
7
7
  attr_reader :key, :crc, :lockspace, :blocking, :locked
8
8
 
9
+ # The advisory function we use from PostgreSQL needs the arguments to be
10
+ # INT, therefore this are the range of int numbers for PostgreSQL
9
11
  MAX_LOCK = 2147483647
10
12
  MIN_LOCK = -2147483648
13
+
14
+ # Max number that a 32bit computer can hold
11
15
  OVERFLOW_ADJUSTMENT = 2**32
12
16
 
13
17
  def initialize(key, options={})
14
18
  raise ArgumentError, "key must be a string" unless key.is_a?(String)
15
19
 
16
- @key = key
17
- @crc = convert_to_crc(key)
18
- @lockspace = (options[:lockspace] || 1)
19
- @blocking = !!options[:blocking]
20
- @locked = false
20
+ @key = key
21
+ @crc = convert_to_crc(key)
22
+ @lockspace = (options[:lockspace] || 1)
23
+ @blocking = !!options[:blocking]
24
+ @locked = false
25
+ @block_timeout = options[:block_timeout]
26
+ @block_spin_wait = options[:block_spin_wait] || 0.005
21
27
 
22
28
  if !@lockspace.is_a?(Integer) || @lockspace < MIN_LOCK || @lockspace > MAX_LOCK
23
29
  raise ArgumentError, "The :lockspace option must be an integer between #{MIN_LOCK} and #{MAX_LOCK}"
@@ -32,8 +38,13 @@ class Locker
32
38
  def run(&block)
33
39
  connection = ActiveRecord::Base.connection_pool.checkout
34
40
  connection.transaction :requires_new => true do
41
+ if @blocking && @block_timeout
42
+ break_at = Time.now + @block_timeout
43
+ end
44
+
35
45
  while !get(connection) && @blocking
36
- sleep 0.5
46
+ break if break_at && break_at < Time.now
47
+ sleep @block_spin_wait
37
48
  end
38
49
 
39
50
  if @locked
@@ -58,9 +69,10 @@ class Locker
58
69
  @locked = false
59
70
  # Using a mutex to synchronize so that we're sure we're not
60
71
  # executing a query when we kill the thread.
61
- mutex.synchronize{}
62
- if checker.alive?
63
- checker.exit rescue nil
72
+ mutex.synchronize do
73
+ if checker.alive?
74
+ checker.exit rescue nil
75
+ end
64
76
  end
65
77
  end
66
78
  true
@@ -75,7 +87,13 @@ class Locker
75
87
  protected
76
88
 
77
89
  def get(connection)
78
- result = exec_query(connection, "SELECT pg_try_advisory_xact_lock(#{connection.quote(@lockspace)}, #{connection.quote(@crc)})")
90
+ lockspace_quote = connection.quote(@lockspace)
91
+ crc_quote = connection.quote(@crc)
92
+
93
+ result = exec_query(
94
+ connection,
95
+ "SELECT pg_try_advisory_xact_lock(#{lockspace_quote}, #{crc_quote})"
96
+ )
79
97
  @locked = successful_result?(result)
80
98
  end
81
99
 
@@ -86,6 +104,8 @@ class Locker
86
104
  end
87
105
  end
88
106
 
107
+ # CRC32 digest to get a decimal numeric of the key used, make sure the
108
+ # resulting number is within PostgreSql max and min Integer numbers
89
109
  def convert_to_crc(key)
90
110
  crc = Zlib.crc32(key)
91
111
  crc -= OVERFLOW_ADJUSTMENT if crc > MAX_LOCK
@@ -93,7 +113,11 @@ class Locker
93
113
  end
94
114
 
95
115
  def successful_result?(result)
96
- result.rows.size == 1 && result.rows[0].size == 1 && result.rows[0][0] == "t"
116
+ result.rows.size == 1 &&
117
+ result.rows[0].size == 1 && (
118
+ result.rows[0][0] == 't' || # Checking for old ActiveRecord
119
+ result.rows[0][0].class == TrueClass # Checking for the value true
120
+ )
97
121
  end
98
122
 
99
123
  def exec_query(connection, query)
@@ -26,6 +26,11 @@ class Locker
26
26
  ensure_key_exists
27
27
  end
28
28
 
29
+ def self.prune(before)
30
+ m = model || ::Lock
31
+ m.where(["locked_until < ?", before]).delete_all
32
+ end
33
+
29
34
  def self.run(key, options={}, &block)
30
35
  locker = new(key, options)
31
36
  locker.run(&block)
@@ -1,3 +1,3 @@
1
1
  class Locker
2
- VERSION = "0.3.0"
2
+ VERSION = "0.6.0".freeze
3
3
  end
@@ -1,5 +1,4 @@
1
- # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path("../lib", __FILE__)
1
+ $LOAD_PATH.push File.expand_path("lib", __dir__)
3
2
  require "locker/version"
4
3
 
5
4
  Gem::Specification.new do |s|
@@ -7,8 +6,8 @@ Gem::Specification.new do |s|
7
6
  s.version = Locker::VERSION
8
7
  s.authors = ["Nathan Sutton", "Justin Greer"]
9
8
  s.email = ["nate@zencoder.com", "justin@zencoder.com"]
10
- s.summary = %q{Locker is a locking mechanism for limiting the concurrency of ruby code using the database.}
11
- s.description = %q{Locker is a locking mechanism for limiting the concurrency of ruby code using the database. It presently only works with PostgreSQL.}
9
+ s.summary = 'Locker is a locking mechanism for limiting the concurrency of ruby code using the database.'
10
+ s.description = 'Locker is a locking mechanism for limiting the concurrency of ruby code using the database. It presently only works with PostgreSQL.'
12
11
 
13
12
  s.rubyforge_project = "locker"
14
13
 
@@ -16,7 +15,9 @@ Gem::Specification.new do |s|
16
15
  s.test_files = `git ls-files -- spec/*`.split("\n")
17
16
  s.require_paths = ["lib"]
18
17
 
19
- s.add_dependency "activerecord", ">=3.2", "<5"
20
- s.add_development_dependency "pg", "~> 0"
18
+ s.add_dependency "activerecord", ">=3.2", "< 6"
19
+ s.add_development_dependency "pg", "~> 0", "< 1"
20
+ s.add_development_dependency "pry", "~> 0.10.4"
21
+ s.add_development_dependency "pry-byebug", "~> 3.4.2"
21
22
  s.add_development_dependency "rspec", "~> 3.2"
22
23
  end
@@ -72,6 +72,27 @@ describe Locker::Advisory do
72
72
  expect(lock1_result).to be true
73
73
  expect(lock2_result).to be true
74
74
  end
75
+
76
+ it "blocks with a timeout" do
77
+ started_at = Time.now
78
+ t1 = Thread.new do
79
+ Locker::Advisory.run("foo") do
80
+ sleep 2
81
+ end
82
+ end
83
+
84
+ t2 = Thread.new do
85
+ sleep 0.5
86
+ Locker::Advisory.run("foo", :blocking => true, :block_timeout => 1) do
87
+ sleep 10 # never hits this
88
+ end
89
+ end
90
+
91
+ expect(t1.value).to be(true)
92
+ expect(t2.value).to be(false)
93
+
94
+ expect(Time.now - started_at).to be < 2.5
95
+ end
75
96
  end
76
97
 
77
98
  end
@@ -144,4 +144,19 @@ describe Locker do
144
144
  end
145
145
  end
146
146
 
147
+ describe "pruning" do
148
+ it "deletes old locks" do
149
+ l1 = Lock.create!(:key => "key1", :locked_until => 2.minutes.ago)
150
+ l2 = Lock.create!(:key => "key2", :locked_until => 4.minutes.ago)
151
+ l3 = Lock.create!(:key => "key3", :locked_until => 6.minutes.ago)
152
+ l4 = Lock.create!(:key => "key4", :locked_until => 8.minutes.ago)
153
+
154
+ Locker.prune(5.minutes.ago)
155
+
156
+ expect( Lock.where(:key => "key1").first ).to_not be_nil
157
+ expect( Lock.where(:key => "key2").first ).to_not be_nil
158
+ expect( Lock.where(:key => "key3").first ).to be_nil
159
+ expect( Lock.where(:key => "key3").first ).to be_nil
160
+ end
161
+ end
147
162
  end
@@ -5,6 +5,13 @@ require 'rubygems'
5
5
  require 'bundler/setup'
6
6
  require 'active_record'
7
7
 
8
+ begin
9
+ require 'pry'
10
+ require 'pry-byebug'
11
+ rescue LoadError
12
+ # jruby won't have these
13
+ end
14
+
8
15
  require 'locker'
9
16
 
10
17
  ActiveRecord::Base.time_zone_aware_attributes = true
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: locker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan Sutton
8
8
  - Justin Greer
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-04-03 00:00:00.000000000 Z
12
+ date: 2020-08-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
@@ -20,7 +20,7 @@ dependencies:
20
20
  version: '3.2'
21
21
  - - "<"
22
22
  - !ruby/object:Gem::Version
23
- version: '5'
23
+ version: '6'
24
24
  type: :runtime
25
25
  prerelease: false
26
26
  version_requirements: !ruby/object:Gem::Requirement
@@ -30,7 +30,7 @@ dependencies:
30
30
  version: '3.2'
31
31
  - - "<"
32
32
  - !ruby/object:Gem::Version
33
- version: '5'
33
+ version: '6'
34
34
  - !ruby/object:Gem::Dependency
35
35
  name: pg
36
36
  requirement: !ruby/object:Gem::Requirement
@@ -38,6 +38,9 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - - "<"
42
+ - !ruby/object:Gem::Version
43
+ version: '1'
41
44
  type: :development
42
45
  prerelease: false
43
46
  version_requirements: !ruby/object:Gem::Requirement
@@ -45,6 +48,37 @@ dependencies:
45
48
  - - "~>"
46
49
  - !ruby/object:Gem::Version
47
50
  version: '0'
51
+ - - "<"
52
+ - !ruby/object:Gem::Version
53
+ version: '1'
54
+ - !ruby/object:Gem::Dependency
55
+ name: pry
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: 0.10.4
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: 0.10.4
68
+ - !ruby/object:Gem::Dependency
69
+ name: pry-byebug
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: 3.4.2
75
+ type: :development
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: 3.4.2
48
82
  - !ruby/object:Gem::Dependency
49
83
  name: rspec
50
84
  requirement: !ruby/object:Gem::Requirement
@@ -69,15 +103,16 @@ extensions: []
69
103
  extra_rdoc_files: []
70
104
  files:
71
105
  - ".gitignore"
72
- - ".ruby-gemset"
73
- - ".ruby-version"
106
+ - ".tool-versions"
74
107
  - ".travis.yml"
75
108
  - CHANGELOG.md
76
109
  - Gemfile
110
+ - Gemfile_rails_3
111
+ - Gemfile_rails_4
112
+ - Gemfile_rails_5
77
113
  - LICENSE
78
114
  - README.md
79
115
  - Rakefile
80
- - gemfiles/rails3.gemfile
81
116
  - generators/locker/USAGE
82
117
  - generators/locker/locker_generator.rb
83
118
  - generators/locker/templates/migration.rb
@@ -94,10 +129,10 @@ files:
94
129
  - spec/locker/advisory_spec.rb
95
130
  - spec/locker/locker_spec.rb
96
131
  - spec/spec_helper.rb
97
- homepage:
132
+ homepage:
98
133
  licenses: []
99
134
  metadata: {}
100
- post_install_message:
135
+ post_install_message:
101
136
  rdoc_options: []
102
137
  require_paths:
103
138
  - lib
@@ -113,8 +148,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
113
148
  version: '0'
114
149
  requirements: []
115
150
  rubyforge_project: locker
116
- rubygems_version: 2.4.6
117
- signing_key:
151
+ rubygems_version: 2.7.6.2
152
+ signing_key:
118
153
  specification_version: 4
119
154
  summary: Locker is a locking mechanism for limiting the concurrency of ruby code using
120
155
  the database.
@@ -1 +0,0 @@
1
- locker
@@ -1 +0,0 @@
1
- ruby-2.2.1
@@ -1,15 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- gem "activerecord", ">=3.2", "<4"
4
-
5
- platforms :jruby do
6
- gem 'activerecord-jdbcpostgresql-adapter'
7
- end
8
-
9
- platforms :ruby do
10
- gem "pg"
11
- end
12
-
13
- group :development, :test do
14
- gem "rspec"
15
- end