Permalink
Browse files

Initial import.

  • Loading branch information...
0 parents commit 44e7651562314490674cd71d08171e6a6a69bc64 @jpr5 jpr5 committed Jul 13, 2011
Showing with 534 additions and 0 deletions.
  1. 0 .gitignore
  2. +1 −0 LICENSE
  3. +42 −0 README.txt
  4. +3 −0 Rakefile
  5. +95 −0 lib/mongo/locking.rb
  6. +274 −0 lib/mongo/locking/locker.rb
  7. +101 −0 lib/mongo/locking/model_methods.rb
  8. +18 −0 mongo-locking.gemspec
No changes.
@@ -0,0 +1 @@
+Still working this out.
@@ -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
+#
@@ -0,0 +1,3 @@
+task :default do
+ puts "TBC"
+end
@@ -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'
+
Oops, something went wrong.

0 comments on commit 44e7651

Please sign in to comment.