Skip to content

Commit

Permalink
Initial import.
Browse files Browse the repository at this point in the history
  • Loading branch information
jpr5 committed Jul 13, 2011
0 parents commit 44e7651
Show file tree
Hide file tree
Showing 8 changed files with 534 additions and 0 deletions.
Empty file added .gitignore
Empty file.
1 change: 1 addition & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Still working this out.
42 changes: 42 additions & 0 deletions README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
##
## Usages
##
#
# Order.lockable!
# Order.lockable! :key => :id
# Order.lockable! :key => proc { |me| SHA1.hexdigest(me.balls) }
# Order.lockable! :scope => "OtherClass"
#
# OrderItem.locked_by! { |me| me.order }
# OrderItem.locked_by! :parent => proc { |me| me.order }
# OrderItem.locked_by! :order
# OrderItem.locked_by! :parent => :order
#
##
## Useful tests
##
#
# Pn == process N
# Order.id == 1
# OrderItem.id == 1, OrderItem.order_id = 1
#
# - General race, same object
#
# P1: Order.first.lock { debugger } # gets and holds lock
# P2: Order.first.lock { puts "hi" } # retries acquire, fails
#
# - General race, locked root, attempt to lock from child
#
# P1: Order.first.lock { debugger } # gets and holds lock
# P2: OrderItem.first.lock { puts "hi" } # retries acquire, fails
#
# - General race, locked root from child, attempt to lock from child
#
# P1: OrderItem.first.lock { debugger } # gets and holds lock
# P2: OrderItem.first.lock { puts "hi" } # retries acquire, fails
#
# - Nested lock acquisition
#
# P1: Order.first.lock { puts "1"; Order.first.lock { puts "2" } }
# # should see 1 and 2
#
3 changes: 3 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
task :default do
puts "TBC"
end
95 changes: 95 additions & 0 deletions lib/mongo/locking.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
##
## Mongo Locking Library
##
#
# Despite the Locker being placed on the model classes, the dependency graph is
# actually instance-based, thus the graph can only be resolved during runtime.
#
# Locking takes an instance to start with, optionally walks the graph through
# parent "pointers" (procs whose arbitrary function produces the parent
# lockable), then does the atomic incr/decr dance with the root lockable's key.
#
# TODO: More docs. TBC.

require 'mongo'
require 'active_support/core_ext/module/delegation'

module Mongo
module Locking
extend self

module Exceptions
class Error < RuntimeError; end
class ArgumentError < Error; end # bad param
class InvalidConfig < Error; end # something bogus in config[]
class CircularLock < Error; end # circular lock reference
class LockTimeout < Error; end # lock failed, retries unsuccessful
class LockFailure < Error; end # lock failed, something bad happened
end
include Exceptions

attr_accessor :logger, :collection
delegate :debug, :info, :warn, :error, :fatal, :to => :logger, :allow_nil => true

def included(klass)
klass.send(:include, ModelMethods)
end

##
## Configuration
##

def configure(opts = {})
opts.each { |k, v| send("#{k}=", v) }
return self
end

def collection=(new)
@collection = new
ensure_indices if @collection.kind_of? Mongo::Collection
return @collection
end

# Allow for a proc to be initially set as the collection, in case of
# delayed loading/configuration/whatever. It'll only be materialized
# when first accessed, which is presumably either when ensure_indices is
# called imperatively, or more likely upon the first lock call.
def collection
if @collection.kind_of? Proc
@collection = @collection.call
ensure_indices if @collection.kind_of? Mongo::Collection
end
return @collection
end

##
## Document Indices
##
#
# A Mongoid model would look like:
# field :scope, :type => String
# field :key, :type => String
# field :refcount, :type => Integer, :default => 0
# field :expire_at, :type => DateTime
# index [ [ :scope, Mongo::ASCENDING ], [ :key, Mongo::ASCENDING ] ], :unique => true
# index :refcount
# index :expire_at

LOCK_INDICES = {
[["scope", Mongo::ASCENDING], ["key", Mongo::ASCENDING]] => { :unique => true, :background => true },
[["refcount", Mongo::ASCENDING]] => { :unique => false, :background => true },
[["expire_at", Mongo::ASCENDING]] => { :unique => false, :background => true },
}

def ensure_indices
LOCK_INDICES.each do |spec, opts|
@collection.ensure_index(spec, opts)
end
end

end # Locking
end # Mongo

require 'mongo/locking/locker'
require 'mongo/locking/model_methods'

Loading

0 comments on commit 44e7651

Please sign in to comment.