Permalink
Browse files

Avoid conflicts with top level Mutex class

  • Loading branch information...
flori committed May 7, 2012
1 parent 23451ac commit c3c5a3ec5b435e6a2721c920169a9e373df9433d
View
@@ -1,3 +1,3 @@
-pkg
-Gemfile.lock
.*.sw[pon]
+Gemfile.lock
+pkg
View
@@ -2,16 +2,9 @@
== Description
-This gem provides a Mutex that is based on ctiveRecord's database connection.
+This gem provides a Mutex that is based on ActiveRecord's database connection.
(At the moment this only works for Mysql.) It can be used to synchronise
-different ruby processes via the connected database.
-
-== Download
-
-The latest version of the <b>active_record_mutex</b> source archive can be
-found at
-
-* http://www.ping.de/~flori
+ruby processes (also on different hosts) via the connected database.
== Installation
View
@@ -4,7 +4,7 @@ require 'gem_hadar'
GemHadar do
name 'active_record_mutex'
- path_name 'active_record/mutex'
+ path_name 'active_record/database_mutex'
author 'Florian Frank'
email 'flori@ping.de'
homepage "http://github.com/flori/#{name}"
@@ -15,6 +15,6 @@ GemHadar do
ignore '.*.sw[pon]', 'pkg', 'Gemfile.lock'
readme 'README.rdoc'
- dependency 'mysql2', '~>0.2.11'
+ dependency 'mysql2', '~>0.3.0'
dependency 'activerecord'
end
View
@@ -1 +1 @@
-0.0.1
+1.0.0
@@ -1,38 +1,38 @@
# -*- encoding: utf-8 -*-
Gem::Specification.new do |s|
- s.name = %q{active_record_mutex}
- s.version = "0.0.1"
+ s.name = "active_record_mutex"
+ s.version = "1.0.0"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Florian Frank"]
- s.date = %q{2011-07-18}
- s.description = %q{Mutex that can be used to synchronise ruby processes via an ActiveRecord datababase connection. (Only Mysql is supported at the moment.)}
- s.email = %q{flori@ping.de}
- s.extra_rdoc_files = ["README.rdoc", "lib/active_record/mutex/version.rb", "lib/active_record/mutex.rb", "lib/active_record_mutex.rb"]
- s.files = [".gitignore", "CHANGES", "Gemfile", "README.rdoc", "Rakefile", "VERSION", "active_record_mutex.gemspec", "lib/active_record/mutex.rb", "lib/active_record/mutex/version.rb", "lib/active_record_mutex.rb", "test/mutex_test.rb"]
- s.homepage = %q{http://github.com/flori/active_record_mutex}
+ s.date = "2012-05-07"
+ s.description = "Mutex that can be used to synchronise ruby processes via an ActiveRecord datababase connection. (Only Mysql is supported at the moment.)"
+ s.email = "flori@ping.de"
+ s.extra_rdoc_files = ["README.rdoc", "lib/active_record_mutex.rb", "lib/active_record/mutex.rb", "lib/active_record/database_mutex.rb", "lib/active_record/database_mutex/version.rb", "lib/active_record/database_mutex/implementation.rb"]
+ s.files = [".gitignore", "CHANGES", "Gemfile", "README.rdoc", "Rakefile", "VERSION", "active_record_mutex.gemspec", "lib/active_record/database_mutex.rb", "lib/active_record/database_mutex/implementation.rb", "lib/active_record/database_mutex/version.rb", "lib/active_record/mutex.rb", "lib/active_record_mutex.rb", "test/mutex_test.rb"]
+ s.homepage = "http://github.com/flori/active_record_mutex"
s.rdoc_options = ["--title", "ActiveRecordMutex - Implementation of a Mutex for Active Record", "--main", "README.rdoc"]
s.require_paths = ["lib"]
- s.rubygems_version = %q{1.7.2}
- s.summary = %q{Implementation of a Mutex for Active Record}
+ s.rubygems_version = "1.8.24"
+ s.summary = "Implementation of a Mutex for Active Record"
s.test_files = ["test/mutex_test.rb"]
if s.respond_to? :specification_version then
s.specification_version = 3
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
- s.add_development_dependency(%q<gem_hadar>, ["~> 0.0.6"])
- s.add_runtime_dependency(%q<mysql2>, ["~> 0.2.11"])
+ s.add_development_dependency(%q<gem_hadar>, ["~> 0.0.12"])
+ s.add_runtime_dependency(%q<mysql2>, ["~> 0.3.0"])
s.add_runtime_dependency(%q<activerecord>, [">= 0"])
else
- s.add_dependency(%q<gem_hadar>, ["~> 0.0.6"])
- s.add_dependency(%q<mysql2>, ["~> 0.2.11"])
+ s.add_dependency(%q<gem_hadar>, ["~> 0.0.12"])
+ s.add_dependency(%q<mysql2>, ["~> 0.3.0"])
s.add_dependency(%q<activerecord>, [">= 0"])
end
else
- s.add_dependency(%q<gem_hadar>, ["~> 0.0.6"])
- s.add_dependency(%q<mysql2>, ["~> 0.2.11"])
+ s.add_dependency(%q<gem_hadar>, ["~> 0.0.12"])
+ s.add_dependency(%q<mysql2>, ["~> 0.3.0"])
s.add_dependency(%q<activerecord>, [">= 0"])
end
end
@@ -0,0 +1,42 @@
+require 'active_record'
+require 'active_record/database_mutex/version'
+require 'active_record/database_mutex/implementation'
+
+module ActiveRecord
+ # This module is mixed into ActiveRecord::Base to provide the mutex methods
+ # that return a mutex for a particular ActiveRecord::Base subclass/instance.
+ module DatabaseMutex
+ # This is the base exception of all mutex exceptions.
+ class MutexError < ActiveRecordError; end
+
+ # This exception is raised if a mutex of the given name isn't locked at the
+ # moment and unlock was called.
+ class MutexUnlockFailed < MutexError; end
+
+ # This exception is raised if a mutex of the given name is locked at the
+ # moment and lock was called again.
+ class MutexLocked < MutexError; end
+
+ def self.included(modul)
+ modul.instance_eval do
+ extend ClassMethods
+ end
+ end
+
+ module ClassMethods
+ # Returns a mutex instance for this ActiveRecord subclass.
+ def mutex
+ @mutex ||= Implementation.new(:name => name)
+ end
+ end
+
+ # Returns a mutex instance for this ActiveRecord instance.
+ def mutex
+ @mutex ||= Implementation.new(:name => self.class.name)
+ end
+ end
+end
+
+ActiveRecord::Base.class_eval do
+ include ActiveRecord::DatabaseMutex
+end
@@ -0,0 +1,111 @@
+module ActiveRecord
+ module DatabaseMutex
+ class Implementation
+ # Creates a mutex with the name given with the option :name.
+ def initialize(opts = {})
+ @name = opts[:name] or raise ArgumentError, "mutex requires a :name argument"
+ end
+
+ # Returns the name of this mutex as given as a constructor argument.
+ attr_reader :name
+
+ # Locks the mutex if it isn't already locked via another database
+ # connection and yields to the given block. After executing the block's
+ # content the mutex is unlocked (only if it was locked by this
+ # synchronize method before).
+ #
+ # If the mutex was already locked by another database connection the
+ # method blocks until it could aquire the lock and only then the block's
+ # content is executed. If the mutex was already locked by the current database
+ # connection then the block's content is run and the the mutex isn't
+ # unlocked afterwards.
+ #
+ # If a value in seconds is passed to the :timeout option the blocking
+ # ends after that many seconds and the method returns immediately if the
+ # lock couldn't be aquired during that time.
+ def synchronize(opts = {})
+ locked_before = aquired_lock?
+ lock opts
+ yield
+ rescue ActiveRecord::DatabaseMutex::MutexLocked
+ return nil
+ ensure
+ locked_before or unlock
+ end
+
+ # Locks the mutex and returns true if successful. If the mutex is
+ # already locked and the timeout in seconds is given as the :timeout
+ # option, this method raises a MutexLocked exception after that many
+ # seconds. If the :timeout option wasn't given, this method blocks until
+ # the lock could be aquired.
+ def lock(opts = {})
+ if opts[:timeout]
+ lock_with_timeout opts
+ else
+ begin
+ lock_with_timeout :timeout => 1
+ rescue MutexLocked
+ retry
+ end
+ end
+ end
+
+ # Unlocks the mutex and returns true if successful. Otherwise this method
+ # raises a MutexLocked exception.
+ def unlock(*)
+ case query("SELECT RELEASE_LOCK(#{ActiveRecord::Base.quote_value(name)})")
+ when 1 then true
+ when 0, nil then raise MutexUnlockFailed, "unlocking of mutex '#{name}' failed"
+ end
+ end
+
+ # Returns true if this mutex is unlocked at the moment.
+ def unlocked?
+ query("SELECT IS_FREE_LOCK(#{ActiveRecord::Base.quote_value(name)})") == 1
+ end
+
+ # Returns true if this mutex is locked at the moment.
+ def locked?
+ not unlocked?
+ end
+
+ # Returns true if this mutex is locked by this database connection.
+ def aquired_lock?
+ query("SELECT CONNECTION_ID() = IS_USED_LOCK(#{ActiveRecord::Base.quote_value(name)})") == 1
+ end
+
+ # Returns true if this mutex is not locked by this database connection.
+ def not_aquired_lock?
+ not aquired_lock?
+ end
+
+ # Returns a string representation of this DatabaseMutex instance.
+ def to_s
+ "#<#{self.class} #{name}>"
+ end
+
+ alias inspect to_s
+
+ private
+
+ def lock_with_timeout(opts = {})
+ timeout = opts[:timeout] || 1
+ case query("SELECT GET_LOCK(#{ActiveRecord::Base.quote_value(name)}, #{timeout})")
+ when 1 then true
+ when 0 then raise MutexLocked, "mutex '#{name}' is already locked"
+ end
+ end
+
+ def query(sql)
+ if result = ActiveRecord::Base.connection.execute(sql)
+ result = result.first.first.to_i
+ $DEBUG and warn %{query("#{sql}") = #{result}}
+ end
+ result
+ rescue ActiveRecord::StatementInvalid
+ nil
+ end
+ end
+ end
+end
+
@@ -1,6 +1,6 @@
-module ActiveRecord::Mutex
- # ActiveRecord::Mutex version
- VERSION = '0.0.1'
+module ActiveRecord::DatabaseMutex
+ # ActiveRecord::DatabaseMutex version
+ VERSION = '1.0.0'
VERSION_ARRAY = VERSION.split(/\./).map { |x| x.to_i } # :nodoc:
VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
Oops, something went wrong.

0 comments on commit c3c5a3e

Please sign in to comment.