locker 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ /spec/database.yml
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Zencoder
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,177 @@
1
+ # Locker
2
+
3
+ Locker is a locking mechanism for limiting the concurrency of ruby code using the database.
4
+
5
+ Locker is dependent on Postgres and the ActiveRecord (>= 2.3.14) gem.
6
+
7
+ ## The Basics
8
+
9
+ In its simplest form it can be used as follows:
10
+
11
+ ```ruby
12
+ Locker.run("unique-key") do
13
+ # Code that only one process should be running
14
+ end
15
+ ```
16
+
17
+ ## What does it do?
18
+
19
+ Suppose you have a process running on a server that continually performs a task. In our examples we'll use an RSS/Atom feed checker:
20
+
21
+ ### Server 1
22
+
23
+ #### Code (lib/new_feed_checker.rb)
24
+
25
+ ```ruby
26
+ while true
27
+ FeedChecker.check_for_new_feeds
28
+ end
29
+ ```
30
+
31
+ `script/rails runner lib/new_feed_checker.rb`
32
+
33
+ This is great if you have only one server, or if you're okay with running the code on only one of your servers and don't care if the server goes down (and thus the code stops running until the server is back up). If you wanted to make this more fault tolerant you might add another server performing the same task:
34
+
35
+ ### Server 2
36
+
37
+ *Same as Server 1*
38
+
39
+ This would work fantastic, so long as `FeedChecker.check_for_new_feeds` is safe to run concurrently on two or more servers. If it's not safe to run concurrently, you need to either make it concurrency-safe or make sure only one server runs the code at any given time. This is where Locker comes in. Lets change the code to take advantage of Locker.
40
+
41
+ ### Server 1 and 2
42
+
43
+ #### Code (lib/new_feed_checker.rb)
44
+
45
+ ```ruby
46
+ Locker.run("new-feed-checker") do # One server will get the lock
47
+ # Only one server will get here
48
+ while true
49
+ FeedChecker.check_for_new_feeds
50
+ end
51
+ end # Lock is released at this point
52
+ ```
53
+
54
+ `script/rails runner lib/new_feed_checker.rb`
55
+
56
+ When we run this code on both servers, only one server will obtain the lock and run `FeedChecker.check_for_new_feeds`. The other server will simply skip the block entirely. Only the server that obtains the lock will run the code, and only one server can obtain the lock at any given time. The first server to get to the lock wins! After the server that obtained the lock finishes running the code block, the lock will be released.
57
+
58
+ This is great! We've made sure that only one server can run the code at any given time. But wait! Since the server that didn't obtain the lock just skips the code and finishes running we still can't handle one of the servers going down. If only we could wait for the lock to become available instead of skipping the block. Good news, we can!
59
+
60
+ ### Server 1 and 2
61
+
62
+ #### Code (lib/new_feed_checker.rb)
63
+
64
+ ```ruby
65
+ Locker.run("new-feed-checker", :blocking => true) do
66
+ # Only one server will get here at a time. The other server will patiently wait.
67
+ while true
68
+ FeedChecker.check_for_new_feeds
69
+ end
70
+ end # Lock is released at this point
71
+ ```
72
+
73
+ `script/rails runner lib/new_feed_checker.rb`
74
+
75
+ The addition of `:blocking => true` means that whichever server doesn't obtain the lock at first will simply wait and keep trying to get the lock. If the server that first obtains the lock goes down at any point, the second server will automatically take over. By using this technique we've made it so that we don't need to make the code handle concurrency while simultaneously making sure that the code stays running even if a server goes down.
76
+
77
+ ## Installation
78
+
79
+ If you're using bundler you can add it to your 'Gemfile':
80
+
81
+ ```ruby
82
+ gem "locker"
83
+ ```
84
+
85
+ Then, of course, `bundle install`.
86
+
87
+ Otherwise you can just `gem install locker`.
88
+
89
+ ## Setup
90
+
91
+ This gem includes generators for Rails 3.0+:
92
+
93
+ ```bash
94
+ script/rails generate locker [ModelName]
95
+ ```
96
+
97
+ The 'ModelName' defaults to 'Lock'. This will generate the Lock model and its migration.
98
+
99
+ I apologize if you're using Rails 2.3.x, I couldn't be arsed to figure out how to make generators for it and Rails 3.x+, so you'll need to create the migration and the model yourself:
100
+
101
+ ```ruby
102
+ class CreateLocks < ActiveRecord::Migration
103
+ def self.up
104
+ create_table :locks do |t|
105
+ t.string :locked_by
106
+ t.string :key
107
+ t.datetime :locked_at
108
+ t.datetime :locked_until
109
+ end
110
+
111
+ add_index :locks, :key, :unique => true
112
+ end
113
+
114
+ def self.down
115
+ drop_table :locks
116
+ end
117
+ end
118
+ ```
119
+
120
+ ```ruby
121
+ class Lock < ActiveRecord::Base
122
+ end
123
+ ```
124
+
125
+ ## Advanced Usage
126
+
127
+ Locker uses some rather simple methods to accomplish its purpose. These simple methods include obtaining, renewing, and releasing the locks.
128
+
129
+ ```ruby
130
+ lock = Locker.new("some-unique-key")
131
+ lock.get # => true (Lock obtained)
132
+ # Do something that doesn't take too long here
133
+ lock.renew # => true (Lock renewed)
134
+ # Do another thing that doesn't take too long here
135
+ lock.release # => false (Lock released)
136
+ ```
137
+
138
+ The locks consist of records in the `locks` table which have a the following columns: `key`, `locked_by`, `locked_at`, and `locked_until`. The `key` column has uniqueness enforced at the database level to prevent race conditions and duplicate locks. `locked_by` has an identifier unique to the process and object running the code block. This unique identifier makes sure that that we know if we should be able to renew our lock. `locked_at` is a utility column for checking how long a lock has been monopolized. `locked_until` tells us when the lock will expire if it is not renewed.
139
+
140
+ When Locker is used via the `run` method, an auto-renewer thread is run until the `run` block finishes, at which time the lock is released. By default all locks are obtained for 30 seconds and auto-renewed every 10 seconds. Locks that expire can be taken over by other processes or threads. If your lock expires and another process or thread takes over, Locker will raise `Locker::LockStolen`. The lock duration and time between renewals can be customized.
141
+
142
+ ```ruby
143
+ # :lock_for is the lock duration in seconds. Must be greater than 0 and greater than :renew_every
144
+ # :renew_every is the time to sleep between renewals in seconds. Must be greater than 0 and less than :lock_for
145
+ Locker.run("some-unique-key", :lock_for => 60, :renew_every => 5) do
146
+ # Your code goes here
147
+ end
148
+ ```
149
+
150
+ If you changed the name of the Lock model, or if you have multiple Lock models, you can customize the model to be used either when you run `Locker.run` or on the Locker class itself.
151
+
152
+ ```ruby
153
+ Locker.model = SomeOtherOtherLockModel
154
+
155
+ Locker.run("some-unique-key") do
156
+ # Locked using SomeOtherOtherLockModel
157
+ end
158
+
159
+ Locker.run("some-unique-key", :model => SomeOtherLockModel) do
160
+ # Locked using SomeOtherLockModel
161
+ end
162
+ ```
163
+
164
+ ## A Common pattern
165
+
166
+ 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.
167
+
168
+ ```ruby
169
+ while true
170
+ Locker.run("new-feed-checker", :blocking => true) do
171
+ FeedChecker.check_for_new_feeds
172
+ end
173
+ sleep(Kernel.rand + 1) # Delay the next try so that the other servers will have a chance to obtain the lock
174
+ end
175
+ ```
176
+
177
+ Instead of the first server to obtain the lock having a monopoly, each server will obtain a lock only for the duration of the call to `FeedChecker.check_for_new_feeds`. We introduce a random delay so that other servers will have a chance to obtain the lock. If we didn't add that delay then after the first server finished running the FeedChecker it would immediately re-obtain the lock. This is due to how the 'blocking' mechanism works. The blocking mechanism will try to obtain the lock then sleep for half a second, repeating continually until the lock is obtained. The random delay, therefore, will make sure that another server will obtain the lock before the first server will attempt to obtain it again (since 1.Xs > 0.5s), while also randomizing the chances of the first server obtaining locks in the future. In effect this will make sure that over a long enough time period each server will have obtained an equal number of locks. A side benefit of this pattern is that if you don't need the code to run constantly you could introduce a much larger sleep and random value.
@@ -0,0 +1,7 @@
1
+ require 'bundler'
2
+ require 'rspec/core/rake_task'
3
+
4
+ Bundler::GemHelper.install_tasks
5
+ RSpec::Core::RakeTask.new
6
+
7
+ task :default => :spec
@@ -0,0 +1 @@
1
+ Autotest.add_discovery { "rspec2" }
@@ -0,0 +1,11 @@
1
+ Description:
2
+ Explain the generator
3
+
4
+ Example:
5
+ rails generate locker [Lock]
6
+
7
+ 'Lock' is the model name to use for the model.
8
+
9
+ This will create:
10
+ db/migrations/123456_create_locks.rb
11
+ app/models/locks.rb
@@ -0,0 +1,16 @@
1
+ class LockerGenerator < Rails::Generators::NamedBase
2
+ include Rails::Generators::Migration
3
+
4
+ source_root File.expand_path('../templates', __FILE__)
5
+ argument :name, :type => :string, :default => "Lock"
6
+
7
+ def self.next_migration_number(path)
8
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
9
+ end
10
+
11
+ def create_locker_files
12
+ migration_template "migration.rb", "db/migrate/create_#{plural_name}.rb"
13
+ template "model.rb", "app/models/#{singular_name}.rb"
14
+ end
15
+
16
+ end
@@ -0,0 +1,29 @@
1
+ class Create<%= plural_name.camelize %> < ActiveRecord::Migration
2
+ <%- if ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR == 1 -%>
3
+ def change
4
+ create_table :<%= plural_name %> do |t|
5
+ t.string :locked_by
6
+ t.string :key
7
+ t.datetime :locked_at
8
+ t.datetime :locked_until
9
+ end
10
+
11
+ add_index :<%= plural_name %>, :key, :unique => true
12
+ end
13
+ <%- else -%>
14
+ def self.up
15
+ create_table :<%= plural_name %> do |t|
16
+ t.string :locked_by
17
+ t.string :key
18
+ t.datetime :locked_at
19
+ t.datetime :locked_until
20
+ end
21
+
22
+ add_index :<%= plural_name %>, :key, :unique => true
23
+ end
24
+
25
+ def self.down
26
+ drop_table :<%= plural_name %>
27
+ end
28
+ <%- end -%>
29
+ end
@@ -0,0 +1,2 @@
1
+ class <%= class_name %> < ActiveRecord::Base
2
+ end
@@ -0,0 +1,3 @@
1
+ require "active_record"
2
+ require "locker/locker"
3
+ require "locker/version"
@@ -0,0 +1,93 @@
1
+ class Locker
2
+ class LockStolen < StandardError; end
3
+
4
+ if !defined?(SecureRandom)
5
+ SecureRandom = ActiveSupport::SecureRandom
6
+ end
7
+
8
+ attr_accessor :identifier, :key, :renew_every, :lock_for, :model, :locked, :blocking
9
+
10
+ class << self
11
+ attr_accessor :model
12
+ end
13
+
14
+ def initialize(key, options={})
15
+ @identifier = "host:#{Socket.gethostname} pid:#{Process.pid} guid:#{SecureRandom.hex(16)}" rescue "pid:#{Process.pid} guid:#{SecureRandom.hex(16)}"
16
+ @key = key
17
+ @renew_every = (options[:renew_every] || 10.seconds).to_f
18
+ @lock_for = (options[:lock_for] || 30.seconds).to_f
19
+ @model = (options[:model] || self.class.model || ::Lock)
20
+ @blocking = !!options[:blocking]
21
+ @locked = false
22
+
23
+ raise ArgumentError, "renew_every must be greater than 0" if @renew_every <= 0
24
+ raise ArgumentError, "lock_for must be greater than 0" if @lock_for <= 0
25
+ raise ArgumentError, "renew_every must be less than lock_for" if @renew_every >= @lock_for
26
+
27
+ ensure_key_exists
28
+ end
29
+
30
+ def self.run(key, options={}, &block)
31
+ locker = new(key, options)
32
+ locker.run(&block)
33
+ end
34
+
35
+ def run(blocking=@blocking, &block)
36
+ while !get && blocking
37
+ sleep 0.5
38
+ end
39
+
40
+ if @locked
41
+ begin
42
+ renewer = Thread.new do
43
+ while @locked
44
+ sleep @renew_every
45
+ renew
46
+ end
47
+ end
48
+
49
+ block.call
50
+ ensure
51
+ renewer.exit rescue nil
52
+ release if @locked
53
+ end
54
+
55
+ true
56
+ else
57
+ false
58
+ end
59
+ end
60
+
61
+ def get
62
+ @locked = update_all(["locked_by = ?, locked_at = clock_timestamp() at time zone 'UTC', locked_until = clock_timestamp() at time zone 'UTC' + #{lock_interval}", @identifier],
63
+ ["key = ? AND (locked_by IS NULL OR locked_by = ? OR locked_until < clock_timestamp() at time zone 'UTC')", @key, @identifier])
64
+ end
65
+
66
+ def release
67
+ @locked = update_all(["locked_by = NULL"],["key = ? and locked_by = ?", @key, @identifier])
68
+ end
69
+
70
+ def renew
71
+ @locked = update_all(["locked_until = clock_timestamp() at time zone 'UTC' + #{lock_interval}"], ["key = ? and locked_by = ?", @key, @identifier])
72
+ raise LockStolen unless @locked
73
+ @locked
74
+ end
75
+
76
+ private
77
+
78
+ def lock_interval
79
+ "interval '#{@lock_for} seconds'"
80
+ end
81
+
82
+ def ensure_key_exists
83
+ model.find_by_key(@key) || model.create(:key => @key)
84
+ rescue ActiveRecord::StatementInvalid => e
85
+ raise unless e.message =~ /duplicate key value violates unique constraint/
86
+ end
87
+
88
+ # Returns a boolean. True if it updates any rows, false if it didn't.
89
+ def update_all(*args)
90
+ model.update_all(*args) > 0
91
+ end
92
+
93
+ end
@@ -0,0 +1,3 @@
1
+ class Locker
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "locker/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "locker"
7
+ s.version = Locker::VERSION
8
+ s.authors = ["Nathan Sutton", "Justin Greer"]
9
+ 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.}
12
+
13
+ s.rubyforge_project = "locker"
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- spec/*`.split("\n")
17
+ s.require_paths = ["lib"]
18
+
19
+ s.add_dependency "activerecord", ">=2.3.14"
20
+ s.add_development_dependency "pg"
21
+ s.add_development_dependency "rspec"
22
+ s.add_development_dependency "autotest"
23
+ end
@@ -0,0 +1,119 @@
1
+ require "spec_helper"
2
+
3
+ describe Locker do
4
+ before do
5
+ Lock.delete_all
6
+ FakeLock.fake_locks = {}
7
+ end
8
+
9
+ describe "initialization" do
10
+ it "should have default values" do
11
+ locker = Locker.new("foo")
12
+ locker.key.should == "foo"
13
+ locker.renew_every.should == 10
14
+ locker.lock_for.should == 30
15
+ locker.model.should == Lock
16
+ locker.identifier.should match(/^#{Regexp.escape("host:#{Socket.gethostname} pid:#{Process.pid}")} guid:[a-f0-9]+$/)
17
+ locker.blocking.should be_false
18
+ locker.locked.should be_false
19
+ end
20
+
21
+ it "should have some overridable values" do
22
+ locker = Locker.new("bar", :renew_every => 20.seconds, :lock_for => 1.minute, :blocking => true, :model => FakeLock)
23
+ locker.key.should == "bar"
24
+ locker.renew_every.should == 20
25
+ locker.lock_for.should == 60
26
+ locker.model.should == FakeLock
27
+ locker.identifier.should match(/^#{Regexp.escape("host:#{Socket.gethostname} pid:#{Process.pid}")} guid:[a-f0-9]+$/)
28
+ locker.blocking.should be_true
29
+ locker.locked.should be_false
30
+ end
31
+
32
+ it "should ensure that the key exists" do
33
+ Lock.find_by_key("baz").should be_nil
34
+ Locker.new("baz", :renew_every => 20.seconds, :lock_for => 1.minute, :blocking => true)
35
+ Lock.find_by_key("baz").should_not be_nil
36
+ Locker.new("baz", :renew_every => 20.seconds, :lock_for => 1.minute, :blocking => true)
37
+ end
38
+
39
+ it "should validate renew_every and lock_for values" do
40
+ expect{ Locker.new("foo", :renew_every => 0) }.to raise_error(ArgumentError)
41
+ expect{ Locker.new("foo", :renew_every => 1) }.to_not raise_error
42
+ expect{ Locker.new("foo", :lock_for => 0) }.to raise_error(ArgumentError)
43
+ expect{ Locker.new("foo", :renew_every => 4, :lock_for => 2) }.to raise_error(ArgumentError)
44
+ expect{ Locker.new("foo", :renew_every => 1, :lock_for => 1.00001) }.to_not raise_error
45
+ end
46
+ end
47
+
48
+ describe "locking" do
49
+ it "should lock a record" do
50
+ locker = Locker.new("foo")
51
+ locker.get.should be_true
52
+ lock = Lock.find_by_key("foo")
53
+ lock.locked_until.should be <= (Time.now.utc + locker.lock_for)
54
+ lock.locked_by.should == locker.identifier
55
+ lock.locked_at.should be < Time.now.utc
56
+ end
57
+
58
+ it "should renew a lock" do
59
+ locker = Locker.new("foo")
60
+ locker.get.should be_true
61
+ lock = Lock.find_by_key("foo")
62
+ lock.locked_until.should be <= (Time.now.utc + locker.lock_for)
63
+ lock.locked_by.should == locker.identifier
64
+ lock.locked_at.should be < Time.now.utc
65
+ locker.renew.should be_true
66
+ lock = Lock.find_by_key("foo")
67
+ lock.locked_until.should be <= (Time.now.utc + locker.lock_for)
68
+ lock.locked_by.should == locker.identifier
69
+ lock.locked_at.should be < Time.now.utc
70
+ end
71
+
72
+ it "should raise when someone steals the lock" do
73
+ locker = Locker.new("foo")
74
+ locker.get.should be_true
75
+ lock = Lock.find_by_key("foo")
76
+ lock.update_attribute(:locked_by, "someone else")
77
+ expect{ locker.renew }.to raise_error(Locker::LockStolen)
78
+ end
79
+ end
80
+
81
+ describe "blocking" do
82
+ before do
83
+ @first_locker = Locker.new("foo", :renew_every => 0.2.second, :lock_for => 0.6.second)
84
+ @second_locker = Locker.new("foo")
85
+ @first_locker.get
86
+ end
87
+
88
+ it "should block and wait for the first lock to release before running the second" do
89
+ start_time = Time.now.to_f
90
+ @second_locker.run(true){"something innocuous"}
91
+ end_time = Time.now.to_f
92
+ time_ran = (end_time - start_time)
93
+ time_ran.should be >= 0.6, "Oops, time was #{end_time-start_time} seconds"
94
+ end
95
+ end
96
+
97
+ describe "non-blocking" do
98
+ before do
99
+ @first_locker = Locker.new("foo")
100
+ @second_locker = Locker.new("foo")
101
+ @first_locker.get
102
+ end
103
+
104
+ it "should return false when we can't obtain the lock" do
105
+ @second_locker.run{raise "SHOULD NOT RUN KTHXBAI"}.should be_false
106
+ @first_locker.run{ "something" }.should be_true
107
+ end
108
+
109
+ it "should take less than half a second to fail" do
110
+ start_time = Time.now.to_f
111
+ return_value = @second_locker.run{raise "SHOULD NOT RUN KTHXBAI"}
112
+ end_time = Time.now.to_f
113
+ return_value.should be_false
114
+ run_time = (end_time - start_time)
115
+ run_time.should be < 0.5
116
+ end
117
+ end
118
+
119
+ end
@@ -0,0 +1,65 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ require 'rubygems'
5
+ require 'bundler/setup'
6
+ require 'active_record'
7
+
8
+ require 'locker'
9
+
10
+ ActiveRecord::Base.time_zone_aware_attributes = true
11
+ ActiveRecord::Base.default_timezone = "UTC"
12
+
13
+ config = YAML.load_file(File.join(File.dirname(__FILE__), 'database.yml'))
14
+ ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public'))
15
+ begin
16
+ ActiveRecord::Base.establish_connection(config)
17
+ rescue
18
+ ActiveRecord::Base.connection.create_database(config['database'], config.merge("encoding" => config['encoding'] || ENV['CHARSET'] || 'utf8'))
19
+ ActiveRecord::Base.establish_connection(config)
20
+ end
21
+
22
+ ActiveRecord::Base.connection.execute("DROP TABLE IF EXISTS locks")
23
+ ActiveRecord::Base.connection.create_table(:locks) do |t|
24
+ t.string :locked_by
25
+ t.string :key
26
+ t.datetime :locked_at
27
+ t.datetime :locked_until
28
+ end
29
+ ActiveRecord::Base.connection.add_index :locks, :key, :unique => true
30
+
31
+ class Lock < ActiveRecord::Base
32
+ end
33
+
34
+ class FakeLock
35
+ attr_accessor :locked_by, :key, :locked_at, :locked_until
36
+
37
+ cattr_accessor :fake_locks
38
+ self.fake_locks = {}
39
+
40
+ def self.find_by_key(key)
41
+ fake_locks[key]
42
+ end
43
+
44
+ def self.create(attributes={})
45
+ fake_locks[attributes[:key]] = new(attributes)
46
+ true
47
+ end
48
+
49
+ def initialize(attributes={})
50
+ attributes.each do |key, value|
51
+ send("#{key}=", value)
52
+ end
53
+ end
54
+ end
55
+
56
+ RSpec.configure do |c|
57
+ c.before do
58
+ ActiveRecord::Base.connection.increment_open_transactions
59
+ ActiveRecord::Base.connection.begin_db_transaction
60
+ end
61
+ c.after do
62
+ ActiveRecord::Base.connection.rollback_db_transaction
63
+ ActiveRecord::Base.connection.decrement_open_transactions
64
+ end
65
+ end
metadata ADDED
@@ -0,0 +1,141 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: locker
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Nathan Sutton
14
+ - Justin Greer
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2011-08-31 00:00:00 Z
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: activerecord
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 31
30
+ segments:
31
+ - 2
32
+ - 3
33
+ - 14
34
+ version: 2.3.14
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: pg
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 3
46
+ segments:
47
+ - 0
48
+ version: "0"
49
+ type: :development
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ name: rspec
53
+ prerelease: false
54
+ requirement: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ hash: 3
60
+ segments:
61
+ - 0
62
+ version: "0"
63
+ type: :development
64
+ version_requirements: *id003
65
+ - !ruby/object:Gem::Dependency
66
+ name: autotest
67
+ prerelease: false
68
+ requirement: &id004 !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ hash: 3
74
+ segments:
75
+ - 0
76
+ version: "0"
77
+ type: :development
78
+ version_requirements: *id004
79
+ description: Locker is a locking mechanism for limiting the concurrency of ruby code using the database. It presently only works with PostgreSQL.
80
+ email:
81
+ - nate@zencoder.com
82
+ - justin@zencoder.com
83
+ executables: []
84
+
85
+ extensions: []
86
+
87
+ extra_rdoc_files: []
88
+
89
+ files:
90
+ - .gitignore
91
+ - Gemfile
92
+ - LICENSE
93
+ - README.md
94
+ - Rakefile
95
+ - autotest/discover.rb
96
+ - lib/generators/locker/USAGE
97
+ - lib/generators/locker/locker_generator.rb
98
+ - lib/generators/locker/templates/migration.rb
99
+ - lib/generators/locker/templates/model.rb
100
+ - lib/locker.rb
101
+ - lib/locker/locker.rb
102
+ - lib/locker/version.rb
103
+ - locker.gemspec
104
+ - spec/locker/locker_spec.rb
105
+ - spec/spec_helper.rb
106
+ homepage:
107
+ licenses: []
108
+
109
+ post_install_message:
110
+ rdoc_options: []
111
+
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ none: false
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ hash: 3
120
+ segments:
121
+ - 0
122
+ version: "0"
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ none: false
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ hash: 3
129
+ segments:
130
+ - 0
131
+ version: "0"
132
+ requirements: []
133
+
134
+ rubyforge_project: locker
135
+ rubygems_version: 1.8.7
136
+ signing_key:
137
+ specification_version: 3
138
+ summary: Locker is a locking mechanism for limiting the concurrency of ruby code using the database.
139
+ test_files:
140
+ - spec/locker/locker_spec.rb
141
+ - spec/spec_helper.rb